'this' 在回调中绑定为闭包,如果以特定顺序调用,则通过 apply 变为未定义
'this' in callback bound as closure via apply becoming undefined if called in specific order
我有一个 JS 函数,我尝试使用 this
应该是点击按钮触发该函数:
function myAction( greeting ) { console.log( greeting + ", the button is :" + this ); this.remove(); }
然后我的页面上有几个按钮,比方说两个(即使页面上只有两个按钮也会发生这种情况;我已经测试过了):
<button id="button-one" class="click-me">Click Me</button>
<button id="button-two" class="click-me">Click Me</button>
根据我的经验,当我想使用需要使用相关元素作为 this
加上其他函数参数的事件侦听器回调时;如果我们有几个按钮共享例如相同的 class;达到此目的的最佳方法是将该回调绑定为闭包(将迭代元素捕获为 this
+ apply(传递相关的 this
元素):
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", (
function(i) {
return function() { myAction.apply( myButtons[i], ["hello"] ); };
}(i))
);
}
我对这种方法从来没有任何问题;它总是有效。这实际上是错误的方法吗?
因为,我现在面临的是,当我点击#button-one
,然后点击#button-two
时,this
实际上在严格模式下变成了undefined并且window 在非严格模式下;在第二次点击。当我反过来做时,this
总是坚持成为相应点击的按钮。它似乎以某种方式更新了 myButtons
集合,这样当第一个按钮通过第一次调用被删除时, 1
索引在集合中变得不可用,因为只剩下一个按钮;另一个被删除。至少那是我的理论。但是我以前从未在类似的代码案例中遇到过这个问题?那我该如何编码我想要发生的事情呢?
编辑
感谢大家;并特别感谢@Nenad Vracar,因为他的解决方案导致所需的最少编辑,只需更改即可完美运行:
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", (
function(i) {
return function() { myAction.apply( myButtons[i], ["hello"] ); };
}(i))
);
}
收件人:
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", function() {
myAction.apply( this, ["hello"] );
});
}
简单的智能解决方案,我简直是见树不见林!再次感谢!
注意。事件委托在这里并不是一个真正的选择,因为页面的 DOM 结构/需要附件的元素有些复杂,我通常更喜欢将函数附加到需要它的元素。另外,我宁愿不将函数代码更改为闭包本身,也不要更改函数代码本身。因此,我决定使用此解决方案的原因。
您可以在使用 querySelectorAll
的地方使用稍微不同的方法,然后您可以在其上使用 forEach
循环。在点击事件处理程序中,您不需要额外的闭包,myAction
中的 this
将是点击事件处理程序中的 this
,因为您将其作为 thisArg
传递apply
.
的参数
function myAction(greeting) {
console.log(greeting + ", the button is :" + this.textContent);
this.remove();
}
document
.querySelectorAll(".click-me")
.forEach(function(btn) {
btn.addEventListener('click', function() {
myAction.apply(this, ['Hello'])
})
})
<button id="button-one" class="click-me">One</button>
<button id="button-two" class="click-me">Two</button>
您现在的方法似乎很复杂。这里有两个简化流程的建议。
高阶函数事件处理程序
如果你re-definemyAction
作为一个函数即returns事件监听器,你可以直接传递参数。因此,分配处理程序时无需将其包装在另一个函数中:
function myAction( greeting ) { //take input
return function() { //return the function to act as event listener
console.log( greeting + ", the button is :" + this ); this.remove();
}
}
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
//pass a variable to be used in the listener
myButtons[i].addEventListener( "click", myAction( "hello" ) );
}
<button id="button-one" class="click-me">Click Me</button>
<button id="button-two" class="click-me">Click Me</button>
使用事件委托
这是首选方法。您可以将单个事件侦听器添加到 DOM 树中的共同祖先,而不是向 每个 元素添加事件侦听器。那么你只需要一个功能。
值得注意的是,事件监听器会接收所有children的事件,因此需要过滤掉不相关的。你可以制作一个简单的包装器来检查目标目标是否正确,然后执行装饰处理程序:
function myAction( greeting ) {
return function(event) { //take an event
var el = event.target; //extract the element from it
console.log( greeting + ", the button is :" + el ); el.remove(); //use the element
}
}
/*
* Convert a handler to a delegate to only work on a sub-section of elements
* @param {string} filterSelector - the handler will only be triggered
* for elements matching this selector
* @param {Function} handler - the handler to wrap
*
* @return {Function} a function to serve as a delegated event handler
*/
function delegate( filterSelector, handler ) {
return function ( event ) {
//only fire for explicitly desired targets
if (event.target.matches( filterSelector ) ) {
//forward call to the handler
handler.apply( this, arguments );
}
}
}
var delegatedHandler = delegate( ".click-me", myAction( "hello" ) );
document.body //attach higher up in the DOM
.addEventListener( "click", delegatedHandler );
<button id="button-one" class="click-me">Click Me</button>
<button id="button-one" class="dummy">I should stay</button>
<button id="button-two" class="click-me">Click Me</button>
如果您使用的是旧版浏览器,请参阅 Element#matches()
了解潜在的 polyfill。
另请参阅:
What is DOM Event delegation?
Event binding on dynamically created elements?
我有一个 JS 函数,我尝试使用 this
应该是点击按钮触发该函数:
function myAction( greeting ) { console.log( greeting + ", the button is :" + this ); this.remove(); }
然后我的页面上有几个按钮,比方说两个(即使页面上只有两个按钮也会发生这种情况;我已经测试过了):
<button id="button-one" class="click-me">Click Me</button>
<button id="button-two" class="click-me">Click Me</button>
根据我的经验,当我想使用需要使用相关元素作为 this
加上其他函数参数的事件侦听器回调时;如果我们有几个按钮共享例如相同的 class;达到此目的的最佳方法是将该回调绑定为闭包(将迭代元素捕获为 this
+ apply(传递相关的 this
元素):
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", (
function(i) {
return function() { myAction.apply( myButtons[i], ["hello"] ); };
}(i))
);
}
我对这种方法从来没有任何问题;它总是有效。这实际上是错误的方法吗?
因为,我现在面临的是,当我点击#button-one
,然后点击#button-two
时,this
实际上在严格模式下变成了undefined并且window 在非严格模式下;在第二次点击。当我反过来做时,this
总是坚持成为相应点击的按钮。它似乎以某种方式更新了 myButtons
集合,这样当第一个按钮通过第一次调用被删除时, 1
索引在集合中变得不可用,因为只剩下一个按钮;另一个被删除。至少那是我的理论。但是我以前从未在类似的代码案例中遇到过这个问题?那我该如何编码我想要发生的事情呢?
编辑
感谢大家;并特别感谢@Nenad Vracar,因为他的解决方案导致所需的最少编辑,只需更改即可完美运行:
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", (
function(i) {
return function() { myAction.apply( myButtons[i], ["hello"] ); };
}(i))
);
}
收件人:
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
myButtons[i].addEventListener( "click", function() {
myAction.apply( this, ["hello"] );
});
}
简单的智能解决方案,我简直是见树不见林!再次感谢!
注意。事件委托在这里并不是一个真正的选择,因为页面的 DOM 结构/需要附件的元素有些复杂,我通常更喜欢将函数附加到需要它的元素。另外,我宁愿不将函数代码更改为闭包本身,也不要更改函数代码本身。因此,我决定使用此解决方案的原因。
您可以在使用 querySelectorAll
的地方使用稍微不同的方法,然后您可以在其上使用 forEach
循环。在点击事件处理程序中,您不需要额外的闭包,myAction
中的 this
将是点击事件处理程序中的 this
,因为您将其作为 thisArg
传递apply
.
function myAction(greeting) {
console.log(greeting + ", the button is :" + this.textContent);
this.remove();
}
document
.querySelectorAll(".click-me")
.forEach(function(btn) {
btn.addEventListener('click', function() {
myAction.apply(this, ['Hello'])
})
})
<button id="button-one" class="click-me">One</button>
<button id="button-two" class="click-me">Two</button>
您现在的方法似乎很复杂。这里有两个简化流程的建议。
高阶函数事件处理程序
如果你re-definemyAction
作为一个函数即returns事件监听器,你可以直接传递参数。因此,分配处理程序时无需将其包装在另一个函数中:
function myAction( greeting ) { //take input
return function() { //return the function to act as event listener
console.log( greeting + ", the button is :" + this ); this.remove();
}
}
var myButtons = document.getElementsByClassName( "click-me" );
var amountOfButtons = myButtons.length;
for ( var i = 0; i < amountOfButtons; i++ ) {
//pass a variable to be used in the listener
myButtons[i].addEventListener( "click", myAction( "hello" ) );
}
<button id="button-one" class="click-me">Click Me</button>
<button id="button-two" class="click-me">Click Me</button>
使用事件委托
这是首选方法。您可以将单个事件侦听器添加到 DOM 树中的共同祖先,而不是向 每个 元素添加事件侦听器。那么你只需要一个功能。
值得注意的是,事件监听器会接收所有children的事件,因此需要过滤掉不相关的。你可以制作一个简单的包装器来检查目标目标是否正确,然后执行装饰处理程序:
function myAction( greeting ) {
return function(event) { //take an event
var el = event.target; //extract the element from it
console.log( greeting + ", the button is :" + el ); el.remove(); //use the element
}
}
/*
* Convert a handler to a delegate to only work on a sub-section of elements
* @param {string} filterSelector - the handler will only be triggered
* for elements matching this selector
* @param {Function} handler - the handler to wrap
*
* @return {Function} a function to serve as a delegated event handler
*/
function delegate( filterSelector, handler ) {
return function ( event ) {
//only fire for explicitly desired targets
if (event.target.matches( filterSelector ) ) {
//forward call to the handler
handler.apply( this, arguments );
}
}
}
var delegatedHandler = delegate( ".click-me", myAction( "hello" ) );
document.body //attach higher up in the DOM
.addEventListener( "click", delegatedHandler );
<button id="button-one" class="click-me">Click Me</button>
<button id="button-one" class="dummy">I should stay</button>
<button id="button-two" class="click-me">Click Me</button>
如果您使用的是旧版浏览器,请参阅 Element#matches()
了解潜在的 polyfill。
另请参阅:
What is DOM Event delegation?
Event binding on dynamically created elements?