
照片:好奇的莉莉安娜·萨布(CC BY 2.0)
JavaScript是一种多范式语言,支持面向对象的编程和动态链接。 动态链接是一个强大的概念,它使您可以在运行时更改JavaScript代码的结构,但是这些额外的功能和灵活性是通过一些混乱的代价来实现的,其中大多数与JavaScript中的this
行为有关。
动态链接
使用动态绑定时,要调用的方法的定义在运行时而不是在编译时发生。 JavaScript 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) ];
考虑answers
数组中的值是什么,并使用console.log()
检查您的答案。 猜到了吗
让我们从第一种情况开始,然后按顺序继续。 obj.getThis()
返回undefined
,但是为什么呢? 箭头函数从来没有自己的。 取而代之的是,他们总是从词法范围( 近似于词法this )中获取这个 。 对于ES6模块的根,词法区域将具有this
的undefined
值。 obj.getThis.call(a)
相同的原因,也未定义obj.getThis.call(a)
。 对于箭头函数,即使使用.call()
或.bind()
也不能覆盖this
功能。 this
将始终取自词汇领域。
obj.getThis2()
在方法调用期间获取绑定。 如果此函数之前没有此绑定,则可以将此绑定到this
(因为这不是箭头函数)。 在这种情况下, this
是一个obj
对象, this
对象在调用方法时绑定.
或[squareBracket]
属性访问语法。 ( 注意隐式绑定 )
obj.getThis2.call(a)
有点复杂。 call()
方法使用给定的此值和可选参数调用一个函数。 换句话说,该方法从obj.getThis2.call(a)
.call()
参数获取this
绑定,因此obj.getThis2.call(a)
返回对象a
。 ( 注意显式绑定 )
如果是obj.getThis3 = obj.getThis.bind(obj);
我们正在尝试获得一个带有this
界限的箭头函数,正如我们已经弄清楚的那样,它将不起作用,因此我们分别为obj.getThis3()
和obj.getThis3.call(a)
undefined
的obj.getThis3()
。
对于常规方法,您可以绑定,因此obj.getThis4()
返回obj
,如预期的那样。 他已经在这里绑定了obj.getThis4 = obj.getThis2.bind(obj);
,而obj.getThis4.call(a)
考虑到第一个绑定并返回obj
而不是a
。
扭球
我们将解决相同的问题,但是这次我们使用带有公共字段的class
来描述对象(撰写本文时, 第3阶段的创新默认情况下在Chrome中可用,并且具有@babel/plugin-offer-class-properties
):
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)
之外的所有调用obj2.getThis2.call(a)
返回该对象的实例。 obj2.getThis2.call(a)
返回a
。 Arrow函数仍然可以从其词法环境中获得this
信息。 对于类属性,如何从词法环境中定义this
有所不同。 在内部,类属性的初始化看起来像这样:
class Obj { constructor() { this.getThis = () => this; } ...
换句话说,arrow函数是在构造函数上下文中定义的。 由于这是一个类,因此创建实例的唯一方法是使用new
关键字(省略new
会导致错误)。 new
关键字所做的最重要的事情之一是创建对象的新实例,并将其绑定到构造函数中。 这种行为,加上我们上面提到的其他行为,应该可以解释其余的行为。
结论
你成功了吗? 很好地了解JavaScript中的行为方式将为您节省大量调试复杂问题的时间。 如果您在答案中输入错误,则意味着您需要练习一些。 通过示例进行练习,然后返回并再次检查自己,直到可以运行测试并向其他人解释方法为何返回其返回值。
如果这比您预期的要难,那么您并不孤单。 我问了很多有关此主题的开发人员,我认为到目前为止,只有一个开发人员可以完成此任务。
开始搜索可以使用.apply()
或.apply()
重定向的动态方法时,由于添加了类方法和箭头函数,该方法变得更加复杂。 也许您应该再次专注于此。 请记住,箭头函数始终从词法范围中获取this
,而class
实际上在词法上受引擎盖下类的构造函数限制。 如果对此有疑问,请记住可以使用调试器检查其值。
请记住,在解决许多JavaScript任务时,您可以不用这样做。 以我的经验,几乎所有东西都可以用纯函数来重新定义,这些函数将所有参数用作显式参数(可以认为是隐式变量)。 通过纯函数描述的逻辑是确定性的,这使其更具可测试性。 同样,使用这种方法也没有副作用,因此,与进行操作时不同,您不太可能破坏任何东西。 每次设置该值时,取决于其值的内容可能会中断。
但是,有时this
很有用。 例如,在大量对象之间交换方法。 即使在函数式编程中, this
也可用于访问其他对象方法,以实现在现有代数之上构建新代数所需的代数转换。 因此,可以使用this.map()
和this.constructor.of()
获得通用的.flatMap()
this.constructor.of()
。
感谢您提供翻译wksmirnowa和VIBaH_dev的帮助