3

我的问题很简单:我将一个函数传递给稍后要调用的其他函数(示例回调函数),问题是何时、为什么以及最佳实践是什么。

示例:我有xxx()函数,我必须传递它,正如我在下面的 window.onload 事件中向您展示的那样。

最佳做法是什么,为什么?有任何性能方面或者我为什么要选择使用 call 或 bind 来调用这个函数

function xxx(text)
{
    var div = document.createElement("div");
    div.innerHTML = text + " - this: " + this.toString();

    document.body.appendChild(div)
}

function callFunction(func)
{
    func("callFunction");
}

function callUsingCall(func)
{
    func.call(this, ["callUsingCall"]);
}

function callUsingBind(func)
{
    func.call(this, ["callUsingCall"]);
}


window.onload = function(){
    callFunction(xxx);

    callUsingCall(xxx);

    callUsingBind(xxx.bind(document));
}

谢谢,

塞巴斯蒂安·P。

4

2 回答 2

4

我认为没有任何“最佳”做法。

call如果你调用的函数关心什么,你就使用this它。

如果bind要确保只能使用指定的值调用函数,则使用this.

[两者都有一些开销,即至少有一个深度的函数调用/范围]

否则,您只需调用该函数。

简单的:)

于 2012-07-17T13:02:00.073 回答
4

this对象是函数的上下文。就像您为您制造了一台机器,而this对象将是机器工作的地方,例如您的房子。你可以随意移动它。

我们有 4 种方式设置this对象。

调用不是方法的函数:

fn(someArguments)

这样,this对象被设置为 null 或者可能是 window 对象。

将函数作为方法调用:

someObject.fn(someArguments)

在这种情况下,this对象将指向someObject并且它是可变的。

调用函数的callapply方法。

fn.call(anotherObject, someArguments)
someObject.call(anotherObject, someArguments)
someObject.apply(anotherObject, [someArguments])

在这种情况下,this对象将指向someObject此处。调用它时,您正在强制它具有另一个上下文。

绑定一个函数

var fn2 = fn.bind(anotherObject, someArguments)

这将创建另一个绑定到this我们给它的对象的函数(anotherObject)。不管你怎么称呼它,this对象都是一样的。

用例

现在你可以做一些棘手的事情知道这一点。我们之所以在这里拥有它(我认为它首先来自 C++)是因为对象的方法需要访问它们的父对象。this对象提供访问。

var coolObject = {
  points : ['People are amazing'],
  addPoint : function (p) { this.points.push(p) }
}

因此,如果您执行以下操作,它将不起作用:

var addPoint = coolObject.addPoint;
addPoint('This will result in an error');

将抛出错误,因为 this 对象coolObject不再是我们的对象并且没有 points 属性。所以在这样的时候,你可以这样:

var addPoint = coolObject.addPoint;
addPoint.call({points : []}, 'This is pointless');

这是没有意义的,但该功能会起作用,即使this对象不是它应该是的。

var anotherCoolObject = {
  points : ['Im a thief!'],
  addPoint : coolObject.addPoint
}
anotherCoolObject.addPoint('THIS IS CALL STEALING');

如果您这样调用它,该函数仍然可以工作,因为该this对象将指向另一个具有该points属性的CoolObject。

我见过的最流行的用例是对 arguments 对象进行切片:

function returnHalf() {
  return [].slice.call(arguments, 0, arguments.length / 2);
}

returnHalf('Half', 'is', 'not', 'awesome');
// >> [Half', 'is']

所以你看,arguments 对象不是一个 instanceof 数组。如果我们这样做,arguments.slice(...)那么您将被编译器杀死。但是这里我们在 arguments 对象上使用数组的方法,因为它像数组。

有时你不想改变你的函数上下文或者你想添加你自己的参数,你使用绑定。

例如,当您使用 jquery 为事件添加侦听器时,当 jquery 调用您的函数时,this 对象将是元素。但有时你想做一些棘手的事情并改变它:

var myElement = {
  init : function () {
    $(this.element).click(this.listener.bind(this));
  },
  view : "<li>${Name}</li>",
  name : 'ed',
  element : $('#myelement'),
  listener : function () {
    this.element.append($.tmpl( this.view, this ));
  }
}

myElement.init();

所以在这里,你将它绑定到 myElement,这样你就可以访问对象属性来呈现视图。另一个例子如下:

for (var i = 0; i < 10; i++) {
  setTimeout(function () {console.log(i)}, 10)
}

// All of them will be 10.

for (var i = 0; i < 10; i++) {
  setTimeout((function () {console.log(this.i)}).bind({ i : i }, 10)
}

如果您将异步函数调用放在循环中,则在调用回调时,循环结束并且计数器已到达末尾,您可以使用 bind 将当前计数器干净地绑定到您的回调。

我经常使用的另一个很好的用例是在将带参数的函数传递给async模块时,而不创建闭包。

async.parallel({
  writeFile : function (cb) {
    fs.writeFile('lolz.txt', someData, cb);
  }, 
  writeFile2 : function (cb) {
    fs.writeFile('lolz2.txt', someData, cb);
  }
}, function (err){ 
    console.log('finished')
});

async.parallel({
  writeFile : fs.writeFile.bind(fs, 'lolz.txt', someData),
  writeFile2 : fs.writeFile.bind(fs, 'lol2z.txt', someData),
}, function (err){ 
    console.log('finished')
});

这两个实现是相同的。

表现

只需检查这些:

http://jsperf.com/bind-vs-call2

http://jsperf.com/js-bind-vs-closure/2

http://jsperf.com/call-vs-closure-to-pass-scope/10

bind与其他类型的调用相比,具有很大的性能开销,但请确保您不会因未成熟的优化而牺牲可维护性的性能。

你也可以看看这篇文章。

于 2012-07-17T13:18:44.757 回答