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

时间:2011-05-03 15:10:52

标签: javascript html

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

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

10 个答案:

答案 0 :(得分:162)

你可能在谈论unobtrusive Javascript,它看起来像这样:

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

中央javascript文件中的逻辑看起来像这样:

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

优点是

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

答案 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)

有几个原因:

  1. 我发现它有助于分离标记,即HTML和客户端脚本。例如,jQuery可以轻松地以编程方式添加事件处理程序。

  2. 您提供的示例将在任何不支持javascript或关闭javascript的用户代理中被破坏。对于没有javascript的用户代理,progressive enhancement的概念会鼓励为/map/添加一个简单的超链接,然后为支持javascript的用户代理添加一个点击处理程序。

  3. 例如:

    标记:

    <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 errorMinghuan提及了)

但是...

当前看来,这种方法有些过时了,需要进行一些更新。如果有人想成为专业的前端开发人员并编写大型而复杂的应用程序,则他需要使用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)

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

答案 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, '&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')是无效的语法。如果您不替换",那么浏览器会将其解释为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>

enter image description here

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

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