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

IT技术 javascript
2021-01-17 17:11:37

来自MDN

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

我可以很高兴地看到它在这个例子中工作:

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

哪个日志Object { foo="bar"}

但是,如果我链接另一个绑定调用,甚至是“调用”调用,我仍然会调用函数,并将“this”分配给传递给第一个绑定的对象。例子:

(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个回答

人们很容易想到bind以某种方式修改函数以使用新的this. 在这种(不正确的)解释中,人们认为bind向函数添加了某种魔法标志,告诉它在this下次调用时使用不同的方法。如果是这种情况,那么应该可以“覆盖”并更改魔术标志。那么有人会问,随意限制这样做的能力的原因是什么?

但事实上,它不是这样工作的。bind创建并返回一个函数,该函数在调用时调用具有特定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“again”返回的函数,但这不是重新绑定原始函数;它只是绑定外部函数,对内部函数没有影响,因为它已经设置为使用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);
超级棒!为什么文档中没有这样的解释?
2021-04-05 17:11:37

我在MDN上找到了这一行

bind() 函数创建一个新函数(绑定函数),其函数体(ECMAScript 5 术语中的内部调用属性)与它被调用的函数(绑定函数的目标函数)具有相同的函数体(绑定函数的目标函数),并将 this 值绑定到bind() 的第一个参数, 不能被覆盖。

所以也许它一旦设置就真的不能被覆盖。

问为什么不能覆盖它会不会太厚颜无耻?
2021-04-06 17:11:37
这是我不知道的事情,我希望有聪明的人能对此有所了解
2021-04-11 17:11:37

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作为属性添加到绑定函数的函数(并且任何后续调用都将调用此函数,同样)。rebind只是更新receiver并返回相同的函数对象。

可以receiver通过将其设为命名函数表达式来访问绑定函数中属性

呃,document.writeln(JSON.stringify(boo))
2021-03-17 17:11:37
@torazaburo 不幸的是,document.writeln不能很好地格式化对象。替换console.logdocument.writeln生产[Object object]作为输出。
2021-04-09 17:11:37

好吧,这主要是猜测,但我会尝试通过它来推理。

ECMAScript 规范(目前已关闭)对该bind函数进行了以下说明(强调我自己的):

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

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

  1. 让 Target 成为这个值。
  2. 如果 IsCallable(Target) 为 false,则抛出 TypeError 异常。
  3. 令 A 是一个新的(可能为空的)内部列表,其中包含在 thisArg(arg1、arg2 等)之后提供的所有参数值,按顺序排列。
  4. 让 F 成为一个新的原生 ECMAScript 对象
  5. 按照 8.12 中的规定设置 F 的除 [[Get]] 之外的所有内部方法。
  6. 按照 15.3.5.4 中的规定设置 F 的 [[Get]] 内部属性。
  7. 将 F 的 [[TargetFunction]] 内部属性设置为 Target。
  8. 将 F 的 [[BoundThis]] 内部属性设置为 thisArg 的值。
  9. 将 F 的 [[BoundArgs]] 内部属性设置为 A。
  10. 将 F 的 [[Class]] 内部属性设置为“Function”。
  11. 将 F 的 [[Prototype]] 内部属性设置为 15.3.3.1 中指定的标准内置函数原型对象。
  12. 按照 15.3.4.5.1 中的描述设置 F 的 [[Call]] 内部属性。
  13. 设置 F 的 [[Construct]] 内部属性,如 15.3.4.5.2 中所述。
  14. 设置 F 的 [[HasInstance]] 内部属性,如 15.3.4.5.3 中所述。
  15. 如果 Target 的 [[Class]] 内部属性是“Function”,则 a. 令 L 为 Target 的长度属性减去 A 的长度。 b. 将 F 的长度属性设置为 0 或 L,以较大者为准。
  16. 否则将 F 的长度属性设置为 0。
  17. 将 F 的 length 自身属性的属性设置为 15.3.5.1 中指定的值。
  18. 将 F 的 [[Extensible]] 内部属性设置为 true。
  19. 让 thrower 成为 [[ThrowTypeError]] 函数对象 (13.2.3)。
  20. 调用 F 的 [[DefineOwnProperty]] 内部方法,参数为“caller”,PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false },和假的。
  21. 调用 F 的 [[DefineOwnProperty]] 内部方法,参数为“arguments”,PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false },和假的。
  22. 返回 F

当您function在使用bind以下方法创建的对象上调用 a

15.3.4.5.1 [[调用]]

当使用此值和参数列表 ExtraArgs 调用使用绑定函数创建的函数对象 F 的 [[Call]] 内部方法时,将执行以下步骤:

  1. 让 boundArgs 是 F 的 [[BoundArgs]] 内部属性的值。
  2. 让 boundThis 成为 F 的 [[BoundThis]] 内部属性的值。
  3. 令 target 为 F 的 [[TargetFunction]] 内部属性的值。
  4. 令 args 是一个新列表,其中包含与列表 boundArgs 相同的值,后跟与列表 ExtraArgs 相同的值。
  5. 返回调用target的[[Call]]内部方法的结果,提供boundThis作为this值,提供args作为参数

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

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

}

然而,当[[call]]用于绑定函数时,thisValue被 的值覆盖[[BoundThis]]bind第二次调用的情况下thisValue,您尝试覆盖第一个的 被替换为[[BoundThis]],基本上不会对 的值产生任何影响thisValue

boundFunction.[[call]](thisValue, arguments) {
  thisValue = boundFunction.[[BoundThis]];
}

您会注意到,如果您尝试使用callorapply然后它们也将无效,因为它们覆盖该thisValue属性的尝试将在[[call]]调用下一个函数时被逆转

这些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()