· 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
指向。