变量
1.全局代码
1 var name = 'lxx'
2 console.log(num1) // undefined
3 var num1 = 20
4 var num2 = 30
5 var result = num1 + num2
2.代码编译,在执行代码前 js 引擎内部提前创建一个 globalObject 对象(GO)声明的变量会加入 GO,此时代码尚未执行,变量没有赋值,GO 中变量初始值为 undefined。这就是为什么第2行代码会输出 undefined 的原因。
var globalObject = {
window:globalObject,
name:undefined,
num1:undefined,
num2:undefined,
result:undefined
}
3.为了执行代码,引擎内部会有一个执行上下文栈(Execution Context Stack)全局执行上下文和函数执行上下文会进入执行上下文栈中执行。
4.全局执行上下文(Global Execution Context)
全局代码执行前会创建全局执行上下文 GEC:根据 ECMAScript 实现的宿主环境,表示全局上下文的对象可能不一样。浏览器中全局上下文是 window 对象。所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和方法。变量或函数的上下文决定了它们可以访问哪些数据,以及他们的行为。
5.变量对象。每个上下文都有一个关联的变量对象(Variable Object)上下文中定义的所有变量和函数都存在于变量对象上。全局执行上下文中的VO就是GO。
6.开始从上往下依次执行代码。
1 var name = 'lxx'
2 console.log(num1) // undefined
3 var num1 = 20
4 var num2 = 30
5 var result = num1 + num2
执行第1行代码 name,将值 lxx 赋值给 GO 里面的 name 属性,执行 var name = 'lxx' 时,给变量name 和 GO 中的 name 赋值,依此类推。
GO 对象会变为如下状态:
var globalObject = {
window:globalObject
name:'lxx',
num1:20,
num2:30,
result:50
}
函数
1.全局代码
1 var name = 'why'
2
3 foo(123)
4 function foo(num) {
5 console.log(m) // undefined
6 var m = 10
7 var n = 20
8 console.log(name)
9 }
2.代码编译,创建 globalObject 对象。 函数在 globalObject 对象中保存的是内存地址。
var globalObject = {
window:globalObject
name:undefined,
foo:0xa00
}
解析函数 foo 时,开辟一块内存空间,存储 foo 函数对象。其中包含 foo 的父级作用域 [[scope]]:parent scope 和函数的执行体。(先创建GO,然后如果解析到函数,再为函数开辟空间。就是因为这样,函数才会整个提升,可以在声明函数之前就调用函数)
3.开始执行代码
首先执行第1行代码,GO中的 name 被赋值为 lxx
var globalObject = {
window:globalObject,
name:lxx,
foo:0xa00
}
执行到函数调用 foo() 代码,并不会立即执行。会去 GO 中查找 foo,foo 的值是一个内存地址,根据内存地址找到 foo 的内容,并创建函数执行上下文,将包含 foo 的内容加入到执行上下文调用栈中。foo 在内存中主要包含两个常用内容:[[scope]]:parent scope 父级作用域和自身函数体。如果 foo 函数中还嵌套了函数 bar ,bar 在一开始不需要执行时是不会编译的。
4.创建函数执行上下文和活动对象
执行第3行代码 foo() 之前会创建函数执行上下文和 AO 对象。如果上下文是函数,则其活动对象 AO(Activation Object)用作变量对象,来保存函数中的变量。函数中的参数和变量都会被提升到 AO 对象中。嵌套函数 bar 此时会被真正的编译。
// AO
{
num:undefined,
m:undefined,
n:undefined
}
5.开始执行函数的代码
第4行代码:将 AO 中的 num 改成 123;
第5行代码:console.log(m) m 从 AO 中读取,输出 undefined;
第6行代码:将 AO 中的 m 赋值为 10;
第7行代码:将 AO 中的 n 赋值为 20;
第8行代码:打印 name,函数的 AO 中找不到,会沿着作用域链向外查找直到 GO,GO中仍找不到则报错。
// AO
{
num:123,
m:10,
n:20
}
6.函数执行完毕 函数执行上下文弹出执行上下文栈,并且销毁该函数执行上下文和该函数的 AO。如果再次调用 foo(),又会创建一个新的函数执行上下文并加入到执行上下文栈重复以上步骤。