为什么在HTML中使用onClick()是一种不好的做法?


133

我已经多次听到onClick()在HTML 中使用JavaScript事件(例如)是一种不好的做法,因为它不利于语义。我想知道不利之处以及如何解决以下代码?

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>


4
onlickc没什么问题,但是通过设置href #,任何选择禁用javascript的人都会被卡住,无法做任何事情。对于某些站点来说,这是一件好事,但是对于仅打开一个窗口,不提供“真实”链接是完全愚蠢的。
Marc B

2
一定要阅读该链接。它与语义几乎没有关系,而与……有更多关系……页面上的所有内容:-)
糟糕的是,

尝试在新标签页中打开链接,您将看到一个示例,说明错误原因的原因……
Sebastien C.

2
应该是<button>,因为链接应该指向实际资源。这样更语义化,并且对屏幕阅读器用户更清晰。
程序员

Answers:


171

您可能正在谈论简洁的Javascript,看起来像这样:

<a href="#" id="someLink">link</a>

中央javascript文件中的逻辑如下所示:

$('#someLink').click(function(){
    popup('/map/', 300, 300, 'map'); 
    return false;
});

优点是

  • 行为(JavaScript)与表示(HTML)分开
  • 没有语言的混合
  • 您正在使用像jQuery这样的javascript框架,可以为您处理大多数跨浏览器问题
  • 您可以一次将行为添加到许多HTML元素中,而无需重复代码

30
您已经列出了很多优点,但是缺点呢?调试蓝烟?
abelito 2011年

2
@abelito:不确定调试是否不利。如果有的话,这是调试的优势,因为您可以在任何浏览器调试工具中逐步浏览JavaScript。如果代码在内联中,则不确定是否可以轻松地做到这一点onClick。如果您想针对自己的脚本,也可以编写单元测试,这也很难,如果代码位于内onClick并且没有逻辑分离的话,我也很难。我个人没有调试不引人注目的JavaScript的问题,并且不使用它对管理和测试JavaScript代码的好处非常重要。
Nope 2012年

45
@FrançoisWahl:主要缺点是可发现性:查看HTML并不能告诉您附加了哪些回调,甚至没有任何回调。
Michael Borgwardt 2012年

1
@MichaelBorgwardt:我完全同意你的看法,这是不利的。如果代码结构合理且井井有条,那么在进行项目或调试其他人的代码库时,我认为它并不是一个大问题。总的来说,虽然我同意您的观点,但我认为可发现性并不是编写内联脚本的良好理由,它牺牲了代码可测试性之类的好处,并且能够将您的功能/行为代码与DOM分开。我并不是说内联代码很糟糕,无法正常工作。它确实可以而且绝对可以,这取决于您是否对可测试性感兴趣。
Nope 2012年

4
对于新手,该讨论的另一点是- onclick可以更明确地映射元素交互作用和效果(函数调用)之间的因果关系,并减少认知负担。许多课程将学习者带入,addEventListener其中以更困难的语法包含了更多想法。此外,最近的基于组件的框架(如Riot和React)使用该onclick属性,并且正在改变应该分离的概念。从HTML转移onclick到支持此功能的组件可能是学习过程中更有效的“步骤”过程。
jmk2142

41

如果您使用的是jQuery,则:

HTML:

 <a id="openMap" href="/map/">link</a>

JS:

$(document).ready(function() {
    $("#openMap").click(function(){
        popup('/map/', 300, 300, 'map');
        return false;
    });
});

这样的好处是仍然可以在没有JS的情况下工作,或者如果用户中间单击了链接。

这也意味着我可以通过再次重写为以下内容来处理通用弹出窗口:

HTML:

 <a class="popup" href="/map/">link</a>

JS:

$(document).ready(function() {
    $(".popup").click(function(){
        popup($(this).attr("href"), 300, 300, 'map');
        return false;
    });
});

这样,您只需给其弹出窗口类,便可以将弹出窗口添加至任何链接。

这个想法可以像这样进一步扩展:

HTML:

 <a class="popup" data-width="300" data-height="300" href="/map/">link</a>

JS:

$(document).ready(function() {
    $(".popup").click(function(){
        popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
        return false;
    });
});

现在,我可以在整个站点上使用相同的代码来处理大量弹出窗口,而不必编写大量的onclick内容!是的,可重用!

这也意味着,如果以后我认为弹出式窗口是不正确的做法(确实如此),并且希望将其替换为灯箱样式模式窗口,则可以更改:

popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');

myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');

而且我整个网站上的所有弹出窗口现在的工作方式都完全不同。我什至可以进行功能检测来决定对弹出窗口执行的操作,或者存储用户首选项以允许或不允许用户使用。使用内联onclick,这需要大量的复制和粘贴工作。


4
无需JavaScript即可使用??是jQuery。它需要在浏览器上启用Javascript才能起作用,对吗?
托马斯·希尔兹

