javascript - ¿Cómo funciona exactamente <script defer="defer">?
Tengo algunos elementos <script>, y el código en algunos de ellos depende del código en otros elementos <script>. Vi que el atributo defer puede ser útil aquí ya que permite posponer la ejecución de los bloques de código.
Para probarlo ejecuté esto en Chrome: http://jsfiddle.net/xXZMN/.
<script defer="defer">alert(2);</script>
<script>alert(1)</script>
<script defer="defer">alert(3);</script>
Sin embargo, muestra una alerta con 2 - 1 - 3. ¿Por qué no muestra una alerta con 1 - 2 - 3?
Respuesta #1
Algunos fragmentos de la especificación de HTML5: http://w3c.github.io/html/semantics-scripting.html#element-attrdef-script-async
Los atributos defer y async no deben especificarse si el atributo src no está presente.
Hay tres modos posibles que se pueden seleccionar usando estos atributos [async y defer]. Si el atributo async está presente, entonces el script se ejecutará de forma asincrónica, tan pronto como esté disponible. Si el atributo async no está presente pero el atributo defer sí lo está, entonces el script se ejecuta cuando la página ha terminado de analizarse. Si ninguno de los atributos está presente, entonces el script se obtiene y se ejecuta inmediatamente, antes de que el agente de usuario continúe analizando la página.
Los detalles exactos del procesamiento de estos atributos son, por razones principalmente históricas, algo no triviales, e involucran una serie de aspectos de HTML. Por lo tanto, los requisitos de implementación están necesariamente dispersos a lo largo de la especificación. Los algoritmos a continuación (en esta sección) describen el núcleo de este procesamiento, pero estos algoritmos hacen referencia y son referenciados por las reglas de análisis para etiquetas de inicio y fin de scripts en HTML, en contenido extranjero y en XML, las reglas para el método document.write(), el manejo de scripting, etc.
Si el elemento tiene un atributo src, y el elemento tiene un atributo defer, y el elemento ha sido marcado como "insertado por el analizador", y el elemento no tiene un atributo async:
El elemento debe añadirse al final de la lista de scripts que se ejecutarán cuando el documento haya terminado de analizarse, asociado con el Documento del analizador que creó el elemento.
Respuesta #2
La verdadera respuesta es: Porque no puedes confiar en defer.
En concepto, defer y async difieren de la siguiente manera:
async permite que el script se descargue en segundo plano sin bloquear. Luego, en el momento en que termina de descargarse, el renderizado se bloquea y ese script se ejecuta. El renderizado se reanuda cuando el script ha terminado de ejecutarse.
defer hace lo mismo, excepto que afirma garantizar que los scripts se ejecuten en el orden en que fueron especificados en la página y que se ejecutarán después de que el documento haya terminado de analizarse. Por lo tanto, algunos scripts pueden terminar de descargarse y luego esperar a los scripts que se descargaron después pero que aparecieron antes que ellos.
Desafortunadamente, debido a lo que realmente es una pelea de estándares, la definición de defer varia de especificación en especificación, e incluso en las especificaciones más recientes no ofrece una garantía útil. Como demuestran las respuestas aquí y este problema, los navegadores implementan defer de manera diferente:
- En ciertas situaciones, algunos navegadores tienen un error que hace que los scripts
deferse ejecuten fuera de orden. - Algunos navegadores retrasan el evento
DOMContentLoadedhasta que se han cargado los scripts condefer, y algunos no. - Algunos navegadores obedecen
deferen elementos<script>con código inline y sin un atributosrc, y algunos lo ignoran.
Afortunadamente, la especificación al menos indica que async anula defer. Por lo tanto, puedes tratar todos los scripts como async y obtener un amplio soporte de navegador de la siguiente manera:
<script defer async src="..."></script>
El 98 % de los navegadores en uso en todo el mundo y el 99 % en EE. UU. evitarán el bloqueo con este enfoque.
(Si necesitas esperar hasta que el documento haya terminado de analizarse, escucha el evento DOMContentLoaded o usa la práctica función .ready() de jQuery. De todos modos, querrías hacer esto para proporcionar una alternativa adecuada en navegadores que no implementan defer en absoluto.)
Respuesta #3
ACTUALIZADO: 19/2/2016
Considera esta respuesta desactualizada. Consulta otras respuestas en esta publicación para obtener información relevante a versiones más recientes del navegador.
Básicamente, defer le dice al navegador que espere «hasta que esté listo» antes de ejecutar el javascript en ese bloque de script. Por lo general, esto sucede después de que el DOM ha terminado de cargarse y document.readyState == 4
El atributo defer es específico de Internet Explorer. En Internet Explorer 8, en Windows 7, el resultado que estoy viendo en tu página de prueba de JS Fiddle es, 1 - 2 - 3.
Los resultados pueden variar de un navegador a otro.
http://msdn.microsoft.com/en-us/library/ms533719(v=vs.85).aspx
Contrario a la creencia popular, IE sigue los estándares más a menudo de lo que la gente admite; en realidad, el atributo "defer" está definido en la especificación DOM Nivel 1 http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html
La definición de defer del W3C: http://www.w3.org/TR/REC-html40/interact/scripts.html#adef-defer:
Cuando se establece, este atributo booleano proporciona una pista al agente de usuario de que el script no va a generar ningún contenido del documento (por ejemplo, no hay "document.write" en JavaScript) y, por lo tanto, el agente de usuario puede continuar analizando y renderizando.
Respuesta #4
defer solo puede usarse en la etiqueta <script> para la inclusión de scripts externos. Por lo tanto, se recomienda usarlo en las etiquetas <script> en la sección <head>.
Respuesta #5
El atributo defer funciona solo con la etiqueta script con src. Se encontró una manera de imitar defer para scripts en línea. Usa el evento DOMContentLoaded.
<script defer src="external-script.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
// Your inline scripts which uses methods from external-scripts.
});
</script>
Esto se debe a que el evento DOMContentLoaded se dispara después de que los scripts con el atributo defer se han cargado completamente.
