为什么在现代语义化HTML中使用内联事件处理程序属性是一个不好的主意?

24

内联事件处理程序是否被认为是一种不良做法?

例如:<button onclick=someFunction()>Click me!</button>

如果是这样的话,使用内联事件处理程序的缺点是什么?


这个回答解决了你的问题吗?为什么在HTML中使用onClick()是一种不好的做法? - Ivar
3个回答

33

这是一个糟糕的主意,因为...

  1. 最佳实践建议在内容、样式和脚本之间进行明确的分离。在HTML中混合使用内联JavaScript(或CSS)与此不一致。

  2. 你只能绑定每个元素的每种事件类型的一个事件,使用on*风格的事件,所以你不能有两个onclick事件处理程序,例如。

  3. 如果事件在内联中指定,JS将作为字符串指定(属性值始终为字符串),并在事件触发时进行评估。评估是有害的。

  4. 由内联事件处理程序表示的函数必须是全局的(或至少是全局可访问的),这在现在很少见;代码通常是命名空间化的,或者封装在模块中(感谢@Sebastian Simon)。

  5. 如果你正在使用内容安全策略(CSP),则必须(不明智地)扩展其允许评估内联JavaScript。

简而言之,通过专用的addEventListener API或通过jQuery等方式集中处理事件。

[2021年编辑]

这些天,响应式框架在某种程度上扭转了这一趋势;在响应式框架中,事件通常被指定为属性,例如在Vue中:
<p v-on:click='foo'>Hello</p>

...其中foo是当前组件数据对象的一个方法。
然而,这并不是真正的内联事件处理;请参考@colin在@adnanmuttaleb的答案下的评论。

1
这里有另一个好的列表,解释为什么应该避免使用 onclick 等。 - Sebastian Simon
关于第一点,可以通过使用类似于 onClick="handler1() || handler2()....|| handlern()" 的方法来解决问题。https://jsfiddle.net/ageck0bh/。 - adnanmuttaleb
3
v-on:click 不是一个属性,而是一个指令,在底层它将使用 addEventListener。https://vuejs.org/v2/guide/events.html#Listening-to-Events - Colin
1
这是一篇很老的帖子,但我自己对这个问题进行了大量研究。关于React或Angular等最后一部分,需要注意区别。这些框架使用模板引擎,看起来像内联JS的东西实际上并不是。它们将通过编译器或模板引擎传递,并将输出代码作为这些元素上的适当事件监听器。原始HTML没有这样的好处。 - NorthStarCode
2
关于第四点:当前最佳实践包括使用ECMAScript模块。模块具有自己的模块作用域,而不是全局作用域。由于on*属性依赖于全局作用域,因此您必须将函数设置为全局属性,从而破坏了模块的一部分目的,特别是封装的好处。 - Sebastian Simon

7
除了已接受答案中提到的语义和其他观点外,所有内联脚本都被认为是一个漏洞和高安全风险。任何希望在现代浏览器上运行的网站都应该设置“内容安全策略”(CSP)属性,可以通过元属性或标头来实现。
这样做与所有内联脚本和样式不兼容,除非明确允许这些内容例外。尽管CSP的目标主要是防止持续性的跨站点脚本(xss)威胁,而内联脚本和样式是xss的一种载体,但它目前不是浏览器的默认行为,但未来可能会改变。

我想你的意思是HTML中完全不使用JavaScript。但是,由于您使用了“内联JavaScript”这个术语,我要指出根据此处最高得票的答案 https://dev59.com/62Ik5IYBdhLWcg3wE6Z8 ,内联JavaScript仅限于script标签之间的内容...而不是例如onclick中的内容,那将是内联事件处理程序。 - barlop
2
冒昧重复一下,你指出onclick是内联事件处理程序是语义学,或者说是没有区别的区别。 "处理程序"是脚本,因此是内联脚本。 “最高投票”仅由于偏见而最高,SO对开发人员有极端的偏见,而不是安全专业人员。如果像我这样的人在SO上更多,就会有更多关注安全的人投票。少数票数并不意味着答案是错误的,此外,SO只允许1个被接受/正确的答案,但在现实中,您认为问题只有1个正确答案是缺乏经验的。 - Stof
@barlop 内联事件处理程序被CSP视为内联JS:除非在script-src中指定'unsafe-inline''unsafe-hashes',否则会阻止它们。 - CheddarLizzard
主要的观点最好描述为,你想让一滴水穿过堤坝,而不是整个河流...当然,你可以稍微打开洪水来让它通过,但你无法控制只有那一个东西会通过。允许内联脚本,即使使用哈希或随机数,而不知道代码的作用,这只会导致糟糕的安全性。你知道它不会在页面加载后执行漏洞利用吗?或者其他同样糟糕的事情?内联就是内联,盲目信任只是盲目和好感。 - Stof

2

在 @Mitya 的回答基础上进行补充。

在大多数现代JS库中,如ReactVue等,内联事件处理程序被认为是惯用方法,但@Mitya提到的大部分限制已经消失了。 我们将以Vuejs为案例研究,并将其与上述要点进行比较:

  1. 您可以拥有多个事件处理程序,请参见此处
  2. 事件值(处理程序)例如onclick不是纯字符串,而是js表达式,请参见此处
  3. 全局作用域问题根本不存在(因为您的代码将由工具(如webpack或其他)翻译,缩小并重新打包)。

在我看来,内联事件处理程序极大地增强了可读性,但观点可能会有所不同。


7
React、Vue和Angular看起来像是使用“内联事件处理程序”,但它们并没有像原问题所描述的那样使用HTML属性。它们使用指令,并在底层使用addEventListener,因此符合最佳实践:https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#inline_event_handlers_%E2%80%94_dont_use_these - Colin
2
@Colin,我已经不知道有多少次需要解释这个问题了。有些人想要把这些指令作为使用HTML事件属性的例子来证明它是可以的(但实际上并不是!)。 - Scott Marcus

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