资讯专栏INFORMATION COLUMN

【译】this 是什么?JavaScript 对象的内部工作原理

Hwg / 3038人阅读

摘要:关键字会实例化一个新的对象实例,并在执行构造函数时将指向该实例。原文链接译是什么对象的内部工作原理

原文链接:What is this? The Inner Workings of JavaScript Objects (需要梯子)

原文作者:Eric Elliott

译文永久链接:【译】什么是 this?JavaScript 对象的内部工作原理

译者:士心

翻译目的:函数动态绑定 this 的特性,经常让开发者感到头疼,这篇文章能帮助梳理概念

JavaScript 是一种支持面向对象编程和动态绑定的多范式语言。动态绑定是其一个强大的特性,它允许 JavaScript 代码在运行时更改 this,但是这一强大而且灵活的特性却会给开发者带来一些困惑,这些困惑集中在 JavaScript 代码运行时的表现上。

动态绑定(Dynamic Binding)

动态绑定指的是在运行时才确定调用函数的方式,而不是更早的编译阶段。JavaScript 通过 this 和原型链来体现动态绑定。也就是说,函数内部的 this 是在运行时确定,并且定义函数的方式不同,确定 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()你的答案,你答对了吗?

译者注:本文的示例需要运行在 ES6 Module 下才符合文章所说的结果,你可以在 HTML 页面中指定脚本标签类型 ... 让代码运行在 ES6 Module 下。如果你将代码直接复制到浏览器控制台运行,下文说的 undefined 实际上是 window

让我们从第一个结果开始。obj.getThis() 返回 undefined,但为什么呢?箭头函数永远不会绑定属于自己的 this,它们的 this 总是绑定在定义时所在的作用域上。本例中的定义时所在的作用域,就是 ES6 模块的根作用域,这里的 thisundefined。因为相同的原因,obj.getThis.call(a) 同样也是 undefined。对于箭头函数,它的 this 不能被重新分配赋值,即使使用 .call().bind(),它的 this 总是绑定在定义时所在的作用域上,而不会指向运行时所在的作用域。

obj.getThis2() 通过一般的函数调用获取其绑定。如果函数之前没有绑定 this(就是说,它不是箭头函数),那该函数就可以拥有自己的 this 绑定,具体绑定到使用 .[] 调用该方法的对象上。

obj.getThis2.call(a) 做了点小动作,call() 方法提供给定的 this 值和可选参数调用函数。换句话说,函数从 .call() 参数获取 this 的绑定,因此 obj.getThis2.call(a) 返回 a 对象。

使用 obj.getThis3 = obj.getThis.bind(obj);,我们尝试绑定一个箭头函数,前面我们已经讨论过绑定箭头函数是不起作用的,所以 obj.getThis3()obj.getThis3.call(a) 都得到 undefined

我们可以绑定一般的函数,所以 obj.getThis4() 按预期返回 obj,因为它已经使用 obj.getThis4 = obj.getThis2.bind(obj); 绑定了。而 obj.getThis4.call(a) 遵从第一个的绑定,所以返回 obj 而不是 a

加大难度(Curve Ball)

同样的挑战,不过这一次,使用到了 class 的公共字段语法(public fields syntax) (写这篇文章的时候,该语法提案处于 Stage3 阶段。Chrome 和 @babel/plugin-proposal-class-properties 已经支持):

译者注:公共字段语法(public fields syntax),如果不知道是什么的话,可以看阮一峰 ES6 | 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 对象外,其它都返回对象实例。箭头函数的 this 仍然绑定在定义时所在的作用域上,区别在于定义时所在作用域的 this 已然不是 undefined。这段代码的底层,会将类的属性赋值编译成:

