¿Por qué usar onClick () en HTML es una mala práctica?

133

Muchas veces he escuchado que usar eventos JavaScript, como onClick()HTML, es una mala práctica, porque no es bueno para la semántica. Me gustaría saber cuáles son las desventajas y cómo solucionar el siguiente código.

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
NiLL
fuente
44
No hay nada malo con el onlickc, pero al hacer el href #, cualquiera que elija tener JavaScript deshabilitado se quedará atascado y no podrá hacer nada. Para algunos sitios eso es algo bueno, pero para simplemente abrir una ventana, es completamente estúpido no proporcionar un enlace "real".
Marc B
2
Definitivamente lea ese enlace. Tiene muy poco que ver con la semántica, y más que ver con ... todas las cosas en esa página :-)
morewry
Intente abrir su enlace en una nueva pestaña, y verá un ejemplo de por qué está mal ...
Sebastien C.
2
Eso debería ser un <button>, porque se supone que los enlaces apuntan a un recurso real. Es más semántico de esa manera y es más claro para los usuarios de lectores de pantalla.
programmer5000

Respuestas:

171

Probablemente estés hablando de un JavaScript discreto , que se vería así:

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

con la lógica en un archivo javascript central que se parece a esto:

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

Las ventajas son

  • el comportamiento (Javascript) está separado de la presentación (HTML)
  • no mezclar idiomas
  • está utilizando un marco de JavaScript como jQuery que puede manejar la mayoría de los problemas entre navegadores por usted
  • Puede agregar comportamiento a muchos elementos HTML a la vez sin duplicación de código
Michael Borgwardt
fuente
30
Usted ha enumerado algunas ventajas, pero ¿qué pasa con las desventajas? ¿Depuración de humo azul?
abelito
2
@abelito: No estoy seguro de que la depuración sea una desventaja. En todo caso, es una ventaja para la depuración, ya que puede recorrer su JavaScript dentro de cualquier herramienta de depuración del navegador. No estoy seguro de que pueda hacerlo tan fácil si el código está en línea y onClick. También puede escribir pruebas unitarias si lo desea contra sus scripts, lo que también es muy difícil. Supongo que si el código está dentro onClicky no hay lógica separada. Personalmente, no tengo ningún problema para depurar JavaScript discreto y los beneficios de administrar y probar el código JavaScript son muy buenos para no usarlos.
No
45
@ François Wahl: la principal desventaja es la capacidad de detección: mirar el HTML no le dice qué devoluciones de llamada están asociadas, ni siquiera si hay alguna.
Michael Borgwardt
1
@MichaelBorgwardt: Estoy completamente de acuerdo con usted en que es una desventaja. Si el código está bien estructurado y organizado, no lo veo como un gran problema al trabajar en un proyecto o depurar la base de código de otra persona. En general, aunque estoy de acuerdo con usted, no considero que la capacidad de descubrimiento sea una buena razón para escribir secuencias de comandos en línea, los beneficios sacramentales como la capacidad de prueba del código y la capacidad de separar su código de función / comportamiento del DOM. No digo que el código en línea sea malo y no funcione. Lo hace y está absolutamente bien, dependiendo de si está interesado en cosas como la capacidad de prueba o no.
No
44
Otro punto de esta discusión, con los novatos, onclickpuede mapear más explícitamente las relaciones causales entre la interacción del elemento y el efecto (llamada a la función), así como reducir la carga cognitiva. Muchas lecciones arrojan a los alumnos a un addEventListenerpaquete que contiene muchas más ideas dentro de una sintaxis más difícil. Además, los marcos basados ​​en componentes recientes como Riot y React usan el onclickatributo y están cambiando la noción de lo que debería ser la separación. La transferencia de HTML onclicka un componente que admita esto podría ser un proceso de "pasos" más efectivo para el aprendizaje.
jmk2142
41

Si está utilizando jQuery, entonces:

HTML:

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

JS:

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

Esto tiene la ventaja de seguir trabajando sin JS, o si el usuario hace clic en el medio del enlace.

