JavaScript函数调用:常规调用 vs Call vs Bind Call

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));
}

谢谢您,Sebastian P.

你正在将一个数组作为第二个参数传递 - 你是指这个吗? - Alnitak
2个回答

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属性的对象。

我见过的最常见的用例是对参数对象进行切片:

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

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

所以你看,arguments对象不是数组的一个实例。如果我们执行arguments.slice(...),那么编译器会报错。但是这里我们使用了数组的方法来处理arguments对象,因为它类似于数组。

有时候你不想改变函数上下文或者想要添加自己的参数,那么你可以使用bind。

例如,当你使用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 相比其他调用类型有较大的性能开销,但要确保在进行过早优化时不会牺牲维护性能。

此外,您可以查看this文章。


4
我不认为有任何“最佳实践”。
如果您正在调用的函数关心 this 是什么,那么使用 call
如果您想确保该函数只能使用指定的 this 值进行调用,则使用 bind
[二者都存在一些开销,即至少一个函数调用/作用域深度]
否则,只需调用函数即可。
简单明了 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接