关于javascript:图解-Google-V8设计思想篇学习笔记一

6次阅读

共计 3469 个字符,预计需要花费 9 分钟才能阅读完成。

最新在学《图解 Google V8》

这个专栏的长处是:写的通俗易懂,没有波及 V8 源码局部,对于前端还是比拟敌对的,学完之后可能晓得写下一段 js 代码后,V8 背地都做了哪些事件

这个专栏的不足之处是:没有对技术进行深刻解说,只讲了这个技术是用来解决什么问题的,以及它是怎么工作的

所以这个专栏比拟时候对 V8 还不理解的同学去学习,减少本人的知识面

上面是我本人学习每一章的总结,次要记录我在这章中学到内容,并不是对这章残缺的总结

我会分为三篇来写,这是第一篇:设计思维篇

如何学习谷歌高性能 JavaScript 引擎 V8?

V8 次要波及三个技术:编译流水线、事件循环系统、垃圾回收机制

  1. V8 执行 JavaScript 残缺流程称为:编译流水线

    编译流水线波及到的技术有:

    • JIT

      • V8 混合编译执行和解释执行
    • 惰性解析

      • 放慢代码启动速度
    • 暗藏类(Hide Class)

      • 将动静类型转为动态类型,打消动静类型执行速度慢的问题
    • 内联缓存
  2. 事件循环系统

    • JavaScript 中的难点:异步编程
    • 调度排队工作,让 JavaScript 有序的执行
  3. 垃圾回收机制

    • 内存调配
    • 内存回收

