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

我曾多次听说,在 HTML 中使用诸如 onClick()之类的 JavaScript 事件是一种糟糕的做法,因为它不利于语义。我想知道的缺点是什么,以及如何修复以下代码?

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

如果你正在使用 jQuery,那么:

HTML:

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

约翰逊:

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

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

这也意味着我可以通过重写来处理一般的弹出窗口:

HTML:

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

约翰逊:

$(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>

约翰逊:

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

我现在可以在我的整个网站上使用相同位的代码弹出很多,而不必编写大量的 onclick 东西!为可重用性欢呼!

这也意味着,如果以后我决定弹出窗口是不好的做法,(它们确实是!)并且我想用 lightbox 样式的模态窗口来替换它们,我可以改变:

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

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

我整个网站上的所有弹出窗口现在的工作方式完全不同了。我甚至可以做一些特征提取来决定在弹出窗口中做什么,或者存储一个用户偏好来允许或不允许他们这样做。对于内联单击,这需要大量的复制和粘贴工作。

它不好有几个原因:

  • 它混合了代码和标记
  • 以这种方式编写的代码通过 eval
  • 在全球范围内运行

最简单的方法是向 <a>元素添加一个 name属性,然后您可以这样做:

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

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

有几个原因:

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

  2. 您给出的示例在任何不支持 javascript 或关闭 javascript 的用户代理中都会被破坏。渐进增强的概念将鼓励在没有 javascript 的情况下为用户代理提供到 /map/的简单超链接,然后为支持 javascript 的用户代理语法地添加一个 click 处理程序。

例如:

标价:

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

Javascript:

$(document).ready(function(){


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


})

这是一种称为“ Unobtrusive JavaScript”的 新的范式,当前的“网络标准”是将功能和表示分开。

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

此外,这可能只是一个个人的事情,但我认为它更容易阅读时,您使用事件侦听器,特别是如果您有多于1个 JavaScript 语句要运行。

你可能在谈论 Unobtrusive JavaScript,它看起来像这样:

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

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

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

好处是

  • 行为(Javascript)与表示(HTML)分开
  • 没有语言混合
  • 您使用的是 jQuery 这样的 javascript 框架,它可以为您处理大多数跨浏览器问题
  • 您可以同时向许多 HTML 元素添加行为,而不需要代码复制

我想你的问题会引起讨论。一般来说,将行为和结构分开是好的。此外,afaik,一个内联点击处理程序必须被 evalled“成为”一个真正的 javascript 函数。虽然这个论点站不住脚,但还是相当老套。啊,好吧,读一些关于它的 @ quirkSmode. org

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

你可能见过这样的 JS 文件。

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

它们是立即调用函数表达式(IIFE) ,在它们中声明的任何函数都只存在于它们的内部作用域中。

如果在 IIFE 中声明 function doSomething(){},那么在 HTML 页面中将 doSomething()作为元素的 onClick 操作,就会得到一个错误。

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

对于代码量很少的小型 web 应用程序来说,这并不重要。但是如果您渴望编写大型的、可维护的代码库,那么 onclick=""是您应该努力避免的习惯。

  • 在全局范围内运行的 onclick 事件可能导致意外 错误。
  • 向许多 DOM 元素添加 onclick 事件将减慢
    性能和效率。

修改

Unobtrusive JavaScript 方法在 PAST 中是好的-特别是 HTML 中的事件处理程序绑定被认为是不好的做法(主要是因为 onclick 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 很糟糕并且总是使用 btn.addEventListener("onclick", ... )的新手程序员想要使用一些带有模板的框架(addEventListener也有 缺点-如果我们使用 innerHTML=以动态的方式更新 DOM (这是相当 很快的)-那么我们松散的事件处理程序以那种方式绑定)。然后他将面临一些不好的习惯或错误的框架使用方法——他将以非常糟糕的方式使用框架——因为他将主要关注 js 部分而不是模板部分(并且产生不清晰且难以维护的代码)。为了改变这个习惯,他会失去很多时间(也许他需要一些运气和老师)。

因此,在我看来,根据我的学生的经验,如果他们在开始时使用 html 处理程序绑定,对他们来说会更好。正如我所说的,处理程序是在全局范围内调用的,但是这个阶段的学生通常创建容易控制的小应用程序。为了编写更大的应用程序,他们选择了一些框架。

那怎么办?

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

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

或者

function popup(num,str,event) {
let re=new RegExp(str);
// ...
event.preventDefault();
console.log("link was clicked");
}
<a href="https://example.com" onclick="popup(300,'map',event)">link</a>

But below examples should NOT be allowed

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


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

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

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

它们可能需要冗长的引用转义问题

给定一个 随心所欲字符串,如果你想构造一个内联处理程序,用这个字符串调用一个函数,对于通用解决方案,你必须转义 属性分隔符(与相关的 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>'
);

这是 难以置信难看。在上面的示例中,如果不替换 ',就会产生 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>:

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

enter image description here

因为 disabled是按钮的属性,所以在内联处理程序中引用 disabled是指按钮的属性,而不是外部的 disabled变量。这是相当违反直觉的。with有很多问题: 它可能是令人困惑的 bug 的来源,并且会大大减慢代码的速度。甚至在严格模式下都不允许。但是对于内联处理程序,您是 被迫的,可以通过 with运行代码——而且不仅仅是通过一个 with,而是通过多个嵌套的 with。太疯狂了。

with绝不应该在代码中使用。因为内联处理程序隐式地要求 with及其所有令人困惑的行为,所以也应该避免使用内联处理程序。