[译] 从 JS 对象的内部原理来理解 this 关键字
栏目: JavaScript · 发布时间: 5年前
内容简介:JavaScript 是一种支持面向对象编程和动态绑定的多范式语言。动态绑定是一个非常强大的功能,它允许在运行时更改 JavaScript 代码的结构,但是这种强大的功能和它的灵活性将可能带来一些难以理解的问题,而这些问题主要集中在动态绑定是在运行时而不是编译时确定要调用的方法。JavaScript 通过让我们来看一段代码:
JavaScript 是一种支持面向对象编程和动态绑定的多范式语言。动态绑定是一个非常强大的功能,它允许在运行时更改 JavaScript 代码的结构,但是这种强大的功能和它的灵活性将可能带来一些难以理解的问题,而这些问题主要集中在 this
关键字的行为上。
动态绑定
动态绑定是在运行时而不是编译时确定要调用的方法。JavaScript 通过 this
和原型链实现了这一点。特别是,方法内部的 this
值是在运行时确定的,更改的规则取决于该方法是如何定义的。
让我们来看一段代码:
const a = { a: 'a' }; const obj = { getThis: () => this, getThis2 () { return this; } }; obj.getThis3 = obj.getThis.bind(obj); obj.getThis4 = obj.getThis2.bind(obj); const answers = [ obj.getThis(), obj.getThis.call(a), obj.getThis2(), obj.getThis2.call(a), obj.getThis3(), obj.getThis3.call(a), obj.getThis4(), obj.getThis4.call(a) ]; 复制代码
你可以先尝试把你的答案写下来。然后,使用 console.log()
检查你的答案是否正确?
我们从第一个例子开始往下看。 obj.getThis()
返回 undefined
,这是为什么呢?箭头函数的内部永远不会有自己 this
。相反,它们总是委托给词法作用域。在 ES6 模块的根作用域中,有一个 undefined
的 this
。同样的原因, obj.getThis.call(a)
也是 undefined
。对于箭头函数,即使使用 call()
或 bind()
也不能重新绑定 this
的值。它总是把 this
委托给词法作用域。
obj.getThis2()
通过常规的方式调用对 this
进行动态绑定。如果函数之前没有 this
绑定,函数可以使用这样的方式进行动态绑定(除了箭头函数), this
的值将会绑定到调用它的对象(通过 .
运算符或者 []
语法)。
obj.getThis2.call(a)
有点棘手。 call
方法给函数绑定了给定的 this
值以及可选的参数。换句话说,它从 call()
第一个参数中获得 this
的绑定,因此 obj.getThis2.call(a)
返回对象 a
。
使用 obj.getThis3 = obj.getThis.bind(obj);
,我们试图绑定一个箭头函数的 this
值。我们已经知道了这样不会是预期的那样,所以 obj.getThis3()
和 obj.getThis3.call(a)
都会返回 undefined
。
你可以绑定常规的函数,因此 obj. getThis4()
如预期的那样返回 obj
,因为它的 this
已经与 obj
绑定。 obj.getThis4.call(a)
返回第一次使用 bind
绑定的 obj
而不是 a
。
Curve Ball
同样的问题,但这一次,是类中方法属性的 this
值(在编写本文时处于 Stage 3
,默认情况下在 Chrome 中可用,请使用 @babel/plugin-proposal-class
属性):
class Obj { getThis = () => this getThis2 () { return this; } } const obj2 = new Obj(); obj2.getThis3 = obj2.getThis.bind(obj2); obj2.getThis4 = obj2.getThis2.bind(obj2); const answers2 = [ obj2.getThis(), obj2.getThis.call(a), obj2.getThis2(), obj2.getThis2.call(a), obj2.getThis3(), obj2.getThis3.call(a), obj2.getThis4(), obj2.getThis4.call(a) ]; 复制代码
同样,你可以先写下你的答案。
除了 obj2.getThis2.call(a)
返回一个对象 a
之外,所有的都返回实例对象 obj2
。箭头函数仍然委托给词法作用域中的 this
。不同之处在于,词法对于类是不同的。实际上,类属性赋值被编译成这样:
class Obj { constructor() { this.getThis = () => this; } ... 复制代码
也即是说,箭头函数是在构造函数的上下文中定义的。由于这是一个类,创建实例的唯一方法是使用 new
关键字(不使用 new
将抛出错误)。
new
关键字所做的最重要的事情之一是实例化一个新的对象实例,并将构造函数中的 this
绑定到新的对象实例上。这种特性,加上前面我们所讨论的,就可以解释上面代码的结果了。
总结
你觉得如何?很好地理解 JavaScript 中 this
的行为将为你节省大量调试棘手问题的时间。如果你有遇到任何问题,你可以再尝试一下本文中的例子。运行一下这些例子,然后回来再次调试自己的问题,直到你解决了问题,并去向其他人解释为什么这些函数它们会返回不同的结果。
如果你觉得这比你想象中的要难,不用担心。在这个问题上,我测试了很多的开发者,到目前为止,只有一个开发者获得了满分。
随着类和箭头函数行为特性的增加,使用 call()
、 bind()
或 apply()
方法更加复杂。总结一下:箭头函数总是将 this
委托给词法作用域,而类中的 this
实际上是词法作用域下的构造函数中的 this
值。如果你疑惑 this
是什么,你可以使用调试器来验证一下它的结果。
译者注:《JavaScript Weekly》周刊正在翻译中...
请戳 -> JavaScript-Weekly-zh-CN
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- volatile 关键字的原理和要避免的误区
- Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比
- 番外篇2-基本规范、注释、static关键字、import关键字
- 说说iOS中的常用的关键字static ,class(仅限Swift关键字)
- Golang 关键字
- 2019 关键字
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。