目录
1_深入V8引擎原理
JS代码下载完成后,是如何执行的?
浏览器内核由两部分组成,以webkit为例
-
WebCore:负责HTML解析、布局、渲染等等相关的工作;
-
JavaScriptCore:解析、执行JavaScript代码;
V8是强大的JavaScript引擎
1.1_V8引擎的执行原理【面试】
官方对V8引擎的定义:
-
V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
-
它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。
-
V8可以独立运行,也可以嵌入到任何C ++应用程序中。
1.2_V8引擎的架构
V8引擎本身的源码非复杂,大概有超过100w行C++代码,通过了解架构,可以知道它是如何对JavaScript执行的。
Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码;
-
如果函数没有被调用,那么是不会被转换成AST的;
-
Parse的V8官方文档:https://v8.dev/blog/scanner
Ignition是一个解释器,会将AST转换成ByteCode(字节码)
-
同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
-
如果函数只调用一次,Ignition会解释执行ByteCode;
-
Ignition的V8官方文档:https://v8.dev/blog/ignition-interpreter
TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
-
如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
-
但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
上图是V8引擎官方的解析图,补充两个概念:
词法分析,也叫扫描器(scanner
):
- 将字符序列转换成token序列的过程;
- token是记号(tokenization)的缩写
- 词法分析器(lexical analyzer,简称lexer)
语法分析,也叫语法分析器,称之为parser
。
1.3_JS代码执行原理 - 版本说明
ECMA早期的版本中(ECMAScript3),代码的执行流程的术语和ECMAScript5以及之后的术语会有所区别:
-
目前网上大多数流行的说法都是基于ECMAScript3版本的解析,并且在面试时问到的大多数都是ECMAScript3的版本内容。
-
但是ECMAScript3终将过去, ECMAScript5必然会成为主流,所以最好也理解ECMAScript5甚至包括ECMAScript6以及更好版本的内容;
-
事实上在TC39( ECMAScript5 )的最新描述中,和ECMAScript5之后的版本又出现了一定的差异;
学习顺序:
-
通过ECMAScript3中的概念学习JavaScript执行原理、作用域、作用域链、闭包等概念;
-
通过ECMAScript5中的概念学习块级作用域、let、const等概念;
事实上,它们只是在对某些概念上的描述不太一样,在整体思路上都是一致的。
2_JS执行上下文
初始化全局对象:js引擎在执行代码之前,会在堆内存中创建一个全局对象Global Object(GO)
-
该对象所有的作用域(scope)都可以访问;
-
里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
-
其中还有一个window属性指向自己
执行上下文( Execution Contexts )
js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈,它执行的是全局的代码块:
-
全局的代码块为了执行会构建一个 Global Execution Context(GEC);
-
GEC会 被放入到ECS中 执行;
GEC被放入到ECS中里面包含两部分内容:
-
第一部分:在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值; 这个过程也称之为变量的作用域提升(hoisting)
-
第二部分:在代码执行中,对变量赋值,或者执行其他的函数
VO对象(Variable Object)
每一个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中;
当全局代码被执行的时候,VO就是GO对象了
3_全局代码执行过程
<script>
var message = "Global Message"
function foo(num) {
var message = "Foo Message"
var age = 18
var height = 1.88
console.log("foo function")
}
foo(123)
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log(result)
</script>
执行前
执行后
4_函数的执行过程
执行前
执行后
5_作用域和作用域链(Scope Chain)
当进入到一个执行上下文时,执行上下文也会关联一个作用域链(Scope Chain)
-
作用域链是一个对象列表,用于变量标识符的求值;
-
当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型,添加一系列的对象;
参考他人文章:https://blog.csdn.net/weixin_52834435/article/details/125561301
一个demo
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
解释如下:
当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到 first() 函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。
当从 first() 函数内部调用 second() 函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当 second() 函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。
当 first() 执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文
6_面试题
// 1.面试题一:
var n = 100
function foo() {
n = 200
}
foo()
console.log(n) // 此时n为200 。执行foo函数,在自身查找变量n ,没有找到,去上一层作用域查找,也就是windows,找到n,虽然原始值为100,但是执行foo函数后,改变了n的值
// 2.面试题二:
var n = 100
function foo() {
console.log(n) //此时n为undefined。执行foo函数,先在foo作用域内查找n,发现存在变量n,但是,还没执行到下一行代码,所以此时n的值为undefined
var n = 200
console.log(n) //此时n为200。执行foo函数,先在foo作用域内查找n,发现存在变量n,并且上一行代码已经给n赋值,所以此时n的值为200
}
foo()
// 3.面试题三:
var n = 100
function foo1() {
console.log(n) //这里n的上层作用域是windows。跟foo2来调用foo1没关系,只与foo1所在位置有关
}
function foo2() {
var n = 200
console.log(n) //n=200
foo1() //n=100
}
foo2()
// 4.面试题四:
var n = 100
function foo() {
console.log(n) //undefined 。因为foo执行的时候,在自身作用域查找到变量n了,但是还没被赋值,只能是undefined
return //return直接让函数结束,无法执行到下一行代码
var n = 200
}
foo()
// 6.面试题五:
function foo() {
var a = b = 100 //变量b会被自动添加到全局对象中,但是变量a的作用域只存在于foo对象内部
}
foo()
console.log(b) //b的值为100
console.log(a) //代码执行报错,因为浏览器认为变量a未被定义,