在JavaScript的Array数组中调用一组Function方法


发布者 ourjs  发布时间 1418274192201
关键字 JS学习  JavaScript 
注* 这是一篇很有趣的文章,可以很好地帮助你了解JavaScript的Function是怎么工作的。

我在看几个月前写的一些代码。在一个数组中(arrays)中存放了一组方法(function),在未来的某个时间要一次性将这些方法都执行一遍。

这是一件非常简单的事,但有关代码的一些始终困扰着我。

假设数组是这样的:

var callbacks = [
  function() { console.log(1); },
  function() { console.log(2); }
];

你只需要遍历这个数组,并调用每个函数即可:

callbacks.forEach(function(callback) {
  callback();
});

使用forEach比较优美的一点是你可以传入一个不需要每次定义的匿名函数。所以我们也可以将这个函数事先定义。

function call(fn) {
  fn();
}
callbacks.forEach(call);

注* 在大量调用时命名函数比匿名函数性能稍好 JavaScript中匿名函数和命名函数的性能差异 

这可以解决问题,但是看上去有点傻。 JS已经有Function.prototype.call为什么我们不能用它的而要自己写。所以我想要直接使用Function.prototype.call来完成这个。

Function.prototype.call


首先要弄清楚的是call的内部实现细节。像大多数JS对象一样,函数也使用原型(prototype),所以这个 prototype 内部, this 指某个特定的实例。所以简化后的调用实现将是这个样子:

Function.prototype.call = function(thisArg, arg1, arg2, ...) {
  CALL_FN_WITH_SCOPE(this, thisArg, arg1, arg2, ...);
}


这将是在不同了JavaScript引擎要实现的功能,但要记住的重要事情是,this 是被调用的函数。
可以在这里查看规范实现细节: http://es5.github.io/#x15.3.4.4


第二件你要知道的是,你可以在原型上调用函数,只要你使用正确。所以让我们先尝试只调用一个函数。

Function.prototype.call(callbacks[0]);


这是行不通的。你只是调用了一个函数,然后调用this。我们也许可以用这种机制写一些奇怪的代码,我们的目的并没有达到。你真正想要做的是用特定的this绑定Function.prototype.call并调用,昏了吗?

Function.prototype.call.call(callbacks[0]);

这个可以工作,但它也只是相当于callbacks[0].call()调用,所以这种方式也非常不好。我们想要解决的是希望将函数作为一个参数传过去执行,而不是创建额外的调用的函数。


Array.prototype.forEach


理解forEach的工作原理也非常重要。可以看看MDN的说明。 

注* forEach语法: arr.forEach(callback[, thisArg])

其中最重要的一点: 第二个参数将被作为该函数执行时的 this 参数。如果你看过规范,你可能看到过[[Call]] - 实际上,你的JS最终会被转化成这样:

myFunction.call(thisArg, item, index, array);

注* forEach回调时传入的参数为 callback(item, index, array)


将他们组合在一起...


我们知道我们想要调用Function.prototype.call,我们知道我们想直接执行这个函数,所以,接下来的尝试:


callbacks.forEach(Function.prototype.call);

但是,这也不工作。它会抛出一个错误。请记住,this 也需要传,我们实际上已经传了一个undefined,因为根本就没有这个参数,以我们数组的例子来看,最终调用就等价于这样:

Function.prototype.call.call(undefined, callbacks[0]);

实际上我们已经接近了,我们想将Function.prototype.call作为this传进去,那样就等价于Function.prototype.call.call的调用。而这又是通过回调实现的。我们差不多在创建一个小的JavaScript俄罗斯套娃(注*大娃套小娃)。有点疯狂,这是它的工作原理。

callbacks.forEach(Function.prototype.call, Function.prototype.call);

我们的目的最终达到了,但实际上这并不是简单的代码, 还相当慢 。所以我不建议你这样写,但它做了一个有趣的实验。





回复 (2)
微信扫码 立即评论