JavaScript高级-深入JavaScript的运行原理

456 阅读6分钟

一、浏览器渲染流程

1. 网页的解析过程

31_浏览器输入URL到页面展示的过程.png

  • 浏览器输入域名
  • DNS解析(域名解析)获取IP地址
  • 根据IP地址找到对应的服务器
  • 服务器返回对应的资源

2. 浏览器渲染页面的详细流程

31_浏览器渲染页面的流程.png

  • 解析一:HTML解析过程
    • 默认情况下,服务器会给浏览器返回index.html文件,所以解析HTML是所有步骤的开始
    • 解析HTML,会生成DOM Tree
  • 解析二:生成CSS规则
    • 在解析的过程中,如果遇到CSS的link元素,那么会由浏览器负责下载对应的CSS文件(下载CSS文件时不会影响DOM的解析的)
    • 浏览器下载完CSS文件后,就会对CSS文件进行解析,解析出对应的规则树(CSSOM)
  • 解析三:构建Render Tree
    • 当有了DOM Tree和CSSOM Tree后,就可以两个结合来构建Render Tree
    • 注意一:link元素不会阻塞DOM Tree的构建过程,但是会阻塞Render Tree的构建过程
    • 注意二: Render Tree和DOM Tree不是一一对应的关系
  • 解析四:布局
    • 在Render Tree上运行布局(Layout)以计算每个结点的几何体
    • 渲染树会表示显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息
    • 布局时确定呈现树中所有节点的宽度、高度和位置信息
  • 解析五:绘制
    • 在绘制阶段,浏览器将布局阶段计算的每个frame转为屏幕上实际的像素点
    • 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素

3. 回流和重绘解析

  • 回流
    • 理解回流reflow
      • 第一次确定节点的大小和位置,称之为布局(layout)
      • 之后对节点的大小、位置修改重新计算称之为回流
    • 什么情况下引起回流?
      • DOM结构发生改变(添加、移除节点)
      • 改变了布局(修改了width、height等值)
      • 窗口resize
      • 调用getComputedStyle方法获取尺寸、位置信息
  • 重绘
    • 重绘repaint
      • 第一次渲染内容称之为绘制(paint)
      • 之后重新渲染称之为重绘
    • 什么情况下会引起重绘
      • 比如修改背景色、文字颜色、边框颜色、样式等
      • 回流一定会引起重绘,所以回流是一件很消耗性能的事情
    • 如何避免回流
      • 修改样式时尽量一次性修改(通过cssText修改,或者通过添加class修改)
      • 尽量避免频繁的操作DOM
      • 尽量避免通过getComputedStyle获取尺寸、位置
      • 对某些元素使用position的absolute或者fixed(并不是不会引起回流,而是开销相对较小)

二、深入V8引擎原理

1. 浏览器内核的构成

  • WebCore:负责HTML解析、布局、渲染等相关工作
  • JavaScriptCore:解析、执行JavaScript代码(V8引擎是一个强大的JS引擎)

2. V8引擎执行定义

  • V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等
  • V8可以独立运行,也可以嵌入到任何C++应用程序中

3. V8引擎的架构

31_V8引擎的执行原理.jpg

  • Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码
  • Ignition是一个解释器,会将AST转换成ByteCode(字节码)
    • 字节码某种程度上可以做到跨平台,并且实现JS的动态性
    • Ignition会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算)
    • 如果函数只调用一次,Ignition会解释执行ByteCode
  • TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
    • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能
    • 但是,机器码实际上也会被还原成ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码

4. V8引擎的解析

31_V8引擎的解析图.jpg

  • 词法分析
    • 将字符序列转换成token序列的过程叫做词法分析(将一条语句进行分词处理)
    • token是记号化的缩写
    • 词法分析器也叫扫描器(scanner)
  • 语法分析
    • 语法分析器也可以称之为parser

三、JS执行上下文

  • js引擎内部有一个执行上下文栈(Execution Context Stack),它是用于执行代码的调用栈
  • ESC执行的是全局的代码块
    • 全局的代码块为了执行会构建一个Global Execution Context(GEC)
    • GEC会被放入到ECS中
  • GEC被放入到ECS中里面包括两部分内容
    • 第一部分:在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值(这个过程也称之为变量的作用域提升)
    • 第二部分:在代码执行中,对变量赋值,或者执行其他的函数
  • 认识VO对象
    • 每一个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中
    • 当全局代码被执行的时候,VO就是GO对象了

四、全局代码执行过程

var name = "why"
function foo() {
    var name = "foo"
    console.log(name)
}
var num1 = 20
var num2 = 30
var resutl = num1 + num2
foo()
  • 执行前 31_全局代码执行过程(执行前).jpg
    • 在代码执行前,会对代码进行解析
    • 首先会创建一个全局执行上下文
    • 每个执行上下文都会关联一个VO,全局执行上下文的VO就是GO对象
    • 然后会将全局定义的变量、函数等加入到GO中,但是不会赋值
    • 函数会直接创建函数对象,但不会执行
  • 执行后 31_全局代码执行过程(执行前).jpg
    • 对变量进行赋值,赋值操作是在栈中进行操作的,只不过关联的GO中的属性值会相应的发生变化
    • 执行函数

五、函数代码执行过程

  • 执行前 31_函数执行过程(执行前).jpg
    • 在执行过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Function Execution Context,简称FEC),并且压入到ES栈中
    • 当函数执行上下文时,会创建一个AO对象
    • 这个AO对象关联函数执行上下文的VO
    • 这个AO对象会使用arguments作为初始化,并且初始值是传入的参数
    • 这个AO对象会作为执行上下文的VO来存放变量的初始值
  • 执行后 31_函数执行过程(执行后).jpg

六、作用域和作用域链

  • 作用域和作用域链
    • 当进入到一个执行上下文时,执行上下文也会关联一个作用域链
    • 作用域链是一个对象列表,用于变量标识符的求值
    • 全局的作用域链就是GO
    • 函数的作用域链在函数被创建的时候就决定了,不是执行时才决定
  • 图示
var message = "info"
function foo() {
    var name = "foo"
    function bar() {
        console.log(name)
    }
    return bar
}
var bar = foo()
bar()

31_作用域链.jpg