También significa que podría manejar ventanas emergentes genéricas reescribiendo nuevamente a:

HTML:

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

JS:

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

Esto le permitiría agregar una ventana emergente a cualquier enlace simplemente dándole la clase emergente.

Esta idea podría extenderse aún más así:

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;
    });
});

¡Ahora puedo usar el mismo código para muchas ventanas emergentes en todo mi sitio sin tener que escribir un montón de cosas onclick! ¡Yay por la reutilización!

También significa que si más tarde decido que las ventanas emergentes son una mala práctica (¡lo que son!) Y que quiero reemplazarlas con una ventana modal de estilo lightbox, puedo cambiar:

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

a

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

y todas mis ventanas emergentes en todo mi sitio ahora funcionan de manera totalmente diferente. Incluso podría hacer una detección de funciones para decidir qué hacer en una ventana emergente, o almacenar una preferencia de los usuarios para permitirlos o no. Con el clic en línea, esto requiere una gran copia y un esfuerzo de pegado.

Rich Bradshaw
fuente
44
Funciona sin JavaScript? Es jQuery. Necesita Javascript habilitado en el navegador para funcionar, ¿verdad?
Thomas Shields
2
Sí, pero el enlace puede redirigir a una página que realiza la acción sin JavaScript en este caso.
ThiefMaster
12
El enlace funciona sin JavaScript. Vea cómo el enlace tiene un atributo href normal. Si el usuario no tiene JavaScript, el enlace seguirá siendo un enlace y el usuario aún puede acceder al contenido.
morewry
3
@Thomas Shields: No, quiere decir que si no tienes Javascript, el enlace aún te llevará a / map / ...
Robert
2
Ah Rich! ¡Todos te amamos por esta solución más rápida!
Nirmal
21

Con aplicaciones JavaScript muy grandes, los programadores están utilizando más encapsulación de código para evitar contaminar el alcance global. Y para que una función esté disponible para la acción onClick en un elemento HTML, debe estar en el ámbito global.

Es posible que haya visto archivos JS que se parecen a esto ...

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

Estas son expresiones de función invocadas inmediatamente (IIFE) y cualquier función declarada dentro de ellas solo existirá dentro de su alcance interno.

Si declara function doSomething(){}dentro de un IIFE, luego realiza doSomething()la acción onClick de un elemento en su página HTML, obtendrá un error.

Si, por otro lado, crea un EventListener para ese elemento dentro de ese IIFE y llama doSomething()cuando el oyente detecta un evento de clic, es bueno porque el oyente y doSomething()comparte el alcance del IIFE.

Para pequeñas aplicaciones web con una cantidad mínima de código, no importa. Pero si aspira a escribir bases de código grandes y fáciles de mantener, onclick=""es un hábito que debe evitar.

LetMyPeopleCode
fuente
1
si aspira a escribir bases de código grandes y mantenibles, use marcos JS como Angular, Vue, React ... que recomiendan vincular los controladores de eventos dentro de sus plantillas HTML
Kamil Kiełczewski
20

No es bueno por varias razones:

  • mezcla código y marcado
  • el código escrito de esta manera pasa por eval
  • y se ejecuta en el ámbito global

Lo más simple sería agregar un nameatributo a su <a>elemento, luego podría hacer:

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

aunque la mejor práctica moderna sería usar un en idlugar de un nombre, y usar en addEventListener()lugar de usar onclickya que eso le permite vincular múltiples funciones a un solo evento.

Alnitak
fuente
Incluso sugerir esta manera abre al usuario a un montón de problemas con las colisiones de controladores de eventos.
preacción
3
@preaction al igual que lo hace un controlador de eventos en línea, de ahí que mi respuesta diga que es mejor usar addEventListener()y por qué.
Alnitak
2
¿Alguien puede explicar esto más? "el código escrito de esta manera pasa por eval" ... No encuentro nada en la web sobre esto. ¿Estás diciendo que hay alguna desventaja de rendimiento o seguridad al usar Javascript en línea?
MarsAndBack
12

