By Xulun from F(x) Team
In order to be compatible with old browsers, especially the IE series, frontend code that uses the specifications of ES6 or later is often transcoded into ES5 code using transcoding tools such as Babel.
It has been seven years since the ES6 was released in 2015. What is the compatibility of browsers with ES6 now?
Let's look at the CanIUse
data:
98.14% of browsers support ES6. The data does not exceed 99% because there is still 1.08% of Opera Mini (released in 2015) to be used. Safari on iOS and Chrome released for mobile phones after 2016 all support ES6. The current proportion of users on Safari for iOS 7.0-9.3 is 0.15%. Android WebView has fully supported ES6 since Android 5.0.
It seems that nearly 99% capabilities of devices have not been applied because of the small number of old devices. However, it does not seem to be convincing. In addition, many applications have special processing for low-end machines, and mid-to-high-end machines must be recent old devices. At least for mid-to-high-end machines, the necessity of transcoding compatibility is negligible.
However, ES6 and its later versions are composed of multiple features. Therefore, it cannot be simply understood as ES6 being better than ES5. We will compare transcoded and non-transcoded main features.
The const is to be checked with constants. For example:
let f1 = () => {
const a = 0;
a = 2;
};
f1();
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();
Instead of looking at the bytecode, we can know which is better by looking at the source code.
After ES6, we use the extension operator "..." to perform array copy.
const a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let a2 = [...a1];
Babel is competent for transcoding simply with a concat
function:
var a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var a2 = [].concat(a1);
However, it is different from the point of view of the bytecode because v8 provides the CreateArrayFromIterable
instruction.
Only a 9-byte instruction is required to have it done before transcoding:
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]>>
After transcoding, the function is called, and an empty array is generated, which requires a total of 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>
Transcoding also generates multiple functions for string.raw. For example, this is before transcoding:
let f1 = () => {
String.raw`\n`;
};
f1();
After transcoding, Babel generates a _taggedTemplateLiteral
function for us:
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();
Symbol is a new data type added by ES6. In ES6, we need to judge its type and can use the operator named typeof
.
let f2 = () => {
let s1 = Symbol();
return typeof s1;
};
This is difficult for Babel, and it is necessary to introduce a library to solve it:
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();
return _typeof(s1);
};
v8 offers the CreateRestParameter
command to support rest parameters. However, the original arguments also support the CreateMappedArguments
command.
However, the length will be shorter from the point of view of source code if transcoding is not performed:
let f1 = (...values) => {
let sum = 0;
for (let v of values) {
sum += v;
}
return sum;
};
f1(1, 4, 9);
This is after transcoding:
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;
};
This is a feature of ES2019, and it can omit the error type in the catch. It was supported by Safari in the first half of 2018.
let f3 = f2 => {
try {
f2();
} catch {
console.error("Error");
}
};
After transcoding, Babel generates an unused error variable, •_unused•
, for us:
var f1 = function f1(f2) {
try {
f2();
} catch (_unused) {
console.error("Error");
}
};
With the error variable, v8 generates a CatchContext for us through the 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>
No CatchContext is directly generated because of optional catch parameters.
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>
Parsing and assigning values that use iterators are the highly efficient part of transcoding in the next section. However, it is another case for the generator explicitly using iterators.
Let's look at the simplest generator. We can only generate a few numbers:
let f1 = () => {
let obj1 = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
[...obj1];
};
As we can see, transcoding results define several functions and require the support of regeneratorRuntime:
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|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);
};
If you run the code above with Node, an error will be reported:
var obj1 = _defineProperty({}, Symbol.iterator, /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
^
ReferenceError: regeneratorRuntime is not defined
Since a run library has not been introduced, we have to add a library and install it first:
npm install --save @babel/polyfill
Then, bring in the library:
require("@babel/polyfill");
...
var f1 = function f1() {
var obj1 = {
[Symbol.iterator]() {
return /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
...
The amount of code added needs you to calculate.
The class is similar to syntax sugar of the function in nature, but Babel transcoding may generate more code than most colleagues think. Let's look at a simple example:
class Code {
constructor(source) {
this.source = source;
}
}
code1 = new Code("test1.js");
This is the result after transcoding. Babel generates three functions: the_createClass,_classCallCheck, and _defineProperties:
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 (!(instance instanceof 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");
We know ES6 has added many new objects and new attributes of original objects.
For example, if you use newly added objects (such as Set, Map, WeakSet, and WeakMap) or new methods (such as •Number.isNaN•
), Babel does not transcode those into ES5 statements. How are these statements supported in old browsers? The answer (as you may have guessed) is the •@ babel/polyfill•
library we used in the generator previously.
The Babel polyfill library is based on two open-source libraries:
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));
Babel/runtime executes 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));
There are special cases in everything. In terms of some features, transcoding may lead to better results.
We quote the example of variable exchange by Ruan Yifeng:
let f1 = () => {
let x = 1;
let y = 2;
[x, y] = [y, x];
};
f1();
After transcoding, it becomes like this:
var f1 = function f1() {
var x = 1;
var y = 2;
var _ref = [y, x];
x = _ref[0];
y = _ref[1];
};
f1();
First, let's 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
How many bytes are required for a more concise deconstruction above? The answer is 189 bytes because it involves iterators:
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>
The Nullish operator is the "??" operator. If the results are null and undefined, the value after "??" can be taken:
function greet(input) {
return input ?? "Hello world";
}
It is 9 bytes after being translated into the bytecode:
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
Results after transcoding:
function greet(input) {
return input !== null && input !== void 0 ? input : "Hello world";
}
It is 15 bytes after being translated into the 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
The "??" operator is translated into a JumpIfUndefinedOrNull instruction by v8. After transcoding, there is no such treatment. It becomes two instructions, JumpIfNull and JumpIfUndefined.
Therefore, as long as the browser supports it, the Nullish operator is still worth not transcoding.
Like Nullish, the involution operator is supported by instructions. This saves the overhead of function calls.
let f1 = x => {
return x ** x;
};
f1(10);
Six bytes are enough because of the Exp instruction:
0xb75082936be @ 0 : 0b 03 Ldar a0
0xb75082936c0 @ 2 : 3e 03 00 Exp a0, [0]
0xb75082936c3 @ 5 : a9 Return
After transcoding, it becomes:
var f1 = function f1(x) {
return Math.pow(x, x);
};
f1(10);
A 16-byte instruction is required because there is a function call:
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>
In one case, it is impossible to use native code and necessary to be transcoded. This is the situation of React JSX.
Transcoding is required, but the amount of code brought by different goals is also significantly different.
For example, take a look at the following code, which is a typical React Hooks usage:
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>
);
}
We transcode according to 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);
The transcoding result is listed below:
...
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|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"));
}
However, transcoding aimed at iOS 15 does not need to generate much js code because deconstruction 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"));
}
In the preceding cases (except for deconstruction that introduces iteration), it will become complex. In most cases, it is more conducive to v8 to improve performance without transcoding from the two aspects of source code and bytecode. In particular, simple transcoding is not enough. It depends on the feature of polyfill runtime, which is not cost-effective in terms of code library size and running speed. It is worth using new tools to achieve better results, specifically the mid-to-high-end machines in recent years.
66 posts | 3 followers
FollowHaydenLiu - December 5, 2022
Alibaba Clouder - November 1, 2018
Alibaba F(x) Team - June 20, 2022
Louis Liu - August 27, 2019
Alibaba Cloud Vietnam - June 7, 2024
Alibaba Clouder - July 8, 2020
66 posts | 3 followers
FollowExplore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreA low-code development platform to make work easier
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreMore Posts by Alibaba F(x) Team