魔幻语言 JavaScript 系列之 call、bind 以及上下文
原文:The Most Clever Line of JavaScript
作者:Seva Zaikov
原文
最近 一个朋友 发给我一段非常有趣的 JavaScript 代码,是他在某个 开源库中 看到的:
addressParts.map(Function.prototype.call, String.prototype.trim);一开始,我觉得这是一个“不错的尝试”。但是,印象中 map 好像只接受一个参数,这里却出现第二个参数,所以去查看了 MDN文档,才知道可以传一个上下文(context)作为第二个参数。在这时候,我还无法解释这段代码,运行完之后感到更加困惑了,因为它竟然能如预期那样工作。
我花了至少半个小时来尝试理解这段代码,这是一个很有趣的例子,可以用来说明 JavaScript 是一门多么魔幻的语言,即使已经写了好几年的 JS。当然,你可以选择自己去弄清楚,如果想看看我的理解,请继续阅读。
那么,它到底是如何工作的呢?让我们从一种更简单的实现开始(实际上这种实现代码更短,并且更易读:)):
addressParts.map(str => str.trim());Function.prototype.call 是 JavaScript 函数原型中的一个函数,它调用函数,使用第一个参数作为 this 参数,并传递剩余参数作为被调用函数的参数。举个例子:
// this function has `Function` in prototype chain
// so `call` is available
function multiply(x, y) {
return x * y;
}
multiply.call(null, 3, 5); // 15
multiply(3, 5); // same, 15map 第二个参数的典型用法如下所示,假设有一个基于类的 React 组件,其功能是渲染一个按钮列表:
但是,以我的经验来看,这种使用第二个参数的做法并不常见,更常见的做法是使用类属性或装饰器来避免绑定。
译者注:
map第二个参数的用法等同于
还有一个类似的方法 -- Function.prototype.apply,工作原理与 call 相同,只是第二个参数应该是一个数组(译者注:或者是一个类数组),它将被转换成一个参数列表,用逗号分隔。所以,让我们看看如何使用它来计算最大值:
现在,我们重新创建一个可以解决问题的函数调用方式。我们想删除字符串两端的空白字符,这个方法位于 String.prototype ,所以我们使用 . 操作符来调用它(虽然,字符串是原始值(primitive),但是当我们进行方法调用时,会在内部被转换成对象)。我们继续:
我们现在距离答案更近一步,但是仍然没有解释清楚最初那段代码:
让我们自己来实现 Function.prototype.call:
现在,我们可以来理一理所有的东西。当我们在 .map 里面声明函数的时候,我们给 Function.prototype.call 绑定String.prototype.trim 作为 this 上下文,然后我们在数组中的每个元素上调用这个函数,把每个字符串作为 thisArg 参数的值传递给 call。这意味着,String.prototype.trim 将使用字符串作为 this 上下文来调用。我们已经知道这样做是有效的,看看下面的例子:
问题解决了!但是,我认为这并不是一个好的做法,至于应该如何使用一种好的方式来完成这件事, 很简单,只需传递一个匿名函数就能搞定:
也谈谈 JavaScript 中的 call、apply 和 bind
作者在最后这一段可能讲得有些简略,尤其是对于 bind 的用法,谈谈我的理解思路:
Last updated