我听说在HTML中使用JavaScript事件,如onClick()
,是个不好的实践,因为它不符合语义化。我想知道这样做的缺点是什么,并且如何修复以下代码?
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
我听说在HTML中使用JavaScript事件,如onClick()
,是个不好的实践,因为它不符合语义化。我想知道这样做的缺点是什么,并且如何修复以下代码?
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
你可能在谈论不侵入式JavaScript,它看起来像这样:
<a href="#" id="someLink">link</a>
使用一个中央的JavaScript文件,其中的逻辑看起来像这样:
$('#someLink').click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
优点是:
onClick
方式,可能就没有这么容易了。你还可以编写单元测试,如果你愿意的话,对你的脚本进行测试也很困难,特别是当代码被放在onClick
里面且没有分离出来逻辑时。我个人没有问题使用不干扰式 JavaScript 进行调试,管理和测试 JavaScript 代码的好处太多了,所以一定要使用它。 - Nopeonclick
可以更明确地映射元素交互和效果(函数调用)之间的因果关系,并减轻认知负荷。许多教程会让学习者直接使用addEventListener
,这会将许多更复杂的语法概念压缩在一起。此外,最近的组件化框架,如 Riot 和 React,使用了onclick
属性,正在改变分离应该是什么样子的概念。从HTML的onclick
转移到支持此功能的组件可能是更有效的“步骤”学习过程。 - jmk2142 <a id="openMap" href="/map/">link</a>
JS:
JavaScript:$(document).ready(function() {
$("#openMap").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
});
这样做的好处是即使没有 JavaScript,或者用户使用中键点击链接仍然可以正常工作。
这还意味着我可以通过再次重写来处理通用弹出窗口:
HTML:
<a class="popup" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), 300, 300, 'map');
return false;
});
});
通过给链接添加 popup 类,您可以将弹出窗口添加到任何链接。
这个想法甚至可以进一步扩展,像这样:
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,这需要大量复制和粘贴。
在非常庞大的JavaScript应用程序中,程序员正在更多地使用代码封装来避免污染全局作用域。要使函数可用于HTML元素中的onClick操作,它必须位于全局作用域中。
您可能已经看到过像这样的JS文件...
(function(){
...[some code]
}());
这些是立即调用的函数表达式(IIFEs),在其中声明的任何函数只存在于其内部作用域中。
如果你在IIFE中声明了function doSomething(){}
,然后将doSomething()
设置为HTML页面元素的onClick操作,你会得到一个错误。
另一方面,如果你在那个IIFE中创建了一个元素的事件监听器,并在监听器检测到单击事件时调用doSomething()
,那么就没问题了,因为监听器和doSomething()
共享IIFE的作用域。
对于小型Web应用程序来说,代码量很少,这并不重要。但是如果你有志写大型、可维护的代码库,那么onclick=""
是一个应该避免的习惯。
有几个原因不太好:
eval
最简单的方法是给你的<a>
元素添加一个name
属性,然后可以这样做:
document.myelement.onclick = function() {
window.popup('/map/', 300, 300, 'map');
return false;
};
现代最佳实践是使用id
而不是name
,并使用addEventListener()
而不是onclick
,因为这允许您将多个函数绑定到单个事件。
addEventListener()
,并解释为什么这样做更好。 - Alnitak无侵入式JavaScript方法在过去曾经很好用 - 特别是在HTML中绑定事件处理程序被认为是一种不好的做法(主要原因是onclick事件在全局范围内运行,可能导致意外错误
,这是由YiddishNinja提到的)。
目前看来,这种方法有些过时,需要进行更新。如果有人想成为专业的前端开发者并编写大型和复杂的应用程序,则需要使用诸如Angular、Vue.js等框架......但是这些框架通常使用(或允许使用)HTML模板,其中事件处理程序直接绑定在html模板代码中,非常方便、清晰和有效 - 例如,在angular模板中,通常人们会写:
<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-handlers-bind。正如我所说,确实是调用处理程序处于全局范围内,但在这个阶段,学生通常创建容易控制的小型应用程序。要编写更大的应用程序,他们选择一些框架。
我们可以更新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>
但以下示例不应被允许
<button onclick="console.log('xx'); someAction(); return true">Click Me</button>
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
现实在不断变化,我们的观点也应该随之改变。
addEventListener
。广告2:是的,它们可以被覆盖(但通常没有人这样做) - 那有什么问题吗?广告3:删除onclick属性后,处理程序将不会被触发 - 证明在此处。 - Kamil KiełczewskiaddEventListener
。(click)
和onclick
绝对不是等价的。 - Colinonclick="someAction(3)"
是一个“错误习惯”的例子。someAction
函数正在污染全局命名空间。 - Colin有几个原因:
我发现将标记,即HTML和客户端脚本分开会有助于维护。例如,jQuery让以编程方式添加事件处理程序变得容易。
您提供的示例在任何不支持JavaScript或已关闭JavaScript的用户代理中都将失效。 渐进增强 的概念鼓励为没有 JavaScript 的用户代理使用简单的超链接指向 /map/
,然后为支持 JavaScript 的用户代理以编程方式添加点击处理程序。
例如:
标记:
<a id="example" href="/map/">link</a>
Javascript:
$(document).ready(function(){
$("#example").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
})
<a href="/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
。 - Daniel Sokolowski我想你的问题会引起讨论。总体思路是,将行为和结构分离是有益的。另外,据我所知,内联点击处理程序必须进行eval
才能“变成”一个真正的JavaScript函数。虽然这有点老派,但这是一个相当脆弱的论点。嗯,可以在@quirksmode.org了解更多相关信息。
有两个不使用内联处理程序的理由:
给定一个任意字符串,如果你想构造一个调用该字符串的函数的内联处理程序,为了得到通用解决方案,你将不得不转义属性分隔符(与相关的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, '"')
// 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')
不是有效的语法。如果您没有替换"
,那么浏览器会将其解释为onclick
属性的结束(在上面用"
分隔),这也是不正确的。
如果一个人习惯于使用内联处理程序,则必须确保记住做类似于上述操作(并且正确地执行)每次,这很繁琐,一眼很难理解。最好完全避免使用内联处理程序,以便可以在简单的闭包中使用任意字符串:
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>
disabled
是按钮的属性,内联处理程序中引用disabled
指的是按钮的属性,而不是外部的disabled
变量。这非常令人困惑。使用with
会有很多问题:它可能是混淆错误的来源,并且会显着减慢代码。在严格模式下甚至根本不允许使用。但是对于内联处理程序,您必须通过with
来运行代码,而且不仅要通过一个with
,而且要通过多个嵌套的with
。这太疯狂了。
with
在代码中永远不应该被使用。因为内联处理程序隐式地要求使用with
以及所有混乱的行为,所以应该避免使用内联处理程序。
#
,会导致禁用 JavaScript 的用户无法进行任何操作。对于某些网站来说这是好事,但是仅仅是打开一个窗口却不提供“真实”的链接,这是非常愚蠢的做法。 - Marc B<button>
,因为链接应该指向一个实际资源。这样更具语义化,并且更清晰地呈现给屏幕阅读器用户。 - user6560716