· 934 words · 5 min
JS 中任何语句的执行都会依赖特定的上下文,若上下文被切换,语句的执行效果可能会发生改变。 JS 中,切换上下文最主要的场景是函数调用。
JS 中的函数大致可分为几类:
function 关键字定义的函数。⇒ 定义的函数。class 中定义的函数。function * 定义的函数。class 类:实际也是一种函数。async 前缀的函数。对于普通变量而言,这些函数没有本质区别,他们行为的差异在于 this 关键字。
this 是执行上下文很重要的一个组成部分,同一个函数的调用方式不同,得到的 this 值也不同。
function print() {
console.log(this)
}
var o = {
print: print
}
print() // window
o.print() // o
普通函数的 this 值由调用它的引用(谁调用,指向谁)决定。o.print 获取的不是函数本身,
而是一个引用类型(Reference)。因此,o.print 函数上下文中的 this 指向的是 o。
print() 相当于 window.print(),因此 print() 上下文中的 this 指向的是 window。
因此,调用函数时使用的引用,决定了函数执行时刻的 this 值。
一个很重要的认知是,Reference 类型在做一些运算时会被解引用,获取真正的值(被引用的值)来参与运算, 类似函数调用、delete 等操作都需要用到 Reference 类型中的对象。
值得注意的是,如果使用箭头函数,那么不论用什么引用去调用,都不能改变其 this 值。
var print = () => console.log(this);
var o = {
print: print
}
print() // window
o.print() // window
看下面的例子:
var o = {
print: function () {
console.log(this)
}
}
var fn = o.print
o.print() // o
fn() // window
根据上面所说,o.print 调用时指向的是引用 o,那么打印出的 this 为 o。
当把 o.print 赋值给了 fn 之后,调用 fn,此时调用 fn 的引用是 window(fn 即 window.fn),
因此 fn() 打印出的 this 是 window。
函数能够引用定义时的变量(词法环境),也能够记住定义时的 this。函数中使用私有的 [[Environment]] 来保存上下文信息。
// fn.js
var b = 2
function fn() {
console.log(b)
console.log(a)
}
// index.js
var a = 1
fn() // 打印出 2,然后 Uncaught ReferenceError: a is not defined
上面的例子中,fn 能够访问定义时词法环境中的 b,但是缺不能访问执行时词法环境中的 a,这就是因为执行了上下文的切换机制。
JS 用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。当函数被调用时,会入栈一个新的执行上下文, 函数调用结束时,执行上下文被出栈。如下图所示:
this 是一个更为复杂的机制,JS 标准定义了 [[thisMode]] 私有属性,该属性有三个取值:
lexical:从上下文中找 this,对应了箭头函数。global:this 为 undefined 时,取全局对象,对应了普通函数。strict:当严格模式时,this 严格按照调用时传入的值,可能为 null 或者 undefined。函数创建新的执行上下文中的词法环境记录时,会根据 [[thisMode]] 来标记新纪录的[[thisBindingStatus]] 私有属性。
代码执行遇到 this 时,会逐层检查当前词法环境记录中的 [[ThisBindingStatus]],当找到有 this 的环境记录时获取 this 的值。
Function.prototype.call 和 Function.prototype.apply 可以指定函数调用时传入的 this 值。
无论使用任何方法,都不可改变箭头函数的内部 this 指向。