0: 'a', 1: 'b', 2: 'c' };
Array.prototype.forEach.call(arrayLike, (v, i) => {
console.log(i, v);
});
原理:在类数组对象上调用数组内置的 Array.prototype 的方法。
这比在真正的数组中调用 forEach 慢,引擎数组的 forEach 在 V8 中是高度优化的。
真要这么做,应该先把 arrayLike 转化成一个真正的数组,然后对转化后的结果调用数组操作。
三.对象
3-1.初始化
JS 不像强类型语言在编译前对象结构就固定了,每个属性之间具有固定偏移量,因而可根据属性类型确定偏移量。
因为字典在内存中查找对象属性的位置效率很低,所以 V8 搞了隐藏类去记录偏移量,来模拟 java 编译的固定对象结构,优化属性的访问速度。
所以,对于对象的使用,应该避免隐藏类的改变。
建议:
使用字面量初始化多个对象时,保证命名属性的顺序一致。
使用字面量一次性初始化完整对象属性。
避免使用 delete 。同时若操作不当时,会退化成慢属性。
属性分为命名属性和元素属性。
// bad case
var obj1 = { age: 18 };
obj1.name = 'CS阿吉';
delete obj1.age;
// good case
var obj2 = { name: 'CS阿吉', age: 18 };
动态增删会导致JS在 V8 对应的隐藏类被改变,性能损耗在隐藏类的新建。
添加元素属性不会创建新的隐藏类
每个 JavaScript 对象都有一个关联的隐藏类,用于保存有关对象形状的信息,是 V8 优化编译器和内联缓存的非常重要的组成部分。
经过优化编译器的代码只能真对固定结构,一旦代码执行中,对象结构动态变化,则机器码失效
隐藏类描述了对象的命名属性布局,其作用是优化命名属性的访问速度。简单来说,隐藏类不包含元素属性信息。
3-2.属性数量
尽量控制一个对象的命名属性个数 <= 10个。
因为命名属性的个数不同,会采用不同的存储方式。
访问速度由快到慢依此是:对象内属性,快属性,慢属性。
因为对象内属性会直接存储到对象本身,快属性使用的是连续存储,慢属性采用的是字典存储。
3-3.扩展运算符
const a = {name: 'hh'}
const rest1 = {
...a,
age: 10,
const rest2 = {
age: 10,
...a,
rest1 更高效,V8团队直接给出的书写建议。
四.函数
4-1.解析方式
V8 采用的是混合编译执行+解释执行,即JIT。
V8 有两种解析方式:全解析(eager parse)、预解析(lazy parse)。
全解析是针对变量。预解析针对非IIFE的函数定义,只解析函数声明,不会解析函数内部代码,不会为函数内部代码生成 AST。
// 预解析:只解析了函数声明,函数体整体作为字符串存储到堆中。等 add1 执行的时候,才取出字符串解析AST,再编译为字节码执行。
function add1(a, b) {
return a + b;
// 全解析:编译阶段保存解析+编译得到字节码
const add2 = (function (a, b) {
return a + b;
})();
// add2 执行速度会更快。