我想你一定经历过以下场景:
- 简要说说 this
- 指出下面的 this 指向
- 为什么我的 this 不是我想的 this
我不单单经历过,我正在经历着。
它不是
在开始讲 this 是如何实际工作前,首先要摒弃一些误解————它实际上不是如何工作的。
它自己
通常我们在写函数的时候,我们都倾向于认为 this 指向函数自己。在某种情况下它看上去好像是那么一回事,但那是不对的。
思考以下代码:
function foo(num) {
console.log('foo:' + num);
this.count++; // {1}
}
// 我们给 foo 添加一个自己的属性 count,用来追踪 foo 被调用了多少次。
foo.count = 0; // {2}
for (let i = 0; i < 10; i++) {
foo(i)
}
console.log(foo.count)
想好了吗,在浏览器运行看看是不是你想的结果叭。
怎么样,跟你想的一样吗?不一样也没关系,往下看,然后理解记住。
在行 {2} 时,程序确确实实向函数对象 foo 添加了一个 count 属性。但对于行 {1} 的而言,this 根本就不是指向函数对象,所以即便属性名称一样,但跟对象不同,因而产生了混淆。
注意:一个负责人的开发者 应当 在这里提出一个问题:“如果我递增的
count属性不是我以为的那个,那是哪个count被我递增了?”。实际上,如果它再挖深一些,他会发现自己不小心创建了一个全局变量count,而且它当前的值是NaN。当然,一旦他发现这个不寻常的结果后,他会有一堆其他问题:“他怎么是全局的?为什么他是NaN而不是某个正确的计数值?”。
想想看怎么解决这个问题:
function foo(num) {
console.log('foo:' + num);
foo.count++; // {1}
}
// ...
只需要将行 {1} 修改成 foo.count++ 就可以了,也确实能够运行,也确实结果是我们想要的,但它真的是我们想要的吗?
这个方法确实“解决”了问题,但你看看标题,它只是简单地忽略了真正的问题 —— 缺乏对与 this 的含义和其工作方式上的理解 —— 反而退回到了一个更加熟悉的机制的舒适区:词法作用域。
另外一种解决方案就是强迫 this 指向 foo 函数对象:
function foo(num) {
console.log('foo:' + num);
this.count++; // {1}
}
// 我们给 foo 添加一个自己的属性 count,用来追踪 foo 被调用了多少次。
foo.count = 0; // {2}
for (let i = 0; i < 10; i++) {
foo.call(foo, i)
}
console.log(foo.count)
我们直面 this,我们终将成为勇敢的码上战士。
它的作用域
另外一种常见的误解是:它不知怎的指向了函数的作用域。
直接说结论:this 不会以任何方式指向函数的词法作用域。作用域好像是一个将所有可用的标识符作为属性的对象,这从内部来说是的。但是 js 代码不能访问作用域“对象”。它是引擎实内部实现的。
我们来看一下这个代码:
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a)
}
foo() // undefined
这段代码企图跨域边界,用 this 来引用函数的词法作用域,而且它失败了。
这段代码不止一个错误。首先,试图通过 this.bar() 来引用 bar() 函数。它只是碰巧能够工作。调用 bar() 最自然的方式是省略开头的 this.,而仅使用标识符进行词法引用。其次,开发者试图用 this 在 foo() 和 bar() 的词法作用域间建立一座桥,使得 bar() 可以方位 foo() 内部作用域的变量。这样的桥是不可能的。你不能使用 this 引用在词法作用域中查找东西。
每当你感觉自己在试图使用
this来进行作用域的查询时,提醒你自己:这里没有桥。
this 到底是什么
this 不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this 绑定与函数声明的位置没有关系,而与函数被调用的方式紧密相连。
当一个函数被调用时,会建立一个成为执行环境的活动记录。这个记录包含函数是从何处(调用栈——callstack)被调用的,函数是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 的引用。
this实际上是在函数被调用时建立的一个绑定,它指向 什么 是完全由函数被调用的调用点来决定的。
后话
如果不写这篇文章,也许我不会再去翻开《你不知道的Javascript》这本书,也不会去注意到一些能够点醒我的话。
还是那句话 温故而知新,一起加油吧。
参考
第一章: this 是什么 · 你不知道的JavaScript@Js中文网-前端进阶资源教程 (javascriptc.com)