Hay unas pocas razones:

  1. Me parece que ayuda a mantener el marcado separado, es decir, los scripts HTML y del lado del cliente. Por ejemplo, jQuery facilita la adición de controladores de eventos mediante programación.

  2. El ejemplo que proporcione se rompería en cualquier agente de usuario que no sea compatible con JavaScript o que tenga JavaScript desactivado. El concepto de mejora progresiva fomentaría un hipervínculo simple /map/para los agentes de usuario sin javascript, y luego agregaría un controlador de clics de forma protocolaria para los agentes de usuario que admitan javascript.

Por ejemplo:

Margen:

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

Javascript:

$(document).ready(function(){

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

})
Graham
fuente
2
Gracias por recordarme la mejora progresiva y, por lo tanto, esto también se ajusta a ese paradigma:<a href="https://stackoverflow.com/map/" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>
Daniel Sokolowski
10

Revisión

El enfoque discreto de JavaScript fue bueno en el PASADO, especialmente el enlace de controlador de eventos en HTML se consideró una mala práctica (principalmente porque onclick events run in the global scope and may cause unexpected errorlo mencionado por YiddishNinja )

Sin embargo...

Actualmente parece que este enfoque está un poco desactualizado y necesita alguna actualización. Si alguien quiere ser un desarrollador frontend profesional y escribir aplicaciones grandes y complicadas, entonces necesita usar marcos como Angular, Vue.js, etc. Sin embargo, esos marcos generalmente usan (o permiten usar) plantillas HTML donde los controladores de eventos están vinculados en el código html-template directamente y esto es muy útil, claro y efectivo, por ejemplo, en la plantilla angular la gente suele escribir:

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

En raw js / html el equivalente de esto será

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

La diferencia es que en el onclickevento js sin procesar se ejecuta en el ámbito global, pero los marcos proporcionan la encapsulación.

¿Entonces, dónde está el problema?

El problema es cuando un programador novato que siempre escuchó que html-onclick es malo y que siempre usa btn.addEventListener("onclick", ... )quiere usar un marco con plantillas ( addEventListenertambién tiene inconvenientes , si actualizamos DOM usando dinámicamente innerHTML=(lo cual es bastante rápido ), entonces perdemos eventos los manejadores se unen de esa manera). Luego se enfrentará a algo como malos hábitos o enfoque incorrecto para el uso del marco, y usará el marco de una manera muy mala, porque se centrará principalmente en la parte js y no en la parte de la plantilla (y producirá poco claro y difícil de mantener código). Para cambiar estos hábitos, perderá mucho tiempo (y probablemente necesitará algo de suerte y un maestro).

En mi opinión, según la experiencia con mis alumnos, sería mejor para ellos si utilizan html-handlers-bind al principio. Como digo, es cierto que los controladores son de alcance global, pero en esta etapa los estudiantes generalmente crean pequeñas aplicaciones que son fáciles de controlar. Para escribir aplicaciones más grandes, eligen algunos marcos.

¿Entonces lo que hay que hacer?

Podemos ACTUALIZAR el enfoque de JavaScript discreto y permitir controladores de eventos de enlace (eventualmente con parámetros simples) en html (pero solo controlador de enlace, no poner lógica en onclick como en la pregunta OP). Entonces, en mi opinión en js / html sin procesar, esto debería permitirse

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

o

Pero los ejemplos a continuación NO deberían permitirse

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

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

La realidad cambia, nuestro punto de vista también debería

Kamil Kiełczewski
fuente
hola Kamil, soy un principiante en JavaScript y también me enfrenté a la confusión sobre el uso de onclick, es decir, puedes explicar las desventajas de onclick de la siguiente manera: 1. Solo puedes tener un evento en línea asignado. 2.Los eventos en línea se almacenan como una propiedad del elemento DOM y, como todas las propiedades del objeto, se pueden sobrescribir. 3.Este evento continuará disparándose incluso si elimina la propiedad onclick.
mirzhal
1
Anuncio 1. Sí, solo uno, sin embargo, mi experiencia (con angular) muestra que esto es suficiente en la mayoría de las situaciones; sin embargo, si no es así, solo úselo addEventListener. Anuncio 2. Sí, pueden sobrescribirse (aunque generalmente nadie lo hace): ¿dónde está el problema? Anuncio 3. El controlador no se activará después de eliminar onclick attrib - prueba aquí
Kamil Kiełczewski
8

