内联监听器没有任何好处,相反,这是一种向 HTML 元素添加事件监听器的非常有缺陷的方法。
执行上的差异
#1 this
属性代码内的值通过 JavaScript 绑定到元素with。在称为函数(或任何全局变量)的内联代码中,首先从元素中搜索。如果未找到(通常是这种情况),内联侦听器将从元素的原型链中搜索该函数。如果没有找到匹配的属性名称,则搜索到达window
,并运行被调用的全局函数。但是,如果函数名称与查找路径上的任何属性名称冲突,则会引发错误,或者执行意外的函数。
内联侦听器如何查找的示例action
包装形式的属性,只需点击输入:
function action () {
console.log('Not a function!?');
}
<form action="" method="post">
<input onclick="console.log(action); action();">
</form>
#2属性代码的返回值实际上是在特定事件中使用的(例如onsubmit
)。返回false
防止事件的默认操作。来自附加的侦听器的返回值addEventListener
总是被完全忽略(没有该值的接收器)。
#3处理程序中使用的所有变量和函数都必须是全局可访问的。这也可以算作一个缺陷。
经常被误解的行为
属性代码中调用的函数不是实际的事件处理函数,属性本身中的代码是附加的处理程序。因此事件对象和正确的this
值仅存在于属性处理程序代码中。如果您需要在全局函数中使用这些值中的任何一个,则必须在调用全局函数时手动传递它们。
这是任何 JavaScript 代码的正常行为,也在附加的事件处理程序中addEventListener
。如果您要从事件处理程序调用另一个函数,则必须传递事件对象,并绑定/传递this
值,如果其他函数需要这些值。
从事件侦听器调用另一个函数的示例。
function show () {
console.log('Called:', typeof event, this.toString());
}
const inp = document.querySelectorAll('input')[1];
inp.addEventListener('click', function (e) {
console.log('addEventListener:', typeof e, this.toString());
show();
});
<input onclick="console.log('Inline:', typeof event, this.toString()); show();" placeholder="Inline listener">
<input placeholder="addEventListener">
正如我们所看到的,附加类型之间没有区别——事件对象和this
值进行处理。当事件触发时,在内联侦听器中,属性中的代码是首先执行的代码,而对于其他附加类型,首先执行的代码是处理函数本身。 (示例中的事件对象部分部分不相关,因为几乎所有浏览器当前都实现了全局事件对象。全局事件对象目前已弃用(2023 年),因此这将在未来再次成为问题。)
内联侦听器的缺陷
#1您只能将同一类型的单个侦听器附加到元素。
#2内联侦听器是潜在的攻击媒介,因为属性中的侦听器代码以及从属性代码调用的任何函数都很容易用 DevTools 覆盖。
#3编写内联侦听器时,正确引用字符串很复杂。在服务器上编写动态标签时,引用的复杂性会增加,因为您必须处理 HTML 引用、JS 引用和服务器端语言引用。
#4内联侦听器不适用于模块和浏览器扩展(这些环境不在全局命名空间中,并且您无法从内联侦听器调用模块或插件代码中编写的函数),并且不被许多框架接受,他们不会通过任何安全审核。
#5内联监听器中断关注点分离原理是混合页面的表示层和操作层。
元素.onxxxx
onxxxx
属性不会遭受缺陷 #3、#4 和 #5 的影响,但您不能添加多个带有onxxxx
属性,并且由于元素无论如何都是全局的,因此很容易使用 DevTools 覆盖侦听器。
添加事件监听器
结论:使用addEventListener将事件附加到 HTML 元素,它没有任何缺陷。this
正确绑定,事件对象正确传递,可以附加多个相同类型的事件,并且没有安全风险(当处理程序不是全局可访问的函数时),因为一切都发生在封装的代码中,不需要单个全局变量或功能。
作为奖励,您可以选择触发事件的阶段,将仅一次触发的事件附加到元素而无需额外的工作,并获得更好的滚动性能与某些事件。