JavaScript 中的 this

· 934 words · 5 min

JS 中任何语句的执行都会依赖特定的上下文,若上下文被切换,语句的执行效果可能会发生改变。 JS 中,切换上下文最主要的场景是函数调用。

JS 中的函数大致可分为几类:

对于普通变量而言,这些函数没有本质区别,他们行为的差异在于 this 关键字。

this

this 是执行上下文很重要的一个组成部分,同一个函数的调用方式不同,得到的 this 值也不同。

function print() {
	console.log(this)
}

var o = {
	print: print
}

print() // window
o.print() // o

普通函数的 this 值由调用它的引用(谁调用,指向谁)决定。o.print 获取的不是函数本身, 而是一个引用类型(Reference)。因此,o.print 函数上下文中的 this 指向的是 oprint() 相当于 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,那么打印出的 thiso。 当把 o.print 赋值给了 fn 之后,调用 fn,此时调用 fn 的引用是 windowfnwindow.fn), 因此 fn() 打印出的 thiswindow

this 机制

函数能够引用定义时的变量(词法环境),也能够记住定义时的 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 用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。当函数被调用时,会入栈一个新的执行上下文, 函数调用结束时,执行上下文被出栈。如下图所示:

JavaScript Stack

this 是一个更为复杂的机制,JS 标准定义了 [[thisMode]] 私有属性,该属性有三个取值:

函数创建新的执行上下文中的词法环境记录时,会根据 [[thisMode]] 来标记新纪录的[[thisBindingStatus]] 私有属性。 代码执行遇到 this 时,会逐层检查当前词法环境记录中的 [[ThisBindingStatus]],当找到有 this 的环境记录时获取 this 的值。

改变 this

Function.prototype.callFunction.prototype.apply 可以指定函数调用时传入的 this 值。 无论使用任何方法,都不可改变箭头函数的内部 this 指向。

From 极客时间