Es un nuevo paradigma llamado " JavaScript discreto ". El "estándar web" actual dice separar la funcionalidad y la presentación.

No es realmente una "mala práctica", es solo que la mayoría de los estándares nuevos quieren que uses oyentes de eventos en lugar de JavaScript en línea.

Además, esto puede ser algo personal, pero creo que es mucho más fácil de leer cuando usa oyentes de eventos, especialmente si tiene más de 1 declaración de JavaScript que desea ejecutar.

Cohete Hazmat
fuente
2
La página de Wikipedia sobre el tema tiene más de 4 años. Ya no es un nuevo paradigma. :)
Quentin
@David: Puede que no sea "nuevo", pero todavía hay personas que no lo usan, por lo que es "nuevo" para ellos.
Rocket Hazmat
6

Su pregunta desencadenará la discusión, supongo. La idea general es que es bueno separar el comportamiento y la estructura. Además, afaik, un controlador de clics en línea tiene que ser eval'convertido' en una verdadera función de JavaScript. Y es bastante anticuado, aunque ese es un argumento bastante inestable. Ah, bueno, lee algo al respecto @ quirksmode.org

KooiInc
fuente
No quiero crear una vez más la guerra santa, estoy tratando de encontrar la verdad: \
NiLL
2
  • Los eventos onclick se ejecutan en el ámbito global y pueden causar un error inesperado.
  • Agregar eventos onclick a muchos elementos DOM disminuirá el
    rendimiento y la eficiencia.
Minghuan
fuente
0

Dos razones más para no usar controladores en línea:

Pueden requerir problemas tediosos de escape de cotizaciones

Dada una arbitraria cadena, si usted quiere ser capaz de construir un controlador de línea que llama a una función con la cadena, por la solución general, que tendrá que escapar de los atributos delimitadores (con la entidad HTML asociado), y se le tiene que escapar del delimitador utilizado para la cadena dentro del atributo, como el siguiente:

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>'
);

Eso es increíblemente feo. En el ejemplo anterior, si no reemplazó la 's, se produciría un SyntaxError, porque alert('foo'"bar')no es una sintaxis válida. Si no reemplazó el "s, entonces el navegador lo interpretaría como un fin al onclickatributo (delimitado con "s arriba), que también sería incorrecto.

Si uno usa habitualmente manipuladores en línea, debe asegurarse de recordar hacer algo similar a lo anterior (y hacerlo bien ) cada vez , lo cual es tedioso y difícil de entender de un vistazo. Es mejor evitar los controladores en línea por completo para que la cadena arbitraria se pueda usar en un cierre simple:

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);

¿No es eso mucho mejor?


La cadena de alcance de un controlador en línea es extremadamente peculiar

¿Qué crees que registrará el siguiente código?

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

Pruébalo, ejecuta el fragmento. Probablemente no sea lo que estabas esperando. ¿Por qué produce lo que hace? Porque los manejadores en línea se ejecutan dentro de withbloques. El código anterior está dentro de tres with bloques: uno para el document, uno para el <form>y otro para el <button>:

ingrese la descripción de la imagen aquí

Como disabledes una propiedad del botón, la referencia disableddentro del controlador en línea se refiere a la propiedad del botón, no a la disabledvariable externa . Esto es bastante contrario a la intuición. withtiene muchos problemas: puede ser la fuente de errores confusos y ralentiza significativamente el código. Ni siquiera está permitido en modo estricto. Pero con los controladores en línea, se ve obligado a ejecutar el código a través de withs, y no solo a través de uno with, sino a través de múltiples withs anidados . Es una locura.

withnunca debe usarse en código. Debido a que los controladores en línea requieren implícitamente withjunto con todo su comportamiento confuso, también deben evitarse los controladores en línea.

Cierto rendimiento
fuente