对何时在事件处理程序中使用"绑定"感到困惑



以下成功打印'foo'。

var obj = {
name: 'foo',
printName: function printName() {
console.log(this.name);
}
};
var printButton= document.getElementById('printIt');
printButton.addEventListener('click', function(){
obj.printName(); 
});

但是,以下内容不会:

printButton.addEventListener('click', obj.printName() );

我知道解决方案...只需使用bind以便我们引用obj对象。即:

printButton.addEventListener('click', obj.printName.bind(obj) );

那为什么我们不需要在第一个示例中使用bind。我不知道为什么在匿名函数中包装obj.printName()函数调用会导致console.log正确引用和打印this,但是当直接在click之后调用时,您需要使用bind

好吧,我评论了一些关于这个问题的好信息,所以我不妨回答一下!

功能一流

好吧,让我们从JavaScript的一些基础知识开始,这些基础知识与其他一些编程语言非常不同:在javascript中,函数是一等公民- 这只是一种奇特的说法,您可以将函数保存到变量中,并且可以将函数传递到其他函数中。

const myFunction = function () { return 'whoa a function'; }
array.map(function () { return x + 1; });

由于这个奇妙的功能,表达式之间存在很大差异:

表达式 1

obj.printName

表达式 2

obj.printName();

在表达式 1 中:未调用函数,因此表达式的值为function

在表达式 2 中:正在调用函数,因此表达式的值就是函数返回的值。在您的情况下,这undefined


addEventListener

该方法addEventListener接受两个参数:

  1. 事件类型的string
  2. 将在事件触发时运行的function

下车,那是什么意思?

当您致电时

// doesn't work
printButton.addEventListener('click', obj.printName() );

您不是将类型function的值传递给addEventListener方法,而是传递undefined

// works
printButton.addEventListener('click', obj.printName.bind(obj) );

然后工作(出于一个原因),因为第二个参数实际上是类型function.


bind做什么?为什么它返回一个函数?

现在我们需要讨论bind实际上做了什么。它与指针*this有关。

*指针,我的意思是指向某个对象的引用标识符

bind是存在于每个函数对象上的方法,它只是将所需对象的this指针绑定到函数

最好通过一个例子来说明这一点:

假设您有一个类Fruit,它有一个方法printName。现在我们知道您可以将方法保存到变量中,让我们尝试一下。在下面的示例中,我们分配了两件事:

  1. 使用bindboundMethod
  2. unboundMethod没有使用bind

class Fruit {
constructor() {
this.name = 'apple';
}

printName() {
console.log(this.name);
}
}
const myFruit = new Fruit();
// take the method `printName`
const boundMethod = myFruit.printName.bind(myFruit);
const unboundMethod = myFruit.printName;
boundMethod(); // works
unboundMethod(); // doesn't work

那么当你不打电话给bind时会发生什么?为什么这行不通?

如果在这种情况下不调用 bind,则可以认为存储在标识符unboundMethod中的函数的值为:

// doens't work
const unboundMethod = function() {
console.log(this.name);
}

其中函数的内容与Fruit类中printName的方法的内容相同。你明白为什么这是一个问题吗?

因为this指针仍然存在,但它要引用的对象不再在范围内。当您尝试调用unboundMethod时,您会收到一个错误,因为它无法在this中找到name

那么当你使用bind时会发生什么?

松散地bind可以被认为是用你传递给bind的对象替换函数的this值。

因此,如果我将:myFruit.printName.bind(myFruit)分配给boundMethod,那么您可以像这样考虑分配:

// works
const boundMethod = function() {
console.log(myFruit.name);
}

其中this替换为myFruit

底线/TL;博士

何时在事件处理程序中使用bind

当您想用另一个对象/指针替换函数内的thises 时,您需要使用Function.prototype.bind如果你的函数从不使用this,那么你不需要使用bind

那么为什么我们不需要在第一个例子中使用绑定呢?

如果你不需要"取方法"(即取function类型的值),那么你也不需要使用bind另一种说法是:如果你直接从对象调用该方法,你不需要bind同一个对象。

在包装函数中,您直接调用对象的方法(如表达式 2 中所示)。因为您在调用该方法时没有"采用方法"(我们在 Fruit 示例中将方法"采用"到变量中),因此无需使用bind

printButton.addEventListener('click', function(){
// directly invoke the function
// no method "taking" here
obj.printName();
});

希望这对:D有所帮助

注意:由于要传递函数,因此您需要在obj.printName()中调用不带括号的printButton.addEventListener('click', obj.printName() );

答案在于 Javascript 中绑定this的方式。在 JS 中,调用函数的方式决定了this的绑定方式。因此,当您提供如下所示的回调函数时:

printButton.addEventListener('click', function(){
obj.printName(); 
});

注意,printName 是通过dot表示法调用的。当this绑定到点之前的对象时,这称为隐式绑定规则,在本例中为obj。显然,在这种情况下,您将获得预期的输出。

但是,当您这样称呼它时:

printButton.addEventListener('click', obj.printName );

请注意,您传递的只是 obj 内部函数的address。因此,在这种情况下,有关obj的信息将丢失。换句话说,回调函数的代码没有可用于设置this的有关 obj 的信息。它所拥有的只是要调用的函数的地址。

希望这有帮助!

编辑: 看看这个我称之为模仿本机绑定bind2crude实现。这只是为了说明本机bind函数如何返回新函数。

Function.prototype.bind2 = function (context) {
var callBackFunction = this;//Store the function to call later
return function () {        //return a new function
callBackFunction.call(context);//Later when called, apply 
//context, this is `obj` passed 
//in bind2()
}
};
function hello() {
alert(this.name);
}
obj = {
name:'ABC'
};
var f = hello.bind2(obj);

f();

注意:函数 f() 在这里是如何hard bound的。 f() 与obj进行了硬绑定this。您现在无法将this更改为obj以外的其他。这是绑定的另一件事,可能会帮助您了解。

最新更新