class Obj {
  constructor() {
    this.getThis = () => this;
  }
...

也就是说,箭头函数是在构造函数(constructor)的上下文中定义的。由于它是一个类,创建实例的唯一方法是使用 new 关键字(省略 new 会抛出错误)。

new 关键字会实例化一个新的对象实例,并在执行构造函数时将 this 指向该实例。这种行为,加上我们上面已经提到的其他行为,就能解释清楚结果。

总结

你做得怎样?有没有做对呢?理解了 this 的表现行为,在调试棘手的问题时能节省大量时间。如果你做错了任意一道,那你需要多加练习。研究上面这些例子,然后回来再做一次,直到你都能做对,并向其他人解释为什么这些方法会返回这些值。

如果这些题比你想象的要难,你并不是一个人。针对这个主题,我已经问过了不少开发者,我认为到目前为止只有一位开发人员掌握了。

加上类和箭头函数的行为,会使 .call(). bind().apply() 的动态绑定开始变得复杂。请记住,箭头函数总是将 this 绑定在定义时所在的作用域上,第二个例子 class 中的 this 实际绑定在执行构造函数时的作用域上。如果你还有疑问,请记住使用 debugger 工具来验证是否符合你的想法。

还要记住,在JavaScript中,即使不用 this,你也可以做很多事情。根据我的经验,几乎任何东西都可以使用纯函数重新实现,纯函数接收所有传递给它们的显式参数(你可以将 this 看成是可变的隐式参数)。封装在纯函数中的逻辑具有确定性,这使得它更易于测试,并且没有副作用。这意味着与操作 this 不同,你不可能破坏其他任何东西。而每当你修改 this 时,依赖于 this 的行为就可能被破坏。

也就是说,this 有时很有用,例如:在大量对象之间共享方法。即使在函数式编程中,this 对于访问其他对象上的函数,以实现在现有函数之上构建新函数是很有用的,例如:.flatMap() 可以通过组合 this.map()this.constructor.of() 来实现。

如果你喜欢这篇文章,请关注我,我会持续输出更多原创且高质量的内容。

原文链接:【译】this 是什么?JavaScript 对象的内部工作原理

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103801.html

相关文章

  • JavaScript面试问题:事件委托和this

    摘要:主题来自于的典型面试问题列表。有多种方法来处理事件委托。这种方法的缺点是父容器的侦听器可能需要检查事件来选择正确的操作,而元素本身不会是一个监听器。 showImg(http://fw008950-flywheel.netdna-ssl.com/wp-content/uploads/2014/11/Get-Hired-Fast-How-to-Job-Search-Classifieds...

    浠ラ箍 评论0 收藏0
  • JavaScript 工作原理之二-如何在 V8 引擎中书写最优代码 5 条小技巧()

    摘要:本章将会深入谷歌引擎的内部结构。一个引擎可以用标准解释程序或者即时编译器来实现,即时编译器即以某种形式把解释为字节码。引擎的由来引擎是由谷歌开源并以语言编写。注意到没有使用中间字节码来表示,这样就不需要解释器了。 原文请查阅这里,略有删减。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第二章。 本章将会深入谷歌 V8 引擎的内部结构。我们也会...

    PingCAP 评论0 收藏0
  • []JavaScript如何工作:内存管理以及如何处理四种常见内存泄漏

    摘要:是如何工作的内存管理以及如何处理四种常见的内存泄漏原文译者几个礼拜之前我们开始一系列对于以及其本质工作原理的深入挖掘我们认为通过了解的构建方式以及它们是如何共同合作的,你就能够写出更好的代码以及应用。 JavaScript是如何工作的:内存管理以及如何处理四种常见的内存泄漏 原文:How JavaScript works: memory management + how to han...

    tianren124 评论0 收藏0
  • 】理解JavaScript:闭包

    摘要:当面试中让我解释一下闭包时我懵逼了。这个解释开始可能有点晦涩,让我们抽丝剥茧摘下闭包的真面目。此文不详述作用域有专门的主题阐述,不过作用域是理解闭包原理的基础。这才是闭包的真正便利之处。闭包使用不当就会很坑。 原文链接 为什么深度学习JavaScript? JavaScript如今是最流行的编程语言之一。它运行在浏览器、服务器、移动设备、桌面应用,也可能包括冰箱。无需我举其他再多不相干...

    岳光 评论0 收藏0
  • JavaScript 工作原理之一-引擎,运行时,调用堆栈()

    摘要:本章会对语言引擎,运行时,调用栈做一个概述。调用栈只是一个单线程的编程语言,这意味着它只有一个调用栈。查看如下代码当引擎开始执行这段代码的时候,调用栈会被清空。之后,产生如下步骤调用栈中的每个入口被称为堆栈结构。 原文请查阅这里,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原...

    Betta 评论0 收藏0

发表评论

0条评论

Hwg

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<