2
是的,但是在这种情况下,链接可以重定向到无需JavaScript即可执行操作的页面。
ThiefMaster 2011年

12
链接无需JavaScript就可以使用。查看链接如何具有常规href属性。如果用户没有JavaScript,则该链接仍将是链接,并且用户仍可以访问内容。
忧虑的是2011年

3
@Thomas Shields:不,他的意思是,如果您没有Javascript,该链接仍然会将您带到/ map / ...
Robert

2
啊,富有!我们都爱您这个最精致的解决方案!
Nirmal

21

对于非常大的JavaScript应用程序,程序员正在使用更多的代码封装来避免污染全局范围。为了使函数可用于HTML元素中的onClick动作,它必须处于全局范围内。

您可能已经看到了如下所示的JS文件...

(function(){
    ...[some code]
}());

这些是立即调用的函数表达式(IIFE),在其中声明的任何函数都将仅存在于它们的内部范围内。

如果function doSomething(){}在IIFE中声明,然后doSomething()在HTML页面中执行元素的onClick操作,则会收到错误消息。

另一方面,如果您在该IIFE中为该元素创建一个eventListener并doSomething()在侦听器检测到单击事件时调用该方法,那么您就很好了,因为该侦听器doSomething()共享了IIFE的范围。

对于使用最少代码量的小型Web应用程序来说,没关系。但是,如果您渴望编写大型的,可维护的代码库,onclick=""则应避免这种习惯。


1
如果您希望编写大型,可维护的代码库,请使用Angular,Vue,React等JS框架,建议将事件处理程序绑定到其HTML模板中
KamilKiełczewski,

20

不好有几个原因:

  • 它混合了代码和标记
  • 这样编写的代码会通过 eval
  • 并在全球范围内运行

最简单的事情是将一个name属性添加到您的<a>元素,然后您可以执行以下操作:

document.myelement.onclick = function() {
    window.popup('/map/', 300, 300, 'map');
    return false;
};

尽管现代最佳实践是使用id而不是名称,addEventListener()而是使用而不是使用,onclick因为这样可以将多个函数绑定到单个事件。


甚至建议采用这种方式,也使用户遇到事件处理程序冲突的问题。
预演

3
@preaction就像内联事件处理程序一样,因此为什么我的回答说最好使用addEventListener(),以及为什么。
Alnitak

2
有人可以解释更多吗?“用这种方式编写的代码会经过评估”……在网上我对此一无所获。您是说使用嵌入式Java脚本在性能或安全性方面有不利之处吗?
MarsAndBack '18

12

有几个原因:

  1. 我发现它有助于维护标记,即HTML和客户端脚本。例如,jQuery使以编程方式添加事件处理程序变得容易。

  2. 您提供的示例将在任何不支持javascript或已禁用javascript的用户代理中损坏。渐进增强的概念将鼓励在/map/没有javascript的情况下为用户代理提供一个简单的超链接,然后为支持javascript的用户代理实用地添加一个单击处理程序。

例如:

标记:

<a id="example" href="/map/">link</a>

Javascript:

$(document).ready(function(){

    $("#example").click(function(){
        popup('/map/', 300, 300, 'map');
        return false;
    });

})

2
感谢您提醒我渐进式增强,因此这也同样适合该范式:<a href="https://stackoverflow.com/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Daniel Sokolowski

10

修订版

在PAST中,不引人注目的JavaScript方法是不错的-特别是HTML中的事件处理程序绑定被认为是不好的做法(主要是因为YiddishNinjaonclick events run in the global scope and may cause unexpected error提到了什么

然而...

当前,这种方法似乎有些过时了,需要进行一些更新。如果有人想成为专业的前端开发人员并编写大型而复杂的应用程序,则他需要使用Angular,Vue.js等框架。但是,该框架通常使用(或允许使用)绑定了事件处理程序的HTML模板。直接在html模板代码中,这非常方便,清晰和有效-例如在有角度的模板中,人们通常会这样写:

<button (click)="someAction()">Click Me</button> 

在原始js / html中,这等效于

<button onclick="someAction()">Click Me</button>

区别在于,原始js onclick事件在全局范围内运行-但是框架提供了封装。

那么问题出在哪里呢?

问题是,总是听到html-onclick不好并且总是使用html的新手程序员btn.addEventListener("onclick", ... )想使用带有模板的框架(addEventListener也有缺点 -如果我们使用动态方式更新DOM innerHTML=(这是非常快的),那么我们就会松散事件处理程序以这种方式绑定)。然后,他将面对使用框架的不良习惯或错误方法-他将以非常糟糕的方式使用框架-因为他将主要关注js-part而不是template-part(并且产生不清楚且难以维护的东西)码)。要改变这种习惯,他会浪费很多时间(也许他需要一些运气和老师)。

因此,我认为,根据与学生的经验,如果他们在一开始使用html-handlers-bind会更好。正如我说的那样,确实可以在全局范围内调用处理程序,但是在此阶段,学生通常会创建易于控制的小型应用程序。为了编写更大的应用程序,他们选择了一些框架。

