JavaScript执行原理

102 阅读4分钟

1.编程语言发展:

机器语言;汇编语言;高级语言;

高级语言需要转换成机器语言才能运行

2.从链接到页面:

  1. 输入链接
  2. DNS解析(域名转换成IP)
  3. 请求html文件
  4. 解析html文件(下载css、js等资源)
  5. 页面渲染

3.浏览器内核

  1. 浏览器内核又称为:排版引擎、浏览器引擎、页面渲染引擎、样板引擎
  2. 主要浏览器引擎:
  • Webkit:苹果基于KHTML开发,用于Safari
  • Blink:Google开发,目前应用于Google Chrome、Edge、Opera等

4.浏览器渲染过程

image.png

5.JavaScript引擎

将JavaScript代码翻译成CPU指令来执行

常见JavaScript引擎:

  • JavaScriptCore:苹果开发,WebKtit、小程序中使用
  • V8:Google开发,Chrome中使用

6.浏览器内核与JavaScript引擎之间的关系

以WebKit为例:

  • WebCore:负责HTML解析、布局、渲染等等相关的工作;
  • JavaScriptCore:解析、执行JavaScript代码;

V8引擎原理

V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等

JavaScript代码执行过程:

  • 解析:词法分析
  • 语法分析:生成AST(抽象语法树)astexplorer.net/
  • 解释器:将AST转换成字节码(字节码是跨平台的,可在不同的cpu上解释执行)
  • 执行字节码

image.png

Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码

  • 如果函数没有被调用,那么是不会被转换成AST的;
  • Parse的V8官方文档:v8.dev/blog/scanne…

Ignition是一个解释器,会将AST转换成ByteCode(字节码)。

TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;

  • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
  • 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
  • TurboFan的V8官方文档:v8.dev/blog/turbof…

JavaScript代码执行方式:

  1. 全局对象VO:代码执行前,在堆内存中创建全局对象:Global Object(GO),GO中的变量所有作用域均可访问。GO中包含JavaScript内置属性和全局定义的变量。
  2. 执行上下文栈ECS:创建执行上下文栈(ECS),用于存放执行上下文。执行上下文栈的最底层是全局执行上下文。
  3. 执行上下文:JavaScript代码的执行环境,其中包含变量对象(VO)、词环境、可执行代码等。
  4. 全局执行上下文GEC:在执行上下文栈中压入全局执行上下文,全局执行上下文中的VO指向GO;在代码执行前(在parser转成AST的过程中),会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值(函数的话会将函数保存的地址赋给变量),这个过程也称之为变量的作用域提升
  5. 函数执行上下文:在代码执行过程中,遇到要执行的的函数时,会在执行上下文栈中创建函数执行上下文,当函数是行结束后,会将该函数的执行上下文栈弹出。

作用域链(scope chain)

当查找一个变量时,会沿着作用域链查找。

作用域链生成时机:编译时,所以和函数定义的环境有关,和函数执行的环境无关。

// 例子1:
var age = 100
foo(123)
function foo(num) {
  console.log(m)
  var m = 10
  var n = 20

  function bar() {
    console.log(age)
  }

  bar()
}
// 打印结果:100

例子1执行栈及作用域链分析: image.png

// 函数的父级作用域和函数定义的环境有关,和函数执行的环境无关。
// 例子2:
var message = "Hello Global"
function foo() {
  console.log(message)
}
function bar() {
  var message = "Hello Bar"
  foo()
}
bar()
// 打印结果:"Hello Global"

一些练习:

//练习1:
var n = 100
function foo() {
  n = 200
}
foo()
console.log(n)
// 打印结果:200
//练习2:
function foo() {
  console.log(n)
  var n = 200
  console.log(n)
}
var n = 100
foo()
// 打印结果:
// undefined
// 200
//练习3:
var a = 100
function foo() {
  console.log(a)
  return
  var a = 200
}
foo()
// 打印结果:undefined
//练习4:
function foo() {
  m = 100
}
foo()
console.log(m)
// 打印结果:100
//练习5:
function foo() {
  var a = b = 10
  // => 转成下面的两行代码
  // var a = 10
  // b = 10
}
foo()
// console.log(a)
console.log(b)
// 打印结果:
// 打印a会报错:a is not defined
// 打印b:10