关于 this 我想跟你说

735 阅读3分钟

我想你一定经历过以下场景:

  • 简要说说 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.,而仅使用标识符进行词法引用。其次,开发者试图用 thisfoo()bar() 的词法作用域间建立一座桥,使得 bar() 可以方位 foo() 内部作用域的变量。这样的桥是不可能的。你不能使用 this 引用在词法作用域中查找东西。

每当你感觉自己在试图使用 this 来进行作用域的查询时,提醒你自己:这里没有桥。

this 到底是什么

this 不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this 绑定与函数声明的位置没有关系,而与函数被调用的方式紧密相连。

当一个函数被调用时,会建立一个成为执行环境的活动记录。这个记录包含函数是从何处(调用栈——callstack)被调用的,函数是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 的引用。

this 实际上是在函数被调用时建立的一个绑定,它指向 什么 是完全由函数被调用的调用点来决定的。

后话

如果不写这篇文章,也许我不会再去翻开《你不知道的Javascript》这本书,也不会去注意到一些能够点醒我的话。

还是那句话 温故而知新,一起加油吧。

参考

第一章: this 是什么 · 你不知道的JavaScript@Js中文网-前端进阶资源教程 (javascriptc.com)

【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理) - 掘金 (juejin.cn)