By Xu Lun, F(X)Team, Ali Tao Department
In order to be compatible with older browsers, especially IE series, front-end code using ES6 or above specifications is often transcoded into ES5 code using transcoding tools such as Babel.
It’s been six years since the release of ES6 in 2015. How compatible are browsers with ES6?
Let’s take a look at CanIUse’s data:
As you can see, 98.14% of browsers support ES6. The reason why it doesn’t exceed 99% is that Opera Mini, released in 2015, still has 1.08% usage. For mobile, Safari on iOS and Chrome released after 2016 all support ES6. Safari on iOS 7-9.3 currently accounts for 0.15% of users. Android WebView has fully supported ES6 since version 5.
The fact that more than 99 percent of the device’s capacity is not being used because of a small number of older devices does not seem statistically convincing. In addition, many applications have special processing for low-end machines, and high-end machines must be the recent old equipment. At least for mid – and high-end machines, the compatibility of transcoding is essentially negligible.
However, ES6 and above are made up of multiple features and cannot be abstracted as if 6 is better than 5. We will be the main function transcoding and not transcoding to do a comparison.
No transcoding is better
const
Const is checked with a constant. Here’s an example:
let f1 = () = > {
const a = 0;
a = 2;
};
f1();
Copy the code
After transcoding, Babel generates a _readOnlyError function for us.
function _readOnlyError(name) { throw new TypeError("\" " + name + "\" is read-only"); }
var f1 = function f1() {
var a = 0;
2, _readOnlyError("a");
};
f1();
Copy the code
This need not look at the bytecode, look at the source code to know which is better.
Copy an array
After ES6, we do array copy using the extension operator “…” .
const a1 = [1.2.3.4.5.6.7.8.9.10];
let a2 = [...a1];
Copy the code
Babel does transcoding without ambiguity, a concat function does it:
var a1 = [1.2.3.4.5.6.7.8.9.10];
var a2 = [].concat(a1);
Copy the code
From a bytecode perspective, however, things are different. Because V8 provides the CreateArrayFromIterable directive. So, before transcoding, the 9-byte instruction is done:
Bytecode length: 9
Parameter count 1
Register count 2
Frame size 16
OSR nesting level: 0
Bytecode Age: 0
0x3c140829374e @ 0 : 79 00 00 25 CreateArrayLiteral [0], [0], #37
0x3c1408293752 @ 4 : c4 Star0
0x3c1408293753 @ 5 : 7a CreateArrayFromIterable
0x3c1408293754 @ 6 : c3 Star1
0x3c1408293755 @ 7 : 0e LdaUndefined
0x3c1408293756 @ 8 : a9 Return
Constant pool (size = 1)
0x3c1408293721: [FixedArray] in OldSpace
- map: 0x3c1408002205 <Map>
- length: 1
0: 0x3c1408293715 <ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x3c14082936e5 <FixedArray[10]>>
Copy the code
After the transcoding comes the function call, and generating an empty array takes 21 bytes:
0x3c1408293696 @ 0 : 79 00 00 25 CreateArrayLiteral [0], [0], #37
0x3c140829369a @ 4 : c4 Star0
0x3c140829369b @ 5 : 7b 01 CreateEmptyArrayLiteral [1]
0x3c140829369d @ 7 : c1 Star3
0x3c140829369e @ 8 : 2d f7 01 02 LdaNamedProperty r3, [1], [2]
0x3c14082936a2 @ 12 : c2 Star2
0x3c14082936a3 @ 13 : 5e f8 f7 fa 04 CallProperty1 r2, r3, r0, [4]
0x3c14082936a8 @ 18 : c3 Star1
0x3c14082936a9 @ 19 : 0e LdaUndefined
0x3c14082936aa @ 20 : a9 Return
Constant pool (size = 2)
0x3c1408293665: [FixedArray] in OldSpace
- map: 0x3c1408002205 <Map>
- length: 2
0: 0x3c1408293659 <ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x3c1408293629 <FixedArray[10]>>
1: 0x3c1408202e9d <String[6]: #concat>
Copy the code
String.raw
For String.raw, transcoding also generates extra functions. For example, before transcoding, it is like this:
let f1 = () = > {
String.raw`\n`;
};
f1();
Copy the code
After transcoding, Babel helps us generate a _taggedTemplateLiteral function:
var _templateObject;
function _taggedTemplateLiteral(strings, raw) { if(! raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
var f1 = function f1() {
String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n"))); }; f1();Copy the code
Symbol
Symbol is the new data type in ES6. In ES6, to determine its type, we simply use the Typeof operator.
let f2 = () = > {
let s1 = Symbol(a);return typeof s1;
};
Copy the code
Babel has to introduce a library to solve this problem:
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function"= =typeof Symbol && "symbol"= =typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function"= =typeof Symbol && obj.constructor === Symbol&& obj ! = =Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var f1 = function f1() {
var s1 = Symbol(a);return _typeof(s1);
};
Copy the code
Rest parameters
To support REST parameters, V8 provides the CreateRestParameter directive. However, the original arguments are supported by the CreateMappedArguments directive. It’s a draw.
However, from a source code point of view, non-transcoding is shorter:
let f1 = (. values) = > {
let sum = 0;
for (let v of values) {
sum += v;
}
return sum;
};
f1(1.4.9);
Copy the code
Transcoding is as follows:
var f1 = function f1() {
var sum = 0;
for (var _len = arguments.length, values = new Array(_len), _key = 0; _key < _len; _key++) {
values[_key] = arguments[_key];
}
for (var _i = 0, _values = values; _i < _values.length; _i++) {
var v = _values[_i];
sum += v;
}
return sum;
};
Copy the code
Optional catch parameter
This is a feature of ES2019 to omit error types in catches. Safari will be available in the first half of 2018.
let f3 = f2= > {
try {
f2();
} catch {
console.error("Error"); }};Copy the code
After transcoding, Babel will give us an unused error variable _unused:
var f1 = function f1(f2) {
try {
f2();
} catch (_unused) {
console.error("Error"); }};Copy the code
V8 generates a CatchContext for us via CreateCatchContext and a CATCH_SCOPE for the catch block:
0x1937082936b6 @ 0 : 19 ff fa Mov <context>, r0
0x1937082936b9 @ 3 : 61 03 00 CallUndefinedReceiver0 a0, [0]
0x1937082936bc @ 6 : 8a 20 Jump [32] (0x1937082936dc @ 38)
0x1937082936be @ 8 : c3 Star1
0x1937082936bf @ 9 : 82 f9 00 CreateCatchContext r1, [0]
0x1937082936c2 @ 12 : c4 Star0
0x1937082936c3 @ 13 : 10 LdaTheHole
0x1937082936c4 @ 14 : a6 SetPendingMessage
0x1937082936c5 @ 15 : 0b fa Ldar r0
0x1937082936c7 @ 17 : 1a f9 PushContext r1
0x1937082936c9 @ 19 : 21 01 02 LdaGlobal [1], [2]
0x1937082936cc @ 22 : c1 Star3
0x1937082936cd @ 23 : 2d f7 02 04 LdaNamedProperty r3, [2], [4]
0x1937082936d1 @ 27 : c2 Star2
0x1937082936d2 @ 28 : 13 03 LdaConstant [3]
0x1937082936d4 @ 30 : c0 Star4
0x1937082936d5 @ 31 : 5e f8 f7 f6 06 CallProperty1 r2, r3, r4, [6]
0x1937082936da @ 36 : 1b f9 PopContext r1
0x1937082936dc @ 38 : 0e LdaUndefined
0x1937082936dd @ 39 : a9 Return
Constant pool (size = 4)
0x19370829367d: [FixedArray] in OldSpace
- map: 0x193708002205 <Map>
- length: 4
0: 0x193708293649 <ScopeInfo CATCH_SCOPE>
1: 0x193708202741 <String[7]: #console>
2: 0x193708202769 <String[5]: #error>
3: 0x19370800455d <String[5]: #Error>
Copy the code
CatchContext is not generated when the catch argument is optional:
0x19370829376a @ 0 : 19 ff fa Mov <context>, r0
0x19370829376d @ 3 : 61 03 00 CallUndefinedReceiver0 a0, [0]
0x193708293770 @ 6 : 8a 15 Jump [21] (0x193708293785 @ 27)
0x193708293772 @ 8 : 10 LdaTheHole
0x193708293773 @ 9 : a6 SetPendingMessage
0x193708293774 @ 10 : 21 00 02 LdaGlobal [0], [2]
0x193708293777 @ 13 : c2 Star2
0x193708293778 @ 14 : 2d f8 01 04 LdaNamedProperty r2, [1], [4]
0x19370829377c @ 18 : c3 Star1
0x19370829377d @ 19 : 13 02 LdaConstant [2]
0x19370829377f @ 21 : c1 Star3
0x193708293780 @ 22 : 5e f9 f8 f7 06 CallProperty1 r1, r2, r3, [6]
0x193708293785 @ 27 : 0e LdaUndefined
0x193708293786 @ 28 : a9 Return
Constant pool (size = 3)
0x193708293735: [FixedArray] in OldSpace
- map: 0x193708002205 <Map>
- length: 3
0: 0x193708202741 <String[7]: #console>
1: 0x193708202769 <String[5]: #error>
2: 0x19370800455d <String[5]: #Error>
Copy the code
Generator
Using iterators in this way is the most efficient part of transcoding in the next section. However, explicit use of iterators for generators is a different story.
Let’s look at the simplest Generator where we generate just a few numbers:
let f1 = () = > {
let obj1 = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3; }}; [...obj1]; };Copy the code
We can see that the transcoding result not only defines several functions, but also requires the support of the regeneratorRuntime runtime:
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if(! o)return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || / ^ (? :Ui|I)nt(? : (8 | | 16 and 32)? :Clamped)? Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol! = ="undefined" && iter[Symbol.iterator] ! =null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
var f1 = function f1() {
var obj1 = {
[Symbol.iterator]() {
return /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 1;
case 2:
_context.next = 4;
return 2;
case 4:
_context.next = 6;
return 3;
case 6:
case "end":
return _context.stop();
}
}
}, _callee);
})();
}
};
_toConsumableArray(obj1);
};
Copy the code
If you run the above code with Node, you will get an error:
var obj1 = _defineProperty({}, Symbol.iterator, /*#__PURE__*/regeneratorRuntime.mark(function _callee() {^ReferenceError: regeneratorRuntime is not defined
Copy the code
Since the runtime is not imported, we need to add a library.
npm install --save @babel/polyfill
Copy the code
Then import the library:
require("@babel/polyfill"); .var f1 = function f1() {
var obj1 = {
[Symbol.iterator]() {
return /*#__PURE__*/regeneratorRuntime.mark(function _callee() {...Copy the code
I’ll leave it to the reader to figure out how much code this adds.
class
Although class is essentially a syntactically equivalent to Function, Babel transcodes generate more code than most of you might think. Let’s look at a simple example:
class Code {
constructor(source) {
this.source = source;
}
}
code1 = new Code("test1.js");
Copy the code
After transcoding, Babel generates _createClass,_classCallCheck, and _defineProperties for us:
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); }}function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _classCallCheck(instance, Constructor) { if(! (instanceinstanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}var Code = /*#__PURE__*/_createClass(function Code(source) {
_classCallCheck(this, Code);
this.source = source;
});
code1 = new Code("test1.js");
Copy the code
Built-in objects that rely on Polyfill and Runtime
As we know, ES6 has a number of new objects and new properties for existing objects.
For example, if you use a newly added Set, Map, WeakSet, WeakMap object, or a new method like Number. IsNaN, Babel does not help us transcode into ES5 statements. So how are these statements supported in older browsers? The answer, as you might have guessed, is the @babel/ Polyfill library we used for Generator.
The Babel Polyfill library is actually based on two open source libraries:
- One is Facebook’s ReGenerator library, which currently runs as a 700-plus line file: ReGenerator Runtime.
- The other is the core-JS library, which provides most of the built-in object support.
Our code is written like this:
Array.from(new Set([1.2.3.2.1]));
[1[2.3], [4[5]]].flat(2);
Promise.resolve(32).then(x= > console.log(x));
Copy the code
Babel/ Runtime actually does something like this:
import from from 'core-js-pure/stable/array/from';
import flat from 'core-js-pure/stable/array/flat';
import Set from 'core-js-pure/stable/set';
import Promise from 'core-js-pure/stable/promise';
from(new Set([1.2.3.2.1]));
flat([1[2.3], [4[5]]].2);
Promise.resolve(32).then(x= > console.log(x));
Copy the code
Transcoding is better
There are special cases for everything, and for some functions, transcoding may be better.
Deconstruction assignment
We quote the example of teacher Ruan Yifeng’s variable exchange:
let f1 = () = > {
let x = 1;
let y = 2;
[x, y] = [y, x];
};
f1();
Copy the code
After transcoding, it looks like this:
var f1 = function f1() {
var x = 1;
var y = 2;
var _ref = [y, x];
x = _ref[0];
y = _ref[1];
};
f1();
Copy the code
Let’s first look at the bytecode after transcoding, which has 44 bytes:
0xf1208293682 @ 0 : 0d 01 LdaSmi [1]
0xf1208293684 @ 2 : c4 Star0
0xf1208293685 @ 3 : 0d 02 LdaSmi [2]
0xf1208293687 @ 5 : c3 Star1
0xf1208293688 @ 6 : 79 00 00 25 CreateArrayLiteral [0], [0], #37
0xf120829368c @ 10 : c0 Star4
0xf120829368d @ 11 : 0c LdaZero
0xf120829368e @ 12 : c1 Star3
0xf120829368f @ 13 : 0b f9 Ldar r1
0xf1208293691 @ 15 : 36 f6 f7 01 StaInArrayLiteral r4, r3, [1]
0xf1208293695 @ 19 : 0d 01 LdaSmi [1]
0xf1208293697 @ 21 : c1 Star3
0xf1208293698 @ 22 : 0b fa Ldar r0
0xf120829369a @ 24 : 36 f6 f7 01 StaInArrayLiteral r4, r3, [1]
0xf120829369e @ 28 : 19 f6 f8 Mov r4, r2
0xf12082936a1 @ 31 : 0c LdaZero
0xf12082936a2 @ 32 : 2f f8 03 LdaKeyedProperty r2, [3]
0xf12082936a5 @ 35 : c4 Star0
0xf12082936a6 @ 36 : 0d 01 LdaSmi [1]
0xf12082936a8 @ 38 : 2f f8 05 LdaKeyedProperty r2, [5]
0xf12082936ab @ 41 : c3 Star1
0xf12082936ac @ 42 : 0e LdaUndefined
0xf12082936ad @ 43 : a9 Return
Copy the code
How many bytes does the more concise deconstruction above require? The answer is 189 bytes because iterators are involved:
0xf120829376e @ 0 : 0d 01 LdaSmi [1]
0xf1208293770 @ 2 : c4 Star0
0xf1208293771 @ 3 : 0d 02 LdaSmi [2]
0xf1208293773 @ 5 : c3 Star1
0xf1208293774 @ 6 : 79 00 00 25 CreateArrayLiteral [0], [0], #37
0xf1208293778 @ 10 : c1 Star3
0xf1208293779 @ 11 : 0c LdaZero
0xf120829377a @ 12 : c2 Star2
0xf120829377b @ 13 : 0b f9 Ldar r1
0xf120829377d @ 15 : 36 f7 f8 01 StaInArrayLiteral r3, r2, [1]
0xf1208293781 @ 19 : 0d 01 LdaSmi [1]
0xf1208293783 @ 21 : c2 Star2
0xf1208293784 @ 22 : 0b fa Ldar r0
0xf1208293786 @ 24 : 36 f7 f8 01 StaInArrayLiteral r3, r2, [1]
0xf120829378a @ 28 : b1 f7 03 05 GetIterator r3, [3], [5]
0xf120829378e @ 32 : 19 f7 f8 Mov r3, r2
0xf1208293791 @ 35 : 9f 07 JumpIfJSReceiver [7] (0xf1208293798 @ 42)
0xf1208293793 @ 37 : 65 bf 00 fa 00 CallRuntime [ThrowSymbolIteratorInvalid], r0-r0
0xf1208293798 @ 42 : c0 Star4
0xf1208293799 @ 43 : 2d f6 01 07 LdaNamedProperty r4, [1], [7]
0xf120829379d @ 47 : c1 Star3
0xf120829379e @ 48 : 12 LdaFalse
0xf120829379f @ 49 : bf Star5
0xf12082937a0 @ 50 : 19 ff f2 Mov <context>, r8
0xf12082937a3 @ 53 : 0b f5 Ldar r5
0xf12082937a5 @ 55 : 96 21 JumpIfToBooleanTrue [33] (0xf12082937c6 @ 88)
0xf12082937a7 @ 57 : 11 LdaTrue
0xf12082937a8 @ 58 : bf Star5
0xf12082937a9 @ 59 : 5d f7 f6 0d CallProperty0 r3, r4, [13]
0xf12082937ad @ 63 : bb Star9
0xf12082937ae @ 64 : 9f 07 JumpIfJSReceiver [7] (0xf12082937b5 @ 71)
0xf12082937b0 @ 66 : 65 b7 00 f1 01 CallRuntime [ThrowIteratorResultNotAnObject], r9-r9
0xf12082937b5 @ 71 : 2d f1 02 0b LdaNamedProperty r9, [2], [11]
0xf12082937b9 @ 75 : 96 0d JumpIfToBooleanTrue [13] (0xf12082937c6 @ 88)
0xf12082937bb @ 77 : 2d f1 03 09 LdaNamedProperty r9, [3], [9]
0xf12082937bf @ 81 : bb Star9
0xf12082937c0 @ 82 : 12 LdaFalse
0xf12082937c1 @ 83 : bf Star5
0xf12082937c2 @ 84 : 0b f1 Ldar r9
0xf12082937c4 @ 86 : 8a 03 Jump [3] (0xf12082937c7 @ 89)
0xf12082937c6 @ 88 : 0e LdaUndefined
0xf12082937c7 @ 89 : c4 Star0
0xf12082937c8 @ 90 : 0b f5 Ldar r5
0xf12082937ca @ 92 : 96 21 JumpIfToBooleanTrue [33] (0xf12082937eb @ 125)
0xf12082937cc @ 94 : 11 LdaTrue
0xf12082937cd @ 95 : bf Star5
0xf12082937ce @ 96 : 5d f7 f6 0f CallProperty0 r3, r4, [15]
0xf12082937d2 @ 100 : bb Star9
0xf12082937d3 @ 101 : 9f 07 JumpIfJSReceiver [7] (0xf12082937da @ 108)
0xf12082937d5 @ 103 : 65 b7 00 f1 01 CallRuntime [ThrowIteratorResultNotAnObject], r9-r9
0xf12082937da @ 108 : 2d f1 02 0b LdaNamedProperty r9, [2], [11]
0xf12082937de @ 112 : 96 0d JumpIfToBooleanTrue [13] (0xf12082937eb @ 125)
0xf12082937e0 @ 114 : 2d f1 03 09 LdaNamedProperty r9, [3], [9]
0xf12082937e4 @ 118 : bb Star9
0xf12082937e5 @ 119 : 12 LdaFalse
0xf12082937e6 @ 120 : bf Star5
0xf12082937e7 @ 121 : 0b f1 Ldar r9
0xf12082937e9 @ 123 : 8a 03 Jump [3] (0xf12082937ec @ 126)
0xf12082937eb @ 125 : 0e LdaUndefined
0xf12082937ec @ 126 : c3 Star1
0xf12082937ed @ 127 : 0d ff LdaSmi [-1]
0xf12082937ef @ 129 : bd Star7
0xf12082937f0 @ 130 : be Star6
0xf12082937f1 @ 131 : 8a 05 Jump [5] (0xf12082937f6 @ 136)
0xf12082937f3 @ 133 : bd Star7
0xf12082937f4 @ 134 : 0c LdaZero
0xf12082937f5 @ 135 : be Star6
0xf12082937f6 @ 136 : 10 LdaTheHole
0xf12082937f7 @ 137 : a6 SetPendingMessage
0xf12082937f8 @ 138 : bc Star8
0xf12082937f9 @ 139 : 0b f5 Ldar r5
0xf12082937fb @ 141 : 96 23 JumpIfToBooleanTrue [35] (0xf120829381e @ 176)
0xf12082937fd @ 143 : 19 ff f0 Mov <context>, r10
0xf1208293800 @ 146 : 2d f6 04 11 LdaNamedProperty r4, [4], [17]
0xf1208293804 @ 150 : 9e 1a JumpIfUndefinedOrNull [26] (0xf120829381e @ 176)
0xf1208293806 @ 152 : b9 Star11
0xf1208293807 @ 153 : 5d ef f6 13 CallProperty0 r11, r4, [19]
0xf120829380b @ 157 : 9f 13 JumpIfJSReceiver [19] (0xf120829381e @ 176)
0xf120829380d @ 159 : b8 Star12
0xf120829380e @ 160 : 65 b7 00 ee 01 CallRuntime [ThrowIteratorResultNotAnObject], r12-r12
0xf1208293813 @ 165 : 8a 0b Jump [11] (0xf120829381e @ 176)
0xf1208293815 @ 167 : ba Star10
0xf1208293816 @ 168 : 0c LdaZero
0xf1208293817 @ 169 : 1c f4 TestReferenceEqual r6
0xf1208293819 @ 171 : 98 05 JumpIfTrue [5] (0xf120829381e @ 176)
0xf120829381b @ 173 : 0b f0 Ldar r10
0xf120829381d @ 175 : a8 ReThrow
0xf120829381e @ 176 : 0b f2 Ldar r8
0xf1208293820 @ 178 : a6 SetPendingMessage
0xf1208293821 @ 179 : 0c LdaZero
0xf1208293822 @ 180 : 1c f4 TestReferenceEqual r6
0xf1208293824 @ 182 : 99 05 JumpIfFalse [5] (0xf1208293829 @ 187)
0xf1208293826 @ 184 : 0b f3 Ldar r7
0xf1208293828 @ 186 : a8 ReThrow
0xf1208293829 @ 187 : 0e LdaUndefined
0xf120829382a @ 188 : a9 Return
Constant pool (size = 5)
0xf1208293731: [FixedArray] in OldSpace
- map: 0x0f1208002205 <Map>
- length: 5
0: 0x0f12082936fd <ArrayBoilerplateDescription PACKED_SMI_ELEMENTS, 0x0f12082936ed <FixedArray[2]>>
1: 0x0f1208004e65 <String[4]: #next>
2: 0x0f1208004441 <String[4]: #done>
3: 0x0f1208005619 <String[5]: #value>
4: 0x0f12080051dd <String[6]: #return>
Copy the code
Compatibility will have to wait
Nullish operator.
The Nullish operator is “??” Operator, “??” if null and undefined. The following values:
function greet(input) {
return input ?? "Hello world";
}
Copy the code
The bytecode translation is 9 bytes:
0x94c082935da @ 0 : 0b 03 Ldar a0
0x94c082935dc @ 2 : 9e 04 JumpIfUndefinedOrNull [4] (0x94c082935e0 @ 6)
0x94c082935de @ 4 : 8a 04 Jump [4] (0x94c082935e2 @ 8)
0x94c082935e0 @ 6 : 13 00 LdaConstant [0]
0x94c082935e2 @ 8 : a9 Return
Copy the code
Transcoding results:
function greet(input) {
returninput ! = =null&& input ! = =void 0 ? input : "Hello world";
}
Copy the code
15 bytes after translation to bytecode:
0x2a8a082935da @ 0 : 0b 03 Ldar a0
0x2a8a082935dc @ 2 : 9a 0a JumpIfNull [10] (0x2a8a082935e6 @ 12)
0x2a8a082935de @ 4 : 0b 03 Ldar a0
0x2a8a082935e0 @ 6 : 9c 06 JumpIfUndefined [6] (0x2a8a082935e6 @ 12)
0x2a8a082935e2 @ 8 : 0b 03 Ldar a0
0x2a8a082935e4 @ 10 : 8a 04 Jump [4] (0x2a8a082935e8 @ 14)
0x2a8a082935e6 @ 12 : 13 00 LdaConstant [0]
0x2a8a082935e8 @ 14 : a9 Return
Copy the code
“??” The operator was translated by V8 into the JumpIfUndefinedOrNull directive, which is no longer the case, becoming JumpIfNull and JumpIfUndefined.
So the Nullish operator is worth not transcoding, as long as the browser supports it.
Power operator
Like Nullish, the power operator is supported by instructions. This saves the overhead of function calls.
let f1 = x= > {
return x ** x;
};
f1(10);
Copy the code
Because of the Exp instruction, it takes 6 bytes:
0xb75082936be @ 0 : 0b 03 Ldar a0
0xb75082936c0 @ 2 : 3e 03 00 Exp a0, [0]
0xb75082936c3 @ 5 : a9 Return
Copy the code
After transcoding, it becomes:
var f1 = function f1(x) {
return Math.pow(x, x);
};
f1(10);
Copy the code
Since there are function calls, 16 bytes of instruction are required:
0xb7508293652 @ 0 : 21 00 00 LdaGlobal [0], [0]
0xb7508293655 @ 3 : c3 Star1
0xb7508293656 @ 4 : 2d f9 01 02 LdaNamedProperty r1, [1], [2]
0xb750829365a @ 8 : c4 Star0
0xb750829365b @ 9 : 5f fa f9 03 03 04 CallProperty2 r0, r1, a0, a0, [4]
0xb7508293661 @ 15 : a9 Return
Constant pool (size = 2)
0xb7508293621: [FixedArray] in OldSpace
- map: 0x0b7508002205 <Map>
- length: 2
0: 0x0b75082028e5 <String[4]: #Math>
1: 0x0b7508202aa1 <String[3]: #pow>
Copy the code
JSX
There’s one situation where you can’t use native code and have to transcode, and that’s how React JSX works.
Although transcoding is required, the amount of code generated by different targets can vary significantly.
For example, this code is typical of React Hooks:
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>
Click me
</button>
</div>
);
}
Copy the code
Let’s transcode by iOS 9:
const babel = require("@babel/core");
const generate = require("@babel/generator");
let result3 = babel.transformSync(code, {
targets: "iOS 9".sourceMaps: true.presets: ["@babel/preset-env"."@babel/preset-react"]});let str1 = result3.code;
console.log(str1);
Copy the code
The transcoding result is as follows:
.function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if(! o)return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || / ^ (? :Ui|I)nt(? : (8 | | 16 and 32)? :Clamped)? Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol! = ="undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for(_i = _i.call(arr); ! (_n = (_s = _i.next()).done); _n =true) { _arr.push(_s.value); if (i && _arr.length === i) break; }}catch (err) { _d = true; _e = err; } finally { try { if(! _n && _i["return"] != null) _i["return"] (); }finally { if (_d) throw_e; }}return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function Example() {
var _useState = (0, _react.useState)(0),
_useState2 = _slicedToArray(_useState, 2),
count = _useState2[0],
setCount = _useState2[1];
return /*#__PURE__*/_react.default.createElement("div".null./*#__PURE__*/_react.default.createElement("p".null."You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
onClick: function onClick() {
return setCount(count + 1); }},"Click me"));
}
Copy the code
For iOS 15 transcoding, there is no need to generate as much JS code since destructuring is supported:
function Example() {
const [count, setCount] = (0, _react.useState)(0);
return /*#__PURE__*/_react.default.createElement("div".null./*#__PURE__*/_react.default.createElement("p".null."You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
onClick: () = > setCount(count + 1)},"Click me"));
}
Copy the code
summary
As we can see from the previous examples, aside from the complexity of structures such as deconstruction introducing iteration, in most cases v8 is better off without transcoding, both from source code and bytecode perspectives. In particular, it is not simply transcoding and relies on the capabilities of the Polyfill runtime, which is not cost-effective in terms of both the size and speed of the code base. At least for mid-range and high-end models in recent years, it’s worth using new weapons to get better results.