01 | V8 是如何执行一段 JavaScript 代码的?

  1. 筹备根底环境:

    • 全局执行上下文:全局作用、全局变量、内置函数
    • 初始化内存中的堆和栈构造
    • 初始化音讯循环系统:音讯驱动器和音讯队列
  2. 结构化 JavaScript 源代码

    • 生成形象语法树(AST
    • 生成相干作用域
  3. 生成字节码:字节码是介于 AST 和机器码的中间代码

    • 解释器能够间接执行
    • 编译器须要将其编译为二进制的机器码再执行
  4. 解释器和监控机器人

    • 解释器:依照程序执行字节码,并输入执行后果
    • 监控机器人:如果发现某段代码被反复屡次执行,将其标记为热点代码
  5. 优化热点代码

    • 优化编译器将热点代码编译为机器码
    • 对编译后的机器码进行优化
    • 再次执行到这段代码时,将优先执行优化后的代码
  6. 反优化

    • JavaScript 对象在运行时可能会被扭转,这段优化后的热点代码就生效了
    • 进行反优化操作,给到解释器解释执行

02 | 函数即对象:一篇文章彻底搞懂 JavaScript 的函数特点

V8 外部为函数对象增加了两个暗藏属性:namecode

  • name 为函数名

    • 如果是匿名函数,nameanonymous
  • code 为函数代码,以字符串的模式存储在内存中

当执行到一个函数调用语句时,V8 从函数对象中取出 code 属性值,而后解释执行这段函数代码

什么是闭包:将内部变量和函数绑定起来的技术

参考资料:

  1. The story of a V8 performance cliff in React

03 | 快属性和慢属性:V8 是怎么晋升对象属性访问速度的?

V8 在实现对象存储时,没有齐全采纳字典的存储形式,因为字典是非线性的数据结构,查问效率会低于线性的数据结构

惯例属性和索引属性

  • 索引属性(elements):数字属性依照索引值的大小升序排列
  • 惯例属性(properties):字符串依据创立时的程序升序排列

它们都是线性数据结构,别离为 elements 对象和 properties 对象

执行一次查问:先从 elements 对象中依照程序读出所有元素,而后再从 properties 对象中读取所有的元素

快属性和慢属性

在拜访一个属性时,比方:foo.aV8 先查找出 properties,而后在从 properties 中查找出 a 属性

V8 为了简化这一步操作,把局部 properties 存储到对象自身,默认是 10 个,这个被称为 对象内属性

线性数据结构通常被称为快属性

线性数据结构进行大量数据的增加和删除,执行效率是非常低的,所以 V8 会采纳慢属性策略

慢属性的对象外部有独立的非线性数据结构(字典)

参考资料:

  1. V8 是怎么跑起来的 —— V8 中的对象示意
  2. Fast properties in V8

04 | 函数表达式:波及大量概念,函数表达式到底该怎么学?

变量晋升

js 中有函数申明的形式有两种:

  • 函数申明

    function foo() {console.log("foo");
    }
  • 函数表达式

    var foo = function () {console.log("foo");
    };

在编译阶段 V8 解析到函数申明和函数表达式(变量申明)时:

  • 函数申明,将其转换为内存中的 函数对象,并放到作用域中
  • 变量申明,将其值设置为 undefined,并当道作用域中

因为在编译阶段,是不会执行表达式的,只会剖析变量的定义、函数的申明

所以如果在申明前调用 foo 函数:

  • 应用函数申明不会报错
  • 应用函数表达式会报错

在编译阶段将所有的变量晋升到作用域的过程称为 变量晋升

立刻执行函数

js 的圆括号 () 能够在两头放一个表达式

两头如果是一个函数申明,V8 就会把 (function(){}) 看成是函数表达式,执行它会返回一个 函数对象

如果在函数表达式前面加上 (),就被称为 立刻调用函数表达式

因为函数立刻表达式也是表达式,所以不会创立函数对象,就不会污染环境

05 |原型链:V8 是如何实现对象继承的?

  • 作用域链是沿着函数的作用域一级一级来查找变量的
  • 原型链是沿着对象的原型一级一级来查找属性的

js 中实现继承,是将 __proto__ 指向对象,然而不举荐应用,次要起因是:

  • 这是暗藏属性,不是规范定义的
  • 应用该属性会造成重大的性能问题

继承

  1. 用构造函数实现继承:

    function DogFactory(color) {this.color = color;}
    DogFactory.prototype.type = "dog";
    const dog = new DogFactory("Black");
    dog.hasOwnProperty("type"); // false
  2. ES6 之后能够通过 Object.create 实现继承

    const animalType = {type: "dog"};
    const dog = Object.create(animalType);
    dog.hasOwnProperty("type"); // false

new 背地做了这些事件

  1. 帮你在外部创立一个长期对象
  2. 将长期对象的 __proto__ 设置为构造函数的原型,构造函数的原型对立叫做 prototype
  3. return 长期对象
function NEW(fn) {return function () {var o = { __proto__: fn.prototype};
    fn.apply(o, arguments);
    return o;
  };
}

__proto__prototypeconstructor 区别

prototype 是函数的独有的;__proto__constructor 是对象独有的

因为函数也是对象,所以函数也有 __proto__constructor

constructor 是函数;prototype__proto__ 是对象

typeof Object.__proto__; // "object"
typeof Object.prototype; // "object"
typeof Object.constructor; // "function"
let obj = new Object();
obj.__proto__ === Object.prototype;
obj.constructor === Object;

objObject 的实例,所以 obj.constructor === Object

obj 的是对象,Object 是函数,所以 obj.__proto__ === Object.prototype

参考资料:

  1. 用本人的形式(图)了解 constructorprototype__proto__ 和原型链
  2. 面试官问:JS 的继承

06 |作用域链:V8 是如何查找变量的?

全局作用域是在 V8 启动过程中就创立了,且始终保留在内存中不会被销毁的,直至 V8 退出

而函数作用域是在执行该函数时创立的,当函数执行完结之后,函数作用域就随之被销毁掉了

因为 JavaScript 是基于词法作用域的,词法作用域就是指,查找作用域的程序是依照函数定义时的地位来决定的。

词法作用域是动态作用域,依据函数在代码中的地位来确定的,作用域是在申明函数时就确定好了

动静作用域链是基于调用栈的,不是基于函数定义的地位的,能够认为 this 是用来补救 JavaScript 没有动静作用域个性的

07 |类型转换:V8 是怎么实现 1+“2”的?

V8 会提供了一个 ToPrimitive 办法,其作用是将 ab 转换为原生数据类型

  1. 先检测该对象中是否存在 valueOf 办法,如果有并返回了原始类型,那么就应用该值进行强制类型转换
  2. 如果 valueOf 没有返回原始类型,那么就应用 toString 办法的返回值
  3. 如果 valueOftoString 两个办法都不返回根本类型值,便会触发一个 TypeError 的谬误。
正文完
 0