Nota del traductor: soy la primera vez que traducí un idioma extranjero, y mis palabras son inevitablemente un poco oscuras, pero hice todo lo posible para expresar la intención original del autor y no tenía mucho polaco. Las críticas y la corrección son bienvenidos. Además, este artículo es largo y tiene una gran cantidad de información, que puede ser difícil de digerir. Deje un mensaje para discutir los detalles. Este artículo se centra principalmente en la optimización del rendimiento de V8, y parte del contenido no es aplicable a todos los motores JS. Finalmente, indique la fuente al reimprimir :)
========================================================================================================================
Muchos motores JavaScript, como el motor V8 de Google (utilizado por Chrome y Node), están diseñados específicamente para grandes aplicaciones JavaScript que requieren una ejecución rápida. Si es un desarrollador y le preocupa el uso de la memoria y el rendimiento de la página, debe comprender cómo funciona el motor JavaScript en su navegador. Ya sea V8, Spidermonkey (Firefox) Carakan (Opera), Chakra (IE) u otros motores, hacerlo puede ayudarlo a optimizar mejor su aplicación . Esto no significa que deba optimizar específicamente para un determinado navegador o motor, y nunca hacer esto.
Sin embargo, debe hacerse algunas preguntas:
Un sitio web de carga rápida es como un automóvil deportivo rápido que requiere piezas especialmente personalizadas. Fuente de la imagen: Dybridcars.
Hay algunas dificultades comunes al escribir código de alto rendimiento, y en este artículo mostraremos algunas formas probadas y mejores de escribir código.
Si no tiene una comprensión profunda de los motores JS, no hay problemas para desarrollar una aplicación web grande, al igual que una persona que puede conducir solo ha visto el capó pero no el motor dentro del capó. Dado que Chrome es la primera opción de mi navegador, hablemos de su motor JavaScript. V8 está compuesto por las siguientes partes centrales:
La recolección de basura es una forma de gestión de la memoria , que en realidad es un concepto de coleccionista, que intenta reciclar la memoria ocupada por objetos que ya no se usan. En un lenguaje de recolección de basura como JavaScript, los objetos a los que aún se están haciendo referencia en la aplicación no se borrarán.
La eliminación manual de las referencias de objetos no es necesaria en la mayoría de los casos. Todo funcionará bien simplemente colocando las variables donde se necesitan (idealmente, lo más localmente posible, es decir, la función que se usan en lugar de la capa externa de la función).
El recolector de basura intenta reciclar la memoria. Fuente de la imagen: Valtteri Mäki.
En JavaScript, es imposible forzar la recolección de basura. No debe hacer esto porque el proceso de recolección de basura está controlado por el tiempo de ejecución y sabe cuál es el mejor momento para limpiar.
Hay muchas discusiones sobre el reciclaje de memoria de JavaScript en Internet sobre la palabra clave Eliminar. Aunque puede usarse para eliminar los atributos (teclas) en los objetos (MAP), algunos desarrolladores creen que puede usarse para forzar "deserferencias". Se recomienda evitar usar Eliminar siempre que sea posible. En el ejemplo a continuación delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x: 1}; eliminar buey; // verdadero buey; // indefinidoEncontrará fácilmente la eliminación de referencias en las bibliotecas JS populares: esto tiene un propósito lingüístico. Cabe señalar aquí que evita modificar la estructura del objeto "caliente" en tiempo de ejecución. El motor JavaScript puede detectar tales objetos "calientes" e intentar optimizarlos. Si la estructura del objeto no cambia significativamente durante el ciclo de vida, el motor será más fácil para optimizar el objeto, y la operación de eliminación en realidad desencadenará este cambio estructural más grande, que no conduce a la optimización del motor.
También hay malentendidos sobre cómo funciona Null. Configurar una referencia de objeto a NULL no hace que el objeto sea "vacío", solo establece su referencia a vacío. Usar ox = nulo es mejor que usar Eliminar, pero puede no ser necesario.
var o = {x: 1}; o = nulo; o; // nullo.x // typeErrorSi esta referencia es la última referencia al objeto actual, el objeto se recolectará basura. Si esta referencia no es la última referencia al objeto actual, el objeto es accesible y no se recolectará basura.
También se debe tener en cuenta que el recolector de basura no limpia las variables globales durante el ciclo de vida de la página. No importa cuánto tiempo esté abierto la página, las variables en el alcance del objeto global siempre existirán cuando se ejecute JavaScript.
var myglobalnamespace = {};Los objetos globales solo se limpiarán al actualizar la página, navegar a otra página, cerrar la pestaña o salir del navegador. Las variables en el alcance de la función se limpiarán cuando estén fuera de alcance, es decir, cuando la función sale, no hay referencias y tales variables se limpiarán.
Para que el recolector de basura recolecte tantos objetos como sea posible, no sostenga objetos que ya no se usen . Aquí hay algunas cosas para recordar:
A continuación, hablemos de funciones. Como ya hemos dicho, la recolección de basura funciona al reciclar bloques de memoria (objetos) que ya no son accesibles. Para ilustrar esto mejor, aquí hay algunos ejemplos.
function foo () {var bar = new BoundObject (); bar.somecall ();}Cuando Foo regrese, el objeto señalado por bar será reciclado automáticamente por el recolector de basura porque no tiene referencias existentes.
Comparar:
function foo () {var bar = new BoundObject (); bar.somecall (); Barra de retorno;} // en algún lugar Elsevar b = foo ();Ahora tenemos una referencia que apunta al objeto de la barra, de modo que el ciclo de vida del objeto de la barra continúa desde la llamada a Foo hasta que la persona que llama especifica otra variable B (o B está fuera de alcance).
Cuando vea una función, devuelva una función interna, que saldrá del acceso al alcance, incluso después de ejecutar la función externa. Este es un cierre básico: una expresión de variables que se pueden establecer en un contexto específico. Por ejemplo:
función sum (x) {function sumit (y) {return x + y; }; return sumit;} // useVar suma = sum (4); var sumb = suma (3); console.log (sumb); // Devuelve 7El objeto de función (SUMIT) generado en el contexto de llamadas de suma no puede reciclarse. La variable global (suma) se hace referencia y puede llamarse a través de suma (n).
Echemos un vistazo a otro ejemplo, ¿dónde podemos acceder a la variable más grande?
var a = function () {var lateGestr = new Array (1000000) .Join ('x'); function de return () {return grandegestr; };} ();Sí, podemos acceder a Grangestr a través de A (), por lo que no se recicla. ¿Qué pasa con lo siguiente?
var a = function () {var smallstr = 'x'; var más grande = nueva matriz (1000000) .Join ('x'); función de retorno (n) {return smallstr; };} ();Ya no podemos acceder a Grangestr, ya es un candidato a la recolección de basura. [Nota del traductor: porque LAYGERR ya no tiene referencias externas]
Uno de los peores lugares para filtrar la memoria está en el bucle, o en setTimeout ()/setInterval (), pero esto es bastante común. Piense en los siguientes ejemplos:
var myobj = {callMemaybe: function () {var myRef = this; var val = setTimeOut (function () {console.log ('¡El tiempo se está agotando!'); myref.callmemaybe ();}, 1000); }}; Si ejecutamos myobj.callmemaybe (); Para comenzar el temporizador, podemos ver que la consola imprime "¡El tiempo se está agotando!" uno de cada dos. Si myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
También vale la pena recordar que las referencias en las llamadas de SetTimeOut/SetInterval (como las funciones) deberán ser ejecutadas y completadas antes de que puedan ser recolectadas de basura.
Nunca optimice el código hasta que realmente lo necesite. Ahora, a menudo puede ver algunos puntos de referencia que muestran que N está más optimizado en V8 que en M, pero si lo prueba en código o aplicación de módulo, encontrará que estas optimizaciones son realmente mucho más pequeñas de lo que espera.
Es mejor no hacer nada que hacerlo demasiado. Fuente de la imagen: Tim Sheerman-Chase.
Por ejemplo, queremos crear dicho módulo:
Hay varios factores diferentes en este problema, aunque también es fácil de resolver. ¿Cómo almacenamos datos, cómo dibujamos de manera eficiente las tablas y las agregamos al DOM, y cómo manejamos mejor los eventos de tabla?
El enfoque inicial (ingenuo) para enfrentar estos problemas es usar objetos para almacenar datos y ponerlos en una matriz, usar jQuery para atravesar los datos para dibujar una tabla y agregarlo al DOM, y finalmente usar el enlace de eventos con el comportamiento de clic que esperamos.
Nota: Esto no es lo que debes hacer
var modulea = function () {return {data: dataArrayObject, init: function () {this.addtable (); this.addevents (); }, addTable: function () {for (var i = 0; i <shows; i ++) {$ tr = $ ('<tr> <tr>'); for (var j = 0; j <this.data.length; j ++) {$ tr.append ('<td>' + this.data [j] ['id'] + '</td>'); } $ tr.appendto ($ tbody); }}, addEvents: function () {$ ('Tabla td'). on ('click', function () {$ (this) .toggleclass ('activo');}); }};} ();Este código completa de manera simple y efectiva la tarea.
Pero en este caso, los datos que atravesamos son solo una ID de propiedad numérica que debería haberse almacenado simplemente en la matriz. Curiosamente, es mejor usar DocumentFragment y los métodos DOM locales directamente que generar tablas utilizando jQuery (de esta manera) y, por supuesto, el proxy de eventos tiene un mayor rendimiento que unir cada TD solo.
Tenga en cuenta que aunque JQuery usa DocumentFragment internamente, en nuestro ejemplo, el código llama a agregar dentro de un bucle y estas llamadas involucran algún otro pequeño conocimiento, por lo que el efecto de optimización aquí no es muy bueno. Esperemos que esto no sea un punto de dolor, pero asegúrese de hacer un punto de referencia para asegurarse de que su código esté bien.
Para nuestro ejemplo, las prácticas anteriores traen mejoras de rendimiento (deseadas). El proxy de eventos es una mejora con la vinculación simple, y el DocumentFragment opcional también ayuda.
var modulado = function () {return {data: dataArray, init: function () {this.addtable (); this.addevents (); }, addTable: function () {var td, tr; var fragment = document.CreateDocumentFragment (); var fragment2 = document.createDocumentFragment (); for (var i = 0; i <shows; i ++) {tr = document.createElement ('tr'); for (var j = 0; j <this.data.length; j ++) {td = document.createElement ('td'); td.appendChild (document.createTextNode (this.data [j])); frag2.appendchild (TD); } tr.appendChild (frag2); frag.appendchild (tr); } tbody.appendChild (frag); }, addEvents: function () {$ ('tabla'). on ('hacer clic', 'td', function () {$ (this) .TogglecLass ('activo');}); }};} ();Echemos un vistazo a otras formas de mejorar el rendimiento. Es posible que haya leído que usar el modo prototipo es mejor que el modo de módulo, o ha escuchado que el uso de marcos de plantilla JS funciona mejor. A veces esto es cierto, pero se usan para hacer que el código sea más legible. Por cierto, ¡hay precompilación! Veamos cómo funciona en la práctica.
moduleg = function () {}; moduleg.prototype.data = dataArray; moduleg.prototype.init = function () {this.addtable (); this.AdDevents ();}; moduleg.prototype.addtable = function () {var platplate = _.template ($ ('#template'). text ()); var html = Template ({'data': this.data}); $ tbody.append (html);}; moduleg.prototype.addevents = function () {$ ('table'). on ('click', 'td', function () {$ (this) .ToggleClass ('activo');});}; var modg = new Moduleg ();Resulta que las mejoras de rendimiento que traen en esta situación son insignificantes. La elección de plantillas y prototipos realmente no ofrece nada más. Es decir, el rendimiento no es la razón por la cual los desarrolladores los usan, y la legibilidad, el modelo de herencia y la capacidad de mantenimiento aportadas al código son las razones reales.
Los problemas más complejos incluyen dibujar imágenes de manera eficiente sobre el lienzo y manipular los datos de píxeles con o sin matrices de tipos.
Antes de usar algunos métodos para su propia aplicación, asegúrese de obtener más información sobre la evaluación comparativa de estas soluciones. Tal vez alguien todavía recuerda la excursión y las extensiones posteriores de la plantilla JS. Debe averiguar que la evaluación comparativa no existe en aplicaciones virtuales que no pueda ver, pero debe probar las optimizaciones traídas por su código real.
Los puntos de optimización de cada motor V8 se introducen en detalle fuera del alcance de este artículo. Por supuesto, hay muchos consejos que vale la pena mencionar aquí. Recuerde estos consejos y puede reducir el código que tiene un bajo rendimiento.
función add (x, y) {return x+y;} add (1, 2); agregar ('a', 'b'); agregar (my_custom_object, indefinido);Para obtener más contenido, consulte Daniel Clifford's Compartir en Google E/S. Romper el límite de velocidad de JavaScript con V8. Optimización para V8: también vale la pena leer una serie.
Solo hay una diferencia principal entre objetos y matrices en JavaScript, es decir, la propiedad de longitud mágica de las matrices. Si mantiene esta propiedad usted mismo, entonces los objetos y las matrices en V8 son tan rápidos como los de las matrices.
La clonación de objetos es un problema común para los desarrolladores de aplicaciones. Si bien varios puntos de referencia pueden probar que V8 maneja bien este problema, tenga cuidado. Copiar cosas grandes suele ser más lenta, no hagas eso. El para ... en bucle en JS es especialmente malo porque tiene una especificación demoníaca y puede que nunca sea más rápido que ningún objeto en ningún motor.
Cuando esté seguro de copiar objetos en la ruta del código de rendimiento crítico, use una matriz o una función de "constructor de copias" personalizada para copiar explícitamente cada propiedad. Esta es probablemente la forma más rápida:
function clone (original) {this.foo = original.foo; this.bar = original.bar;} var copy = new Clone (original);Las funciones de almacenamiento en caché al usar el modo de módulo pueden conducir a mejoras de rendimiento. Vea el ejemplo a continuación, porque siempre crea una nueva copia de la función miembro, los cambios que ve pueden ser más lentos.
También tenga en cuenta que usar este método es obviamente mejor, no solo confiar en el modo prototipo (confirmado por la prueba JSPERF).
Mejoras de rendimiento al usar el modo de módulo o el modo prototipo
Esta es una prueba de comparación de rendimiento del modo prototipo y el modo de módulo:
// patrón prototipo klass1 = function () {} klass1.prototype.foo = function () {log ('foo'); } Klass1.prototype.bar = function () {log ('bar'); } // Patrón de módulo klass2 = function () {var foo = function () {log ('foo'); }, bar = function () {log ('bar'); }; return {foo: foo, bar: bar}} // patrón de módulo con funciones en caché var foofunction = function () {log ('foo'); }; var barfunction = function () {log ('bar'); }; Klass3 = function () {return {foo: foofunction, bar: barfunction}} // pruebas de iteración // prototypal var i = 1000, objs = []; while (i--) {var o = new Klass1 () objs.push (new Klass1 ()); O.Bar; o.foo; } // Patrón de módulo var i = 1000, objs = []; while (i--) {var o = new Klass1 () objs.push (new Klass1 ()); O.Bar; o.foo; } // Patrón de módulo var i = 1000, objs = []; while (i--) {var o = klass2 () objs.push (klass2 ()); O.Bar; o.foo; } // Patrón de módulo con funciones en caché var i = 1000, objs = []; while (i--) {var o = klass3 () objs.push (klass3 ()); O.Bar; o.foo; } // Consulte la prueba de detalles completosA continuación, hablemos sobre las técnicas relacionadas con las matrices. En general, no elimine los elementos de matriz , lo que hará la transición de la matriz a una representación interna más lenta. Cuando el índice se vuelve escaso, V8 convertirá el elemento en un patrón de diccionario más lento.
Los literales de matriz son muy útiles, y puede insinuar el tamaño y el tipo de matriz VM. Por lo general, se usa en matrices con tamaños pequeños.
// Aquí V8 puede ver que desea una matriz de 4 elementos que contengan números: var a = [1, 2, 3, 4]; // No hagas esto: a = []; // Aquí v8 no sabe nada sobre la matriz (var i = 1; i <= 4; i ++) {a.push (i);}De ninguna manera es una buena idea almacenar datos de tipos mixtos (como números, cadenas, indefinido, verdadero/falso) en una matriz. Por ejemplo, var arr = [1, "1", indefinido, verdadero, "verdadero"]
Prueba de rendimiento de la inferencia de tipo
Como hemos visto, las matrices de enteros son los más rápidos.
Cuando use matrices dispersas, tenga cuidado de acceder a los elementos mucho más lentos que las matrices completas. Porque V8 no asignará una pieza completa de espacio a una matriz que solo usa parte del espacio. En cambio, se gestiona en un diccionario, ahorrando tanto espacio pero tomando el tiempo para acceder.
Prueba de matrices dispersas y matrices completas
No prealocen grandes matrices (como elementos mayores de 64k), su tamaño máximo, pero debe asignarse dinámicamente. Antes de probar nuestro rendimiento en este artículo, recuerde que esto se aplica solo a algunos motores JavaScript.
Se prueban literales vacíos y matrices previamente asignadas en diferentes navegadores
Nitro (Safari) es más beneficioso para las matrices previamente asignadas. En otros motores (V8, Spidermonkey), la previa asignación no es eficiente.
Prueba de matriz preellocada
// vacío ArrayVar arr = []; for (var i = 0; i <1000000; i ++) {arr [i] = i;} // arrayvar arryvar arr = nueva matriz (1000000); para (var i = 0; i <1000000; i ++) {arr [i] = i;}En el mundo de las aplicaciones web, la velocidad lo es todo. Ningún usuario desea usar una aplicación de tabla que tarda segundos en calcular el número total de una columna o para resumir la información. Esta es una razón importante por la que desea obtener todo el rendimiento en su código.
Fuente de la imagen: Per Olof Forsberg.
Comprender y mejorar el rendimiento de su aplicación es muy útil, pero también es difícil. Recomendamos los siguientes pasos para resolver los puntos de dolor de rendimiento:
Algunas de las herramientas y técnicas recomendadas a continuación pueden ayudarlo.
Hay muchas maneras de ejecutar un punto de referencia para los fragmentos de código JavaScript para probar su rendimiento; la suposición general es que el punto de referencia simplemente compara dos marcas de tiempo. Este patrón es señalado por el equipo JSPERF y se usa en la suite de referencia de Sunspider y Kraken:
var en total tiempo, inicio = nueva fecha, iterations = 1000; while (iterations--) {// El fragmento de código va aquí} // Total Time → el número de millones de segundos tomados // para ejecutar el fragmento de código 1000 Timestotaltime = nueva fecha-inicio;Aquí, el código a probar se coloca en un bucle y ejecuta un número establecido de veces (por ejemplo, 6 veces). Después de esto, la fecha de inicio se resta de la fecha de finalización, y se deriva el tiempo que llevó realizar la operación en el bucle.
Sin embargo, esta evaluación comparativa hace cosas que son demasiado simples, especialmente si desea ejecutar puntos de referencia en múltiples navegadores y entornos. El recolector de basura en sí tiene un cierto impacto en los resultados. Incluso si usa una solución como Window. Performance, estas deficiencias deben tenerse en cuenta.
Independientemente de si solo ejecuta la parte de referencia del código, escriba un conjunto de pruebas o codifique la biblioteca de referencia, los puntos de referencia de JavaScript son en realidad más de lo que piensa. Para obtener puntos de referencia de guía más detallados, le recomiendo que lea los puntos de referencia JavaScript proporcionados por Mathias Bynens y John-David Dalton.
Las herramientas de desarrollador de Chrome tienen un buen soporte para JavaScript Analytics. Puede usar esta función para detectar qué funciones ocupan la mayor parte del tiempo para que pueda optimizarlas. Esto es importante, incluso los pequeños cambios en el código pueden tener un impacto significativo en el rendimiento general.
Panel de análisis de herramientas de desarrollador de Chrome
El proceso de análisis comienza a obtener la línea de base del rendimiento del código y luego lo manifiesta en forma de línea de tiempo. Esto nos dirá cuánto tiempo llevará el código para ejecutarse. La pestaña Perfiles nos da una mejor perspectiva de lo que está sucediendo en la aplicación. Los archivos de análisis de la CPU JavaScript muestran cuánto tiempo de CPU se usa en nuestro código, los archivos de análisis de selección CSS muestran cuánto tiempo se dedica a los selectores de procesamiento, y las instantáneas de TADE muestran cuánta memoria se está utilizando en nuestros objetos.
Con estas herramientas, podemos separar, ajustar y volver a analizar para medir si nuestras optimizaciones de rendimiento funcionales u operativas son realmente efectivas.
La pestaña de perfil muestra información de rendimiento del código.
Una buena introducción al análisis, lea el perfil JavaScript de Zack GrossBart con las herramientas de desarrollador de Chrome.
Consejo: Idealmente, si desea asegurarse de que su análisis no se vea afectado por ninguna aplicación o extensiones instaladas, puede usar el indicador --user-data-dir <empty_directory> para iniciar Chrome. En la mayoría de los casos, esta prueba de optimización del método debe ser suficiente, pero también lleva más tiempo para usted. Esto es lo que el logotipo V8 puede ayudar.
Dentro de Google, las herramientas de desarrollador de Chrome son ampliamente utilizadas por equipos como Gmail para ayudar a detectar y solucionar problemas de memoria.
Estadísticas de memoria en herramientas de desarrollador de Chrome
La memoria cuenta el uso de la memoria privada, el tamaño del montón de JavaScript, el número de nodos DOM, la limpieza de almacenamiento, los contadores de escucha de eventos y los recolectores de basura que preocupa a nuestro equipo. Lectura recomendada La tecnología "3 Snapshot" de Loreena Lee. El punto clave de esta técnica es registrar algún comportamiento en su aplicación, forzar la recolección de basura, verificar si el número de nodos DOM se ha restaurado a la línea de base esperada y luego analizar las instantáneas de los tres montones para determinar si hay una fuga de memoria.
La gestión de la memoria de aplicaciones de una sola página (como AngularJS, Backbone, Ember) es muy importante, casi nunca actualizan la página. Esto significa que las filtraciones de memoria pueden ser bastante obvias. Las aplicaciones de una sola página en terminales móviles están llenas de dificultades porque el dispositivo tiene memoria limitada y ejecuta aplicaciones como clientes de correo electrónico o redes sociales durante mucho tiempo. Cuanto mayor sea la capacidad, más pesada es la responsabilidad.
Hay muchas formas de resolver este problema. En la columna vertebral, asegúrese de usar Dispose () para manejar las vistas y referencias antiguas (actualmente disponibles en Backbone (borde). Esta función se agrega recientemente, eliminando el controlador agregado al objeto "Event" de la vista, y el oyente de eventos a través del modelo o colección del tercer parámetro (contexto de devolución de llamada) pasó a la vista. Dispose () también se llamará por la vista Eliminar (), manejar el trabajo principal de la limpieza cuando el elemento de la devolución de llamada) pase a la vista. El oyente para evitar fugas de memoria cuando detectan que se eliminan los elementos.
Algunos consejos sabios de Derick Bailey:
En lugar de comprender cómo funcionan los eventos y las referencias, siga las reglas estándar para administrar la memoria en JavaScript. Si desea cargar datos en una colección de columna vertebral llena de objetos de usuario, desea borrar la colección para que ya no tome memoria, entonces se requieren todas las referencias a la colección y referencias a los objetos de la colección. Una vez que la referencia utilizada es clara, el recurso se recicla. Estas son las reglas estándar de recolección de basura de JavaScript.
En el artículo, Derick cubre muchas fallas de memoria comunes cuando se usa Backbone.js y cómo resolver estos problemas.
También vale la pena leer el tutorial sobre las filtraciones de memoria de depuración en el nodo de Felix Geisendörfer, especialmente cuando forma parte de una pila de spa más amplia.
Cuando el navegador vuelve a renderizar elementos en un documento, deben recalcularse y sus posiciones y geometría, que llamamos reflujo. La reflujo bloquea las operaciones de los usuarios en el navegador, por lo que es muy útil comprender que mejorar el tiempo de reflujo.
Gráfico de tiempo de reflujo
Debe activar el reflujo o volver a dibujar en lotes, pero use estos métodos con moderación. También es importante tratar de no tratar con DOM. Puede usar DocumentFragment, un objeto de documento ligero. Puede usarlo como una forma de extraer parte del árbol de documentos, o crear un nuevo documento "fragmento". En lugar de agregar constantemente nodos DOM, es mejor realizar operaciones de inserción DOM solo una vez después de usar el fragmento de documento para evitar el reflujo excesivo.
Por ejemplo, escribimos una función para agregar 20 divs a un elemento. Si simplemente agrega un DIV al elemento cada vez, esto desencadenará 20 reflotes.
función addDivs (elemento) {var div; for (var i = 0; i <20; i ++) {div = document.createElement ('div'); div.innerhtml = '¡Heya!'; elemento.appendChild (div); }}Para resolver este problema, podemos usar DocumentFragment, en su lugar, podemos agregarle un nuevo DIV a la vez. Agregar DocumentFragment al DOM después de la finalización solo activará un reflujo una vez.
función addDivs (elemento) {var div; // Crea un nuevo documento de documento vacío. var fragment = document.CreateDocumentFragment (); for (var i = 0; i <20; i ++) {div = document.createElement ('a'); div.innerhtml = '¡Heya!'; Fragment.appendChild (div); } elemento.appendChild (fragmento);}Consulte Make the Web más rápido, la optimización de la memoria de JavaScript y la búsqueda de fugas de memoria.
Para ayudar a descubrir fugas de memoria de JavaScript, los desarrolladores de Google (Marja Hölttä y Jochen Eisinger) desarrollaron una herramienta que funciona junto con las herramientas de desarrolladores de Chrome para recuperar instantáneas del montón y detectar qué objetos están causando la fuga de memoria.
Una herramienta de detección de fugas de memoria de JavaScript
Hay un artículo completo sobre cómo usar esta herramienta. Se recomienda que vaya a la página del proyecto del detector de fuga de memoria para usted.
Si desea saber por qué tales herramientas no se han integrado en nuestras herramientas de desarrollo, hay dos razones. Originalmente fue diseñado para ayudarnos a capturar algunos escenarios de memoria específicos en la biblioteca de cierre, que es más adecuado como una herramienta externa.
Chrome admite pasar algunas banderas directamente a V8 para obtener resultados de salida de optimización de motor más detallados. Por ejemplo, esto puede rastrear la optimización de V8:
"/Aplicaciones/Google Chrome/Google Chrome" --JS-Flags = "-Trace-opt --Trace-Deopt"
Los usuarios de Windows pueden ejecutar Chrome.exe JS-Flags = "Trace-opt Trace-Deopt"
Al desarrollar una aplicación, se puede usar el logotipo V8 a continuación.
El script de procesamiento de V8 usa * (asterisco) para identificar funciones optimizadas y usa ~ (Wavy) para representar funciones no optimizadas.
Si está interesado en aprender más sobre el logotipo del V8 y cómo funciona el interior del V8, se recomienda leer la excelente publicación de Vyacheslav Egorov en V8 Internals.
El tiempo de alta precisión (HRT) es una interfaz de tiempo de alta precisión de nivel de submillisegundo que no proporciona impacto en el tiempo del sistema y los ajustes del usuario. Se puede considerar como un método de medición más preciso que la nueva fecha y la fecha. Esto nos ayuda mucho a escribir puntos de referencia.
El tiempo de alta precisión (HRT) proporciona la precisión actual del tiempo sub-milisegundo
Actualmente, HRT se usa en Chrome (versión estable) en Window.performance.webkitnow (), pero el prefijo se descarta en Chrome Canary, lo que permite llamar a través de Window.performance.now (). Paul Irish publicó más sobre HRT en HTML5Rocks.
Ahora que sabemos el tiempo preciso actual, ¿hay una API que pueda medir con precisión el rendimiento de la página? Well, now there is a Navigation Timing API that provides an easy way to get accurate and detailed time measurement records when web pages are loaded and presented to users. You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
Remembering these points can ensure that you have obtained most of the information in this article. ¡Espero que te sea útil!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani