执行上下文
执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段,本文重点介绍创建阶段。
- 创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明(变量的声明提前有赖于 var 关键字)。
创建作用域链:在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
确定 this 指向。
- 执行阶段
创建完成之后,就会开始执行代码,在这个阶段,会完成变量赋值、函数引用、以及执行其他代码。
- 回收阶段
函数调用完毕后,函数出栈,对应的执行上下文也出栈,等待垃圾回收器回收执行上下文。
执行上下文变量对象
建立 arguments 对象。检查当前执行上下文中的参数,建立该对象下的属性与属性值。
检查当前执行上下文的函数声明,也就是使用 function 关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果该属性之前已经存在,那么该属性将会被新的引用所覆盖。
检查当前执行上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为 undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为 undefined,则会直接跳过,原属性值不会被修改。
this
一般指向
- 函数是否在 new 中被调用(new 操作符指向)?
- 函数是否通过 call、apply、bind 显式指向?
- 函数是否被当做某个对象的方法而调用(隐式指向)?
- 若以上都不是的话,使用默认绑定。
特殊情况
- 被忽略的 this
null
或者undefined
作为this
指向的对象传入call
、apply
或者bind
,这些值在调用时会被忽略,实际应用的是默认指向规则。
function func() {
console.log(this.a)
}
var a = 2
func.call(null) //>> 2
//this指向了window
- 隐式指向之隐式丢失
隐式丢失最容易在赋值时发生;隐式丢失发生时,调用这个函数会应用默认指向规则。下面再举一段更具迷惑性的例子:
function func() {
console.log(this.a)
}
var a = 2
var o = { a: 3, func: func }
var p = { a: 4 }
o.func() //>> 3
;(p.func = o.func)() //>> 2
// 赋值表达式 p.func=o.func 的返回值是目标函数的引用,也就是 func 函数的引用
// 因此调用位置是 func() 而不是 p.func() 或者 o.func()
- 箭头函数
箭头函数并不是使用function
关键字定义的,而是使用被称为“胖箭头”的操作符 =>
定义的。
箭头函数不遵守this
的四种指向规则,而是根据函数定义时的作用域来决定 this
的指向。何谓“定义时的作用域”?就是你定义这个箭头函数的时候,该箭头函数在哪个函数里,那么箭头函数体内的 this 就是它父函数的 this。
function func() {
// 返回一个箭头函数
return a => {
//this 继承自 func()
console.log(this.a)
}
}
var obj1 = {
a: 2,
}
var obj2 = {
a: 3,
}
var bar = func.call(obj1)
bar.call(obj2) //>> 2 不是 3 !
// func() 内部创建的箭头函数会捕获调用时 func() 的 this。
// 由于 func() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,
// this一旦被确定,就不可更改,所以箭头函数的绑定无法被修改。(new 也不行!)