addEventListener与onclick的区别

980

addEventListeneronclick 有什么区别?

var h = document.getElementById("a");
h.onclick = dothing1;
h.addEventListener("click", dothing2);

上述代码位于单独的 .js 文件中,它们都能完美地工作。

21个回答

1234

这两种方法都是正确的,但并没有“最佳”一说,开发者选择使用这两种方法可能有其原因。

事件监听器(addEventListener和IE的attachEvent

早期版本的Internet Explorer与几乎所有其他浏览器实现JavaScript不同。在小于9的版本中,您可以使用attachEvent [doc]方法,如下所示:

element.attachEvent('onclick', function() { /* do stuff here*/ });

在大多数其他浏览器中(包括IE 9及以上版本),您可以使用addEventListener[doc],如下所示:
element.addEventListener('click', function() { /* do stuff here*/ }, false);

使用这种方法(DOM Level 2事件),您可以将理论上无限数量的事件附加到任何单个元素。唯一的实际限制是客户端内存和其他性能问题,这些问题对于每个浏览器都不同。

上面的示例代表了使用匿名函数[文档]。您还可以使用函数引用[文档]或闭包[文档]添加事件侦听器:

const myFunctionReference = function() { /* do stuff here*/ }

element.attachEvent('onclick', myFunctionReference);
element.addEventListener('click', myFunctionReference , false);

addEventListener的另一个重要特性是最后一个参数,它控制侦听器如何响应冒泡事件[doc]。在示例中,我一直传递false,这是大概95%使用情况的标准。对于attachEvent或使用内联事件时,没有等效的参数。

内联事件(HTML onclick=""属性和element.onclick

在支持JavaScript的所有浏览器中,您可以将事件侦听器内联放置,即直接在HTML代码中。您可能已经看到过这个:

<a id="testing" href="#" onclick="alert('did stuff inline');">Click me</a>

大多数经验丰富的开发人员都避免使用这种方法,但它确实可以完成工作;它简单直接。您可能无法在此处使用闭包或匿名函数(尽管处理程序本身是某种匿名函数),并且您对范围的控制受到限制。

您提到的另一种方法:

element.onclick = function () { /*do stuff here */ };

...相当于内联JavaScript,但您可以更好地控制范围(因为您编写的是脚本而不是HTML),并且可以使用匿名函数、函数引用和/或闭包。

内联事件的显着缺点是,与上面描述的事件侦听器不同,您只能分配一个内联事件。内联事件存储为元素的属性/属性[doc],这意味着它可能会被覆盖。

使用上面HTML中的<a>示例:

const element = document.getElementById('testing');
element.onclick = function () { alert('did stuff #1'); };
element.onclick = function () { alert('did stuff #2'); };

...当你点击元素时,你只会看到“Did stuff #2” - 你用第二个值覆盖了onclick属性的第一个赋值,并且也覆盖了原始的内联HTML onclick属性。在这里查看:http://jsfiddle.net/jpgah/

总的来说,不要使用内联事件。可能有特定的用例,但如果你不确定是否有这种用例,那么你就没有并且不应该使用内联事件。


现代 JavaScript(包括Angular等)

自从这个回答最初发布以来,像Angular这样的JavaScript框架变得越来越流行。你会在Angular模板中看到类似这样的代码:

<button (click)="doSomething()">Do Something</button>

这看起来像是一个内联事件,但实际上它并不是。这种类型的模板将被转译成更复杂的代码,该代码在幕后使用事件监听器。我在这里写的关于事件的所有内容仍然适用,但至少有一层你被从繁琐的细节中解放出来了。你应该理解其原理,但如果你的现代JavaScript框架最佳实践涉及在模板中编写此类代码,请不要感觉自己正在使用内联事件 - 你并不是。


哪个更好?

这个问题涉及到浏览器兼容性和必要性。您需要将多个事件附加到一个元素吗?将来会吗?很可能会。attachEvent和addEventListener是必需的。如果不需要,内联事件似乎可以胜任,但是最好为未来做好准备,尽管它可能看起来不太可能,但至少是可预测的。有可能您将不得不转向基于JavaScript的事件监听器,因此最好从那里开始。不要使用内联事件。

jQuery和其他JavaScript框架将DOM级别2事件的不同浏览器实现封装在通用模型中,因此您可以编写跨浏览器兼容的代码,而无需担心IE作为叛逆者的历史。使用jQuery相同的代码,全部跨浏览器并且准备就绪:

$(element).on('click', function () { /* do stuff */ });

不要仅仅为了这个而去获取一个框架。你可以轻松地编写自己的小实用程序来处理旧版浏览器:

function addEvent(element, evnt, funct){
  if (element.attachEvent)
   return element.attachEvent('on'+evnt, funct);
  else
   return element.addEventListener(evnt, funct, false);
}

// example
addEvent(
    document.getElementById('myElement'),
    'click',
    function () { alert('hi!'); }
);

试一下:http://jsfiddle.net/bmArj/

考虑到所有这些,除非你查看的脚本以某种其他方式(在你的问题中未显示的代码中)考虑了浏览器差异,否则使用addEventListener的部分将无法在低于9版本的IE中工作。

文档和相关阅读


16
抱歉打扰一下,我想简明地提供一下你的函数(fiddle:http://jsfiddle.net/bmArj/153/)- function addEvent(element, myEvent, fnc) { return ((element.attachEvent) ? element.attachEvent('on' + myEvent, fnc) : element.addEventListener(myEvent, fnc, false)); }。该函数用于添加事件监听器。 - Deryck
10
@Gaurav_soni 不行。函数的名称以及其包含的所有代码已经在JavaScript文件中公开,该文件是明文的。任何人都可以打开网络控制台并执行或操纵任何JavaScript。如果您的JavaScript包含任何可能成为安全风险的内容,如果它被公开给公众,那么您就有一个严重的问题,因为它已经被公开了。 - Chris Baker
4
只要我们正在压缩这个算法,那么我们也可以彻底做到:function addEvent(e,n,f){return e.attachEvent?e.attachEvent('on'+n,f):e.addEventListener(n,f,!!0)}。这个函数只有98个字符,比原来的算法缩小了40%以上! - Trevor
5
为了满足您的要求,以下是翻译内容:对于你的好奇心,为什么会使用!!0?为什么不是!1或者只是0呢? - Chris Baker
4
@AdrianMoisa 这个答案是在AngularJS刚兴起的时候写的,当时的常见做法仍然是“渐进增强” -- 就是以一种方式编写HTML文档,使其可以与或没有JavaScript一起使用。从这个角度来看,从JavaScript中绑定事件是最佳实践。如今,我不认为很多人还过于担心渐进增强,尤其是考虑到Angular等工具的普及程度。对于内联事件(不使用Angular),仍然存在一些关于关注点分离的争论,但那更多是风格而非实质。 - Chris Baker
显示剩余14条评论

289

如果你有另外两个函数,你就能看到它们之间的区别:

var h = document.getElementById('a');
h.onclick = doThing_1;
h.onclick = doThing_2;

h.addEventListener('click', doThing_3);
h.addEventListener('click', doThing_4);

第 2、3 和 4 个函数可以正常工作,但第一个函数不能。这是因为 addEventListener 不会覆盖现有的事件处理程序,而 onclick 会覆盖任何现有的 onclick = fn 事件处理程序。

当然,另一个重要的区别是,onclick 总是可用的,而 addEventListener 在 Internet Explorer 9 版本之前不起作用。在 IE <9 中,您可以使用类似的 attachEvent(语法略有不同)。


29
非常清晰明了的解释!直戳问题核心。如果我需要为一个事件使用多个函数,那么我只能使用addEventListener来实现,并且为了适应IE浏览器,我需要额外编写attachEvent的代码。 - William Sham
7
2、3和4应该被命名为“dosomething”。1被2覆盖,从未被调用。 - DragonLord
1
@Ludolfyn 我想要明确一点 - 如果内联事件是在HTML中定义的,它会在大多数情况下在HTML离开浏览器视图时被清除。如果您在代码中执行 element.onclick = myFunction,那么当HTML未显示时,它将不会被清除,实际上,您可以将事件附加到从未添加到DOM的元素(因此它们是页面的“一部分”)。在许多情况下,如果您附加了这样的事件,它可能会留下一个打开的引用,因此它不会被GC清除。 - Chris Baker
1
使用addEventListener添加的事件,在某些情况下也需要进行清理。 - Chris Baker
1
非常感谢@ChrisBaker!我仍在开发相关的应用程序,所以这非常有帮助。我正在动态生成和移除DOM元素,因此选择遵循React的建议,在<html>元素中添加一个addEventListener(),然后只需检查event.target属性即可监听特定元素的点击事件。这样我就不必担心用流氓事件侦听器污染内存堆。它以前是内联的(在HTML中定义),即使它随着元素被删除而被删除,它仍会占用内存空间...对吗? - Ludolfyn
显示剩余8条评论

83
在这个答案中,我将描述定义DOM事件处理程序的三种方法。

element.addEventListener()

代码示例:

const element = document.querySelector('a');
element.addEventListener('click', event => event.preventDefault(), true);
<a href="//google.com">Try clicking this link.</a>

element.addEventListener()具有多个优点:

  • 允许您注册无限数量的事件处理程序,并使用element.removeEventListener()将它们删除。
  • 具有useCapture参数,表示您想在其捕获或冒泡阶段处理事件。请参见:无法理解addEventListener中useCapture属性
  • 注重语义。基本上,它使注册事件处理程序更加明确。对于初学者来说,函数调用很明显会发生某些事情,而将事件分配给DOM元素的某个属性至少不是直观的。
  • 允许您分离文档结构(HTML)和逻辑(JavaScript)。在小型 Web 应用程序中,这似乎并不重要,但在任何规模较大的项目中,它确实很重要。与未分离结构和逻辑的项目相比,维护分离了结构和逻辑的项目要容易得多。
  • 消除正确事件名称的混淆。由于使用内联事件侦听器或将事件侦听器分配给DOM元素的.onevent属性,许多经验不足的 JavaScript 程序员认为事件名称例如为onclickonloadon不是事件名称的一部分。正确的事件名称是clickload,并且这就是如何将事件名称传递给.addEventListener()
  • 几乎所有浏览器中都有效。如果您仍需支持 IE <= 8,则可以使用来自MDN的polyfill

element.onevent = function() {}(例如onclickonload

代码示例:

const element = document.querySelector('a');
element.onclick = event => event.preventDefault();
<a href="//google.com">Try clicking this link.</a>

这是在DOM 0中注册事件处理程序的一种方式。现在不建议使用,因为它具有以下缺点:
  • 只允许您注册一个事件处理程序。此外,删除已分配的处理程序并不直观,因为要删除使用此方法分配的事件处理程序,您必须将onevent属性恢复到其初始状态(即null)。
  • 不能适当地响应错误。例如,如果您错误地将字符串分配给window.onload,例如:window.onload = "test";,它不会抛出任何错误。您的代码将无法工作,很难找出原因。然而,.addEventListener()会抛出错误(至少在Firefox中):TypeError:EventTarget.addEventListener的第2个参数不是对象
  • 没有提供一种选择,可以在其捕获或冒泡阶段处理事件的方法。

内联事件处理程序(onevent HTML属性)

代码示例:

<a href="//google.com" onclick="event.preventDefault();">Try clicking this link.</a>

element.onevent类似,现在不鼓励使用。除了element.onevent存在的问题外,它还有以下问题:

  • 是一个潜在的安全问题,因为它使XSS攻击更加具有破坏性。现在的网站应该发送适当的Content-Security-Policy HTTP头来阻止内联脚本,并仅允许来自受信任域的外部脚本。参见Content Security Policy如何工作?
  • 没有将文档结构和逻辑分离
  • 如果您使用服务器端脚本生成页面,并且例如您生成了一百个链接,每个链接都有相同的内联事件处理程序,那么您的代码将比仅定义一次事件处理程序要长得多。这意味着客户端需要下载更多的内容,导致您的网站速度变慢。

另请参阅


32

虽然所有浏览器都支持onclick,但旧版的Internet Explorer不支持addEventListener而是使用attachEvent

onclick的缺点是只能有一个事件处理程序,而其他两个会触发所有已注册的回调函数。


28

摘要:

  1. addEventListener可以添加多个事件,而使用onclick则无法做到。
  2. onclick可以作为HTML属性添加,而addEventListener只能在<script>元素中添加。
  3. addEventListener可以采用第三个参数来停止事件传播。

两者都可用于处理事件。然而,addEventListener应该是首选,因为它可以做到onclick的所有功能并且更多。不要将行内onclick用作HTML属性,因为这将混淆javascript和HTML,这是一个不好的做法。这会使代码难以维护。


那么元素定位通常是如何完成的呢?我的意思是,我个人不会使用内联onclick处理程序,因为我害怕被嘲笑 - 但通常事件绑定的方式在最近几年中变得更糟糕和难以维护。像js-linkjs-form-validation这样的类或带有data-jspackage="init"的数据属性并不比内联处理程序好多少...你实际上有多频繁地使用事件冒泡呢?我个人希望能够编写一个处理程序,而无需检查目标是否实际上与我的元素匹配 - 或者由于随机错误而不得不在多个位置停止传播。 - Christoffer Bubach
@ChristofferBubach 我一直使用事件冒泡。一个简单的例子是由无序列表组成的菜单。您可以在 ul 标签上放置一个单独的事件侦听器。单击任何包含的 li 元素会冒泡到此 ul 事件侦听器。然后,您使用 event.target 确定单击了哪个 li 元素,然后从那里开始。如果不使用冒泡,则必须为每个 li 元素放置单独的事件侦听器。 - BobRodes
@BobRodes 你说得很对,举的例子也很好。尽管这可能是仅有的几个有用的事件冒泡示例之一,但我可以举出另外五个例子,其中它只会引起随机错误。不确定它到底有多高效,必须是一个非常复杂的菜单和相当糟糕的浏览器/JS解释器,才能与直接连接在LI上的处理程序相比获得任何收益吧? ;) - Christoffer Bubach
@ChristofferBubach 更容易维护(即添加或删除处理程序中的项目)。这不是一条硬性规定,但在我看来是如此。 - BobRodes

12
据我所知,DOM的"load"事件仍然只能非常有限地工作。这意味着它只会触发window对象images<script>等元素的事件。直接使用onload赋值也是如此。这两者之间没有技术上的区别。可能.onload=在跨浏览器可用性方面更好。
但是,你不能将load事件分配给<div><span>元素或其他任何元素。

8
一个元素只能附加一个事件处理程序,但可以附加多个事件监听器。

那么,它在实践中是什么样子?

只有最后一个被指定的事件处理程序会运行:

const button = document.querySelector(".btn")
button.onclick = () => {
  console.log("Hello World");
};
button.onclick = () => {
  console.log("How are you?");
};
button.click() // "How are you?" 

所有的事件监听器都会被触发:

const button = document.querySelector(".btn")
button.addEventListener("click", event => {
  console.log("Hello World");
})
button.addEventListener("click", event => {
  console.log("How are you?");
})
button.click() 
// "Hello World"
// "How are you?"

IE的注意事项: attachEvent不再受支持。从IE 11开始,请使用addEventListener文档


6

还有一个细节需要注意:现代桌面浏览器默认情况下认为不同的按钮按下对于AddEventListener('click'onclick来说是不同的“点击”。

  • 在Chrome 42和IE11上,onclickAddEventListener都会在左键和中键单击时触发点击事件。
  • 在Firefox 38上,onclick仅在左键单击时触发,但AddEventListener点击事件会在左键、中键和右键单击时触发。

此外,当涉及滚动光标时,中键行为非常不一致:

  • 在Firefox上,中键事件总是会触发。
  • 在Chrome上,如果中键打开或关闭滚动光标,则不会触发它们。
  • 在IE上,当滚动光标关闭时会触发它们,但当滚动光标打开时不会触发它们。

值得注意的是,任何可用键盘选择的HTML元素(例如input)的“点击”事件在选定元素时也会在空格或回车键按下时触发。


5
你还应该考虑使用事件委托(Event Delegation)!因此,我更喜欢使用addEventListener,并谨慎地使用它!
事实:
1. 事件监听器很重...(在客户端分配内存)。 2. 事件在DOM树中传播进入和再次传播出去。也称为冒泡和捕获,请阅读一下以了解详情。
因此,想象一个简单的例子:一个简单的按钮在body里面的div里面...如果你点击按钮,事件将无论如何流入到按钮中,然后再次流出,就像这样:window-document-div-button-div-document-window
在浏览器后台(假设是JS引擎的软件外围),浏览器只有在检查每次点击的目标位置时才能对点击作出反应。
为了确保在路上的每个可能的事件监听器都被触发,它必须从文档级别向下发送“点击事件信号”到元素...然后再次返回。
可以使用addEventListener等方法来利用这种行为来附加事件监听器:
document.getElementById("exampleID").addEventListener("click",(event) => {doThis}, true/false);
请注意,addEventListener方法的最后一个true/false参数控制事件何时被识别——是在冒泡过程中还是在捕获过程中。 true表示在冒泡过程中识别事件 false表示在冒泡过程中识别事件

使用上述方法处理还可以更加直观地实现以下两个有用的概念:

  1. 您也可以在函数内部使用event.stopPropagation()(例如参考“doThis”)来防止当前事件在捕获和冒泡阶段进一步传播。但它不会阻止任何默认行为发生;例如,链接点击仍然会被处理。
  2. 如果您想停止这些行为,您可以在函数内使用event.preventDefault()(例如参考“doThis”)。这样,您可以告诉浏览器,如果事件没有被明确处理,它的默认操作不应像通常那样被执行。
此外需要注意的是:addEventListener方法的最后一个参数(true/false)还控制着“.stopPropagation()”方法的生效阶段(冒泡或捕获)。 因此,如果您对一个元素应用带有TRUE标志的EventListener,并结合使用.stopPropatation()方法,则该事件甚至不会传递到元素内部的潜在子元素。
总之: 如果你在HTML中使用onClick变体... 对我来说有两个缺点:
1.使用addEventListener,你可以将多个onClick事件附加到同一个或单个元素上,但使用onClick则不可能(至少我目前强烈相信是这样的,如果我错了,请纠正我)。 2.特别是代码维护方面(迄今为止没有详细说明)的下一个方面也真是值得注意的。
关于事件委托,其实归根结底就是这样。如果其他JavaScript代码需要响应点击事件,使用addEventListener可以确保你们两个都能响应它。如果你们都尝试使用onclick,那么其中一个会覆盖另一个。如果你想在同一个元素上使用onclick,你们两个都不能响应。此外,你希望尽可能将行为与HTML分开,以防以后需要更改它。如果有50个HTML文件需要更新而不是一个JavaScript文件,那将很糟糕。(感谢Greg Burghardt,addEventListener vs onclick with regards to event delegation)
  • 这也被称为“无侵入式JavaScript”...看看吧!

有点啰嗦......我会重写一下。他没有问到事件的技术细节。我只会把它缩减到最后一段。 - john k
这是一个很棒的话题,Timo,对于人们理解现代JS非常重要。我们正在前端做比以往更多的工作,内存效率至关重要。每个监听器都会增加页面的总权重。 - Chris Baker

5

element.onclick = function() { /* do stuff */ }

element.addEventListener('click', function(){ /* do stuff */ },false);

这两个方法都可以监听点击事件并执行回调函数,但它们并不相等。如果你需要选择其中一个,下面的内容可能会帮到你。

最主要的区别是onclick只是一个属性,就像所有对象属性一样,如果你多次写入,它将被覆盖。而使用addEventListener(),我们可以为元素简单地绑定一个事件处理程序,每次需要时都可以调用它,无需担心任何被覆盖的属性。 示例如下:

试一试:https://jsfiddle.net/fjets5z4/5/

一开始我很想继续使用onclick,因为它更短,看起来更简单… 实际上确实如此。但我不再推荐使用它了。它就像使用内联JavaScript一样。现在已经非常不鼓励使用这种方式(内联CSS也不鼓励使用,但那是另一个话题)。

然而,尽管addEventListener()是标准,它在旧浏览器(Internet Explorer 9版本以下)中并不起作用,这是另一个重要的区别。如果你需要支持这些古老的浏览器,你应该使用onclick。但你也可以使用jQuery(或其替代品):它基本上简化了你的工作并减少了浏览器之间的差异,因此可以节省你很多时间。

var clickEvent = document.getElementByID("onclick-eg");
var EventListener = document.getElementByID("addEventListener-eg");

clickEvent.onclick = function(){
    window.alert("1 is not called")
}
clickEvent.onclick = function(){
    window.alert("1 is not called, 2 is called")
}

EventListener.addEventListener("click",function(){
    window.alert("1 is called")
})
EventListener.addEventListener("click",function(){
    window.alert("2 is also called")
})

@agiopnl 我的朋友,这就是我展示的所有差异,你可以根据你的需求使用任何一个。 即使你不同意其他人提供的解决方案,请礼貌地表达。 - Arham Chowdhury
我不明白... 为什么你需要担心覆盖属性?相比于担忧,我认为这是改进功能。如果你担心有害代码,你也可以使用removeEventListener()来移除。你说这是不被鼓励的,但你没有给出理由。 - agiopnl

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