addEventListener回调函数中的this关键字

16

我看了一些关于这个主题的答案,但我不确定我理解addEventListenerthis关键字的工作方式。

const button = document.querySelector('button');

function foo() { console.log(this) }

button.addEventListener('click', foo);

foo 是在 addEventListener 中定义的普通函数,它不是 button 对象的方法。当调用 foo 时,应该在全局对象的上下文中执行,因此 this 应该等于 window 而不是 button

看起来类似于这个例子的情况:

const obj = {
  method: function (cb) {
    console.log('method', this); // `this` === `obj`

    return cb();
  }
};

obj.method(function() {
  console.log('cb', this); // `this` === `window`
});

其中obj可以被视为buttonmethod可以是addEventListener,而cb则是addEventListener内的回调函数。

我知道我可以使用bind来改变this的上下文,但我想更深入地了解它为什么能够这样工作。

为什么addEventListener回调函数中的this会在当前元素的上下文中调用,而不是全局对象?


https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_event_listener_callback#The_value_of_this_within_the_handler - Jay Harris
1
"fooaddEventListener内的一个普通函数,它不是button对象上的方法。"- 这并不重要。如果你调用foo(),那么是的,你将不会得到一个this值(或者在松散模式下是window),但当你使用addEventListener时,它将在按钮上调用该函数。就像在你的obj示例中,method会执行cb.call(obj)一样。 - Bergi
还要考虑旧样式:button.onclick = foo。在那里,处理程序实际上是一种方法 :-) - Bergi
这个问题在MDN文档中有很好的解释。(@JayHarris的更新链接) - user11484628
5个回答

22
如果我们使用了使用函数关键字定义的函数作为事件处理程序,那么该事件处理程序函数会在绑定事件的元素上下文中执行。

如果我们使用了使用函数关键字定义的函数作为事件处理程序,那么该事件处理程序函数会在绑定事件的元素上下文中执行。

button.addEventListener('click', foo);
所以,在这种情况下,foo内部的this值将是button元素。
如果我们使用箭头函数而不是它们,则this值将为窗口对象。
原因是箭头函数中的this与创建箭头函数的上下文具有相同的值。
button.addEventListener('click', () => { console.log(this) // window } );

更多关于词法this的信息 什么是词法'this'?


感谢您提到箭头函数以及它们在作用域方面的差异。 - Prid
你是最棒的。感谢您提供的词汇参考。 - TGEE

3
虽然我们知道事件侦听器是在'this'设置为事件目标的情况下执行的,但在您找到的EventTarget链接中,EventTarget.prototype.dispatchEvent方法内的以下代码行将回答您如何实现它的问题。
for (var i = 0, l = stack.length; i < l; i++) {
    stack[i].call(this, event);
}

“stack”数组包含回调函数,并通过传递事件目标实例(this)和事件作为参数来使用.call进行调用。


因此,当我们使用 addEventListener 注册事件时,会创建一个名为 EventTarget.listener 的对象,其中包含所有事件。每个事件都是我们回调函数的数组(或堆栈)。当特定事件发生时,浏览器会调用 EventTarget.prototype.dispatchEvent() 并执行堆栈中的所有回调,使用 call()this 的上下文更改为绑定事件的元素。 - pldg
如果有人想知道是否可以安全地将 this 替换为 event.targetevent.currentTarget,那么绝对是可以的,正如 MDN 文档 中所解释的那样。 - user11484628
这是有道理的。在函数调用中,“this”(不包括方法调用、箭头函数调用和构造函数调用)将返回全局对象(浏览器中的window),或在“严格模式”下返回未定义。必须有某种方式来改变绑定上下文,以使“this”指向使用addEventListener时的元素。 - tuq

2
作为回调函数的一种类型,事件处理程序作为参数传递给函数。让我们创建一个简单的函数,将一个回调函数作为参数传递给它,并看看它实际上是如何工作的。
    function testCallBack(fn){
       console.log('inside testCallBack');
       fn('Hello I am a callBack')
    }

    testCallBack(foo);

    function foo(param){
      console.log(param);
    }

// Outputs: 
inside testCallBack
Hello I am a callBack

JavaScript中的每个作用域都有一个this对象,表示函数的调用对象。
这就是为什么addEventListener回调函数中的this被调用在当前元素的上下文而不是全局对象上的原因。 请参考以下代码以更清晰地理解:

   function sayNameForAll() {
        console.log(this.name);
    }

    var person1 = {
        name: "Rajat",
        sayName: sayNameForAll
    };

    var person2 = {
        name: "pldg",
        sayName: sayNameForAll
    };

    var name = "Sidd";

    person1.sayName();      // outputs "Rajat" here calling object is person1, so this represents person 1
    person2.sayName();      // outputs "pldg"

    sayNameForAll();        // outputs "Sidd"

当你调用 button.addEventListener('click',foo) 时,调用对象就是 button

2

事件监听器的执行会将this设置为触发事件的对象,因为一个监听器可以监听多个对象的事件。

然而,如果调用表达式不包含通过.的成员访问,则常规函数调用不会设置this。在这些情况下,在未激活"use strict"的情况下,this将变为全局上下文,在浏览器中为window

如果您希望cbthisobj,则可以使用cb.apply(this)替换cb(),这将把cbthis设置为封闭函数的this

最后警告:这些this机制仅适用于使用function关键字(以及类似机制)定义的函数。箭头函数内部的this在定义时被锁定为封闭作用域的this


0

就像你可以使用bindcallthis设置为任何你想要的值一样,浏览器API也可以调用任何值设置为this的函数。它被用在很多奇怪的方式中,而且不是非常一致。除了类和方法之外,this更像是一个函数的秘密额外参数。在这种情况下,你可以通过从event.target获取按钮元素来避免需要它。


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