我多次听说在HTML中使用JavaScript事件(例如onClick()
)是一种不好的做法,因为它对语义不利。我想知道缺点是什么以及如何修复以下代码?
<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
答案 0 :(得分:162)
你可能在谈论unobtrusive Javascript,它看起来像这样:
<a href="#" id="someLink">link</a>
中央javascript文件中的逻辑看起来像这样:
$('#someLink').click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
优点是
答案 1 :(得分: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,这需要大量的复制和粘贴工作。
答案 2 :(得分:18)
由于以下几个原因,这并不好:
eval
最简单的方法是在name
元素中添加<a>
属性,然后就可以了:
document.myelement.onclick = function() {
window.popup('/map/', 300, 300, 'map');
return false;
};
虽然现代最佳做法是使用id
而不是名称,并使用addEventListener()
而不是onclick
,因为这样可以将多个函数绑定到单个事件。
答案 3 :(得分:18)
对于非常大的JavaScript应用程序,程序员使用更多的代码封装来避免污染全局范围。要使一个函数可用于HTML元素中的onClick操作,它必须位于全局范围内。
您可能已经看到过看起来像这样的JS文件......
(function(){
...[some code]
}());
这些是立即调用的函数表达式(IIFE),其中声明的任何函数只存在于其内部范围内。
如果您在IIFE中声明function doSomething(){}
,然后在HTML页面中将doSomething()
作为元素的onClick操作,则会收到错误。
另一方面,如果您在该IIFE中为该元素创建一个eventListener并在侦听器检测到单击事件时调用doSomething()
,那么您是好的,因为侦听器和doSomething()
共享IIFE的范围。
对于代码量最少的小型网络应用,无关紧要。但是,如果你渴望编写大型的,可维护的代码库,onclick=""
是一种你应该努力避免的习惯。
答案 4 :(得分:12)
有几个原因:
我发现它有助于分离标记,即HTML和客户端脚本。例如,jQuery可以轻松地以编程方式添加事件处理程序。
您提供的示例将在任何不支持javascript或关闭javascript的用户代理中被破坏。对于没有javascript的用户代理,progressive enhancement的概念会鼓励为/map/
添加一个简单的超链接,然后为支持javascript的用户代理添加一个点击处理程序。
例如:
标记:
<a id="example" href="/map/">link</a>
使用Javascript:
$(document).ready(function(){
$("#example").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
})
答案 5 :(得分:8)
这是一个名为“Unobtrusive JavaScript”的 new 范例。当前的“网络标准”表示将功能和表现分开。
这不是一个“糟糕的做法”,只是大多数新标准都希望你使用事件监听器而不是内嵌JavaScript。
此外,这可能只是个人的事情,但我认为在使用事件监听器时更容易阅读,特别是如果您要运行多于1个JavaScript语句。
答案 6 :(得分:6)
我想你的问题会引发讨论。一般的想法是将行为和结构分开是好的。此外,afaik,内联点击处理程序必须eval
导致'成为'真正的javascript函数。而且它很老式,尽管这是一个非常不稳定的论点。啊,好吧,请阅读一些关于它@quirksmode.org
答案 7 :(得分:4)
Unobtrusive JavaScript方法在PAST中是很好的-特别是HTML中的事件处理程序绑定被认为是不好的做法(主要是因为onclick events run in the global scope and may cause unexpected error
被Minghuan提及了)
当前看来,这种方法有些过时了,需要进行一些更新。如果有人想成为专业的前端开发人员并编写大型而复杂的应用程序,则他需要使用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
也有drawbacks-如果我们使用innerHTML=
以动态方式更新DOM(比appendChild
tests here更快-然后我们以这种方式释放事件处理程序绑定)。然后,他将面对使用框架的不良习惯或错误方法-他将以非常糟糕的方式使用框架-因为他将主要关注js-part而不是template-part(并且产生不清楚且难以维护的东西)码)。要改变这种习惯,他会浪费很多时间(也许他需要一些运气和老师)。
因此,我认为,根据与我的学生的经验,如果他们一开始使用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>
现实改变了,我们的观点也应该
答案 8 :(得分:2)
答案 9 :(得分: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, '"')
// 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
属性的结尾(上面用"
分隔),这也是不正确的。< / p>
如果习惯性地使用内联处理程序,则必须确保每次都记得做与上述类似的事情(并正确正确地执行) 一目了然。最好完全避免使用内联处理程序,以便可以在简单的闭包中使用任意字符串:
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
及其所有令人困惑的行为,因此也应避免使用内联处理程序。