链接.bind()调用JavaScript。出乎意料的结果?

时间:2014-10-24 09:48:43

标签: javascript

来自MDN

  

bind()方法创建一个新函数,在调用时,将其this关键字设置为提供的值

我很高兴看到它在这个例子中起作用:

(function () {
   console.log(this);
}).bind({foo:"bar"})();

记录Object { foo="bar"}

但是,如果我链接另一个绑定调用,甚至是"调用"打电话,我仍然使用"这个"分配给传递给第一个绑定的对象。例子:

(function () {
   console.log(this);
}).bind({foo:"bar"}).bind({oof:"rab"})();

&安培;

(function () {
   console.log(this);
}).bind({foo:"bar"}).call({oof:"rab"});

记录Object { foo="bar"}而不是我期望的结果:Object { oof="rab"}

无论我链接多少个绑定调用,只有第一个看起来有效。

为什么吗

这可能会有所帮助。我刚刚发现jQuery的版本行为方式相同! :o

jQuery.proxy(
  jQuery.proxy(function() {
      console.log(this);
  },{foo:"bar"})
,{oof:"rab"})();

记录Object { foo="bar"}

6 个答案:

答案 0 :(得分:43)

很容易将bind视为修改函数以使用新的this。在这个(不正确的)解释中,人们认为bind为函数添加某种魔术标志,告诉它下次调用它时使用不同的this。如果是这种情况,则应该可以“覆盖”并更改魔术标志。然后人们会问,任意限制这种能力的原因是什么?

但实际上,它是如何运作的。 bind创建并返回 new 函数,当调用时,调用具有特定this的第一个函数。创建函数时,此新创建的函数(使用指定的this调用原始函数)的行为中刻录。它不能被更改,因为函数返回的任何其他函数的内部结构都可以在事后更改。

查看bind

的真正简单实现可能会有所帮助
// NOT the real bind; just an example
Function.prototype.bind = function(ctxt) {
    var fn = this;
    return function bound_fn() {
        return fn.apply(ctxt, arguments);
    };
}

my_bound_fn = original_fn.bind(obj);

如您所见,bound_fn中没有任何地方,bind返回的函数,它是指引用绑定函数的this。它被忽略了,所以

my_bound_fn.call(999, arg)            // 999 is ignored

obj = { fn: function () { console.log(this); } };
obj.fn = obj.fn.bind(other_obj);
obj.fn();                            // outputs other_obj; obj is ignored

所以我可以再次绑定从bind“返回的函数”,但这是重新绑定原始函数;它只是绑定外部函数,它对内部函数没有影响,因为它已经设置为调用底层函数,并将上下文(this值)传递给bind。我可以一次又一次地绑定但是我最终做的就是创建更多外部函数,这些函数可能绑定到某些东西但最终仍然调用从第一个bind返回的最内层函数。

因此,说“bind”不能被覆盖有点误导。

如果我想“重新绑定”一个函数,那么我可以对原始函数进行新的绑定。所以如果我绑定它一次:

function orig() { }
my_bound_fn = orig.bind(my_obj);

然后我想安排我的原始函数与其他this一起调用,然后我不重新绑定绑定函数:

my_bound_fn = my_bound_fn.bind(my_other_obj);     // No effect

相反,我只是创建一个绑定到原始函数的新函数:

my_other_bound_fn = orig.bind(my_other_obj);

答案 1 :(得分:12)

我在MDN上找到了这一行:

  

bind()函数用。创建一个新函数(一个绑定函数)   相同的函数体(ECMAScript 5术语中的内部调用属性)as   它被调用的函数(绑定函数的目标   函数)将此值绑定到bind()的第一个参数,   无法覆盖。

所以也许它一旦确定就无法覆盖。

答案 2 :(得分:5)

torazaburo的优秀答案给了我一个想法。有可能是一个类似绑定的函数,而不是将接收器(this)烘焙到闭包内的调用中,将它作为属性放在函数对象上,然后在调用时使用它。这将允许重新绑定在调用之前更新属性,从而有效地提供您期望的重新绑定结果。

例如,



function original_fn() {
    document.writeln(JSON.stringify(this));
}

Function.prototype.rebind = function(obj) {
    var fn = this;
    var bound = function func() {
        fn.call(func.receiver, arguments);
    };
    bound.receiver = obj;
    bound.rebind = function(obj) {
        this.receiver = obj;
        return this;
    };
    return bound;
}

var bound_fn = original_fn.rebind({foo: 'bar'});

bound_fn();

var rebound_fn = bound_fn.rebind({fred: 'barney'});

rebound_fn();




或者,node.js的输出如下。

{ foo: 'bar' }
{ fred: 'barney' }

请注意,对rebind的第一次调用是调用添加到Function.prototype的那个,因为它是在普通函数original_fn上调用的,但第二次调用是调用rebind 1}}作为属性添加到绑定函数(并且任何后续调用也将调用此函数)。 rebind只更新receiver并返回相同的函数对象。

通过将命名的函数表达式设置为绑定函数,可以访问receiver属性。

答案 3 :(得分:3)

好的,这主要是猜测,但我会尝试通过它。

ECMAScript规范(当前已关闭)声明bind函数的以下内容(强调我自己的):

  

15.3.4.5 Function.prototype.bind(thisArg [,arg1 [,arg2,...]])

     