那么该怎么办?

我们可以更新Unobtrusive JavaScript方法,并允许在html中使用绑定事件处理程序(最终使用简单的参数)(但只能使用绑定处理程序-不能像OP提问中那样将逻辑放入onclick中)。所以我认为在原始js / html中应该允许

<button onclick="someAction(3)">Click Me</button>

要么

但是下面的例子不应该被允许

<button onclick="console.log('xx'); someAction(); return true">Click Me</button>

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>

现实改变了,我们的观点也应该


嗨,卡米尔(Kamil),我是JavaScript的新手,在使用onclick时也遇到了困惑,也就是说,请您如下解释onclick的缺点:1.您可能只分配了一个内联事件。2.内联事件被存储为DOM元素的属性,并且像所有对象属性一样,可以被覆盖。3.即使您删除onclick属性,此事件也会继续触发。
mirzhal

1
广告1.是的,只有一个,但是我的经验(有角度的)表明,这在大多数情况下就足够了-但是,如果不是的话,只需使用即可addEventListener。广告2.是的,它们可以被覆盖(但是通常没有人这样做)-问题出在哪里?广告3。删除onclick属性后,处理程序将不会被解雇- 这里的
KamilKiełczewski,

8

这是一个称为“ Unobtrusive JavaScript ” 的范例。当前的“ Web标准”说将功能和表示分开。

这并不是一个“坏习惯”,只是大多数新标准都希望您使用事件侦听器而不是内联JavaScript。

另外,这可能只是个人原因,但是我认为使用事件侦听器更容易阅读,尤其是当您要运行多个JavaScript语句时。


2
有关该主题的Wikipedia页面已有4年以上的历史了。它不再是一个新的范例。:)
昆汀

@David:它可能不是“新”的,但是仍然有人不使用它,因此对他们来说是“新的”。
火箭危险品

6

我想您的问题将引发讨论。一般的想法是,最好将行为和结构分开。此外,必须将内联点击处理程序afaik eval引导为成为真正的javascript函数。而且它是老式的,尽管那是一个非常不稳定的论点。嗯,嗯,请阅读有关它的@ quirksmode.org


我不想再制造一场圣战,我试图找到真实的地方:\
NiLL 2011年

2
  • onclick事件在全局范围内运行,可能会导致意外错误。
  • 向许多DOM元素添加onclick事件会降低
    性能和效率。

0

不使用内联处理程序的另外两个原因:

他们可能需要繁琐的报价转义问题

给定一个任意的字符串,如果你希望能够构建调用与字符串的函数,对于一般的解决方案内嵌处理器,你必须躲避属性分隔符(与相关的HTML实体),并且你会必须转义属性中用于字符串的定界符,如下所示:

const str = prompt('What string to display on click?', 'foo\'"bar');
const escapedStr = str
  // since the attribute value is going to be using " delimiters,
  // replace "s with their corresponding HTML entity:
  .replace(/"/g, '&quot;')
  // since the string literal inside the attribute is going to delimited with 's,
  // escape 's:
  .replace(/'/g, "\\'");
  
document.body.insertAdjacentHTML(
  'beforeend',
  '<button onclick="alert(\'' + escapedStr + '\')">click</button>'
);

真是丑陋。在上面的示例中,如果不替换's,则将导致SyntaxError,因为alert('foo'"bar')语法无效。如果不替换"s,则浏览器会将其解释为onclick属性的结尾("上面用s 分隔),这也不正确。

如果一个人习惯性地使用嵌入式处理器,一个将不得不作出一定要记得做类似上述的东西(和做正确的每一次,这是繁琐和辛苦的一看就懂。最好完全避免使用内联处理程序,以便可以在简单的闭包中使用任意字符串:

const str = prompt('What string to display on click?', 'foo\'"bar');
const button = document.body.appendChild(document.createElement('button'));
button.textContent = 'click';
button.onclick = () => alert(str);

那不是更好吗?


内联处理程序的作用域链非常独特

您认为以下代码会记录什么?

let disabled = true;
<form>
  <button onclick="console.log(disabled);">click</button>
</form>

试试看,运行代码段。这可能不是您所期望的。为什么会产生它的作用?因为内联处理程序在with块内运行。上面的代码位于三个 with块中:一个用于document,一个用于<form>,以及一个用于<button>

在此处输入图片说明

由于disableddisabled按钮的属性,因此在内联处理程序中进行引用是指按钮的属性,而不是外部disabled变量。这是很违反直觉的。with有很多问题:它可能是导致错误混淆的根源,并且会大大降低代码速度。严格模式下甚至都不允许使用。但是,使用内联处理程序,您不得不通过withs-而不是仅通过with,而是通过多个嵌套withs 来运行代码。这很疯狂。

with永远不要在代码中使用。因为内联处理程序隐式地要求with其所有混乱行为,所以也应避免使用内联处理程序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.