Javascript函数调用:常规调用vs调用与绑定调用

时间:2012-07-17 12:59:07

标签: javascript html

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

样品: 我有 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。

2 个答案:

答案 0 :(得分:4)

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

如果您调用的函数关注的是call,则使用this

如果您希望确保仅使用指定值bind 调用该函数,则使用this

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

否则你只需要调用该函数。

简单:)

答案 1 :(得分:3)

this对象是函数的上下文。这就像你为自己制作一台机器,而this对象就是机器工作的地方,就像你的房子一样。你可以随意移动它。

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

调用非方法的函数:

fn(someArguments)

这样this对象设置为null或者可能是窗口对象。

将该功能称为方法:

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

将抛出错误,因为此对象不再是我们的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。

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

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

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

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

有时你不希望你的函数上下文被改变,或者你想要添加自己的参数,你使用bind。

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

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文章。