bind方法接受一个或多个参数thisArg和(可选)   arg1,arg2等,并通过执行返回一个新的函数对象   以下步骤:

     
      
  1. 让Target为此值。
  2.   
  3. 如果IsCallable(Target)为false,则抛出TypeError异常。
  4.   
  5. 设A是按此顺序在thisArg(arg1,arg2等)之后提供的所有参数值的新(可能为空)内部列表。
  6.   
  7. 设F为新的原生ECMAScript对象
  8.   
  9. 按照8.12中的规定设置F的[[Get]]除外的所有内部方法。
  10.   
  11. 按照15.3.5.4中的规定设置F的[[Get]]内部属性。
  12.   
  13. 将F的[[TargetFunction]]内部属性设置为Target。
  14.   
  15. 将F的[[BoundThis]]内部属性设置为thisArg的值。
  16.   
  17. 将F的[[BoundArgs]]内部属性设置为A.
  18.   
  19. 将F的[[Class]]内部属性设置为"功能"。
  20.   
  21. 将F的[[Prototype]]内部属性设置为标准内置函数原型对象,如中所述   15.3.3.1。
  22.   
  23. 按照15.3.4.5.1。
  24. 中的说明设置F的[[Call]]内部属性   
  25. 按照15.3.4.5.2。
  26. 中的说明设置F的[[Construct]]内部属性   
  27. 按照15.3.4.5.3中的说明设置F的[[HasInstance]]内部属性。
  28.   
  29. 如果Target的[[Class]]内部属性为" Function",则a。令L为Target的长度属性减去A的长度。   将F的length属性设置为0或L,以两者为准   大。
  30.   
  31. 否则将F的长度属性设置为0。
  32.   
  33. 将F长度属性的属性设置为15.3.5.1中指定的值。
  34.   
  35. 将F的[[Extensible]]内部属性设置为true。
  36.   
  37. 让thrower成为[[ThrowTypeError]]函数Object(13.2.3)。
  38.   
  39. 使用参数" caller",PropertyDescriptor {[[Get]]:thrower,[[Set]]:thrower,调用F的[[DefineOwnProperty]]内部方法   [[Enumerable]]:false,[[Configurable]]:false},false。
  40.   
  41. 使用参数" arguments",PropertyDescriptor {[[Get]]:thrower,[[Set]]:thrower,调用F的[[DefineOwnProperty]]内部方法   [[Enumerable]]:false,[[Configurable]]:false},false。
  42.   
  43. 返回F
  44.   

当您在使用function创建的对象上调用bind时:

  

15.3.4.5.1 [[Call]]

     

当使用bind函数创建的函数对象F的[[Call]]内部方法被调用时   这个值和一个参数列表ExtraArgs,以下步骤是   采取:

     
      
  1. 让boundArgs为F [[BoundArgs]]内部属性的值。
  2.   
  3. 设为bound这是F [[BoundThis]]内部属性的值。
  4.   
  5. 让target为F的[[TargetFunction]]内部属性的值。
  6.   
  7. 让args成为一个新列表,其中包含与列表boundArgs相同的值,顺序相同,后跟与列表相同的值   ExtraArgs的顺序相同。
  8.   
  9. 返回调用[[Call]]目标的内部方法的结果,提供boundThis作为此值并提供args作为   参数
  10.   

Call指定如何调用每个函数。有点像JavaScript call

someFunction.[[call]](thisValue, arguments) {

}

但是,当在绑定函数上使用[[call]]时,thisValue将被覆盖[[BoundThis]]的值。在第二次调用bind的情况下,您尝试覆盖第一个的thisValue[[BoundThis]]替换,基本上不会对{{1}的值产生任何影响}:

thisValue

您会注意到,如果您尝试使用boundFunction.[[call]](thisValue, arguments) { thisValue = boundFunction.[[BoundThis]]; } call,那么它们也将无效,因为apply属性的覆盖尝试将在thisValue {1}}调用下一个函数。

答案 4 :(得分:0)

这些bind()如何工作的简化示例更好地解释了它。

以下是函数绑定的结果:

function bound_function() {

    function original_function() {
        console.log(self);
    }

    var self = 1;
    original_function();
}

bound_function()

如果我们将原始函数包装两次会发生什么:

function bound_function2() {

    function bound_function1() {

        function original_function() {
            console.log(self);
        }

        var self = 1;
        original_function();
    }

    var self = 2;
    bound_function1();
}

bound_function2()

答案 5 :(得分:0)

我认为考虑它的方法是:当你第一次调用bind()时,'这个'调用bind()返回的函数内部是FIXED,给定的值。这是可能的,因为它之前没有被修复,它是未绑定的。但是一旦修复它就无法修复到任何其他东西,因为它不再是不固定的,它不再是变量"。

理论上可能存在相反的操作来绑定被叫" unbind"您可以称之为:

myFunk.bind(something)
      .unbind(); // -> has same behavior as original myFunk

名称" bind"表示(伪)变量' this'是绑定的东西,它不是简单地分配一个值,然后可以一次又一次地分配。

什么东西是"绑定"它有一个值,并且该值不能被替换 - 因为它是"绑定"。所以你需要一个unbind()操作来实现这一点。但是因为你可以在某个地方拥有原始功能 没有必要"解开"真。

我同意这种行为可能是令人惊讶和意外的,因此可能容易出错,因为如果你得到一个函数作为参数,似乎没有办法判断你的bind()是否有任何影响。

然而,如果你不太了解这样的函数论证,那么你也不可能知道你可以绑定它的价值是什么,而不会打破它对这个'这个'在里面。

所以bind()操作本身就很危险。重新绑定将是双重危险。所以你最好尽量避免这样做。