Header Ads Widget

Ticker

6/recent/ticker-posts

Comprensión del prototipo de JavaScript

 Se dice que JavaScript es un lenguaje basado en prototipos. Entonces, los "prototipos" deben ser un concepto importante, ¿verdad?

Hoy voy a explicar qué son los prototipos, qué necesitas saber y cómo usarlos de manera efectiva.

¿Qué son los prototipos?

En primer lugar, no se deje engañar por la palabra "Prototipo" . El "prototipo" en JavaScript no es lo mismo que "prototipo" en inglés. No significa una versión inicial de un producto que se ensambló rápidamente.

En cambio, prototipo en JavaScript es simplemente una palabra que no significa absolutamente nada. Podemos reemplazar el prototipo con naranjas y puede significar lo mismo.

Por ejemplo, piense en Apple. Antes de que Apple Computers se hiciera popular, probablemente pensarás en Apple como la fruta de color rojo. “Apple” en Apple Computers no tiene un significado inicialmente, pero lo tiene ahora.

En el caso de JavaScript, el prototipo se refiere a un sistema. Este sistema le permite definir propiedades en objetos a los que se puede acceder a través de las instancias del objeto.

El prototipo está estrechamente relacionado con la programación orientada a objetos. No tendría sentido si no comprende de qué se trata la programación orientada a objetos.

Le sugiero que se familiarice con esta serie introductoria sobre programación orientada a objetos antes de continuar.

Por ejemplo, an Arrayes un modelo para instancias de matriz. Crea una instancia de matriz con []new Array().

const array = ['one', 'two', 'three']
console.log(array)

// Same result as above
const array = new Array('one', 'two', 'three')

Si utiliza console.logesta matriz, no verá ningún método. Pero, sin embargo, puede utilizar métodos como concatslicefilter, y map!

Array no contiene método.

¿Por qué?

Porque estos métodos se encuentran en el prototipo de Array. Puede expandir el __proto__objeto (Chrome Devtools) o el <prototype>objeto (Firefox Devtools) y verá una lista de métodos.

Array.prototype contiene los métodos
Firefox registra el prototipo como prototype

Tanto __proto__en Chrome como <prototype>en Firefox apunta al objeto Prototype. Simplemente están escritos de manera diferente en diferentes navegadores.

Cuando lo usa map, JavaScript busca mapen el propio objeto. Si mapno se encuentra, JavaScript intenta buscar un prototipo. Si JavaScript encuentra un prototipo, continúa buscando mapen ese prototipo.

Entonces, la definición correcta de Prototype es: un objeto al que las instancias pueden acceder cuando intentan buscar una propiedad.

Cadenas de prototipos

Esto es lo que hace JavaScript cuando accede a una propiedad:

Paso 1 : JavaScript comprueba si la propiedad está disponible dentro del objeto. En caso afirmativo, JavaScript utiliza la propiedad de inmediato.

Paso 2 : Si la propiedad NO está dentro del objeto, JavaScript verifica si hay un Prototipo disponible. Si hay un prototipo, repita el paso 1 (y verifique si la propiedad está dentro del prototipo).

Paso 3 : si no quedan más prototipos y JavaScript no puede encontrar la propiedad, hace lo siguiente:

  • Devoluciones undefined(si intentó acceder a una propiedad).
  • Lanza un error (si intentó llamar a un método).

En forma de diagrama, así es como se ve el proceso:

Cadena prototipo.

Ejemplo de cadena de prototipos

Digamos que tenemos una Humanclase. También tenemos una Developersubclase que hereda de HumanHumans tenemos un sayHellométodo y Developerstenemos un codemétodo.

Aquí está el código para Human

class Human {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastname = lastName
}

sayHello () {
console.log(`Hi, I'm ${this.firstName}`)
}
}

Human(y más Developerabajo) se pueden escribir con funciones de constructor. Si usamos funciones de Constructor, se prototypevuelve más claro, pero crear Subclases se vuelve más difícil. Por eso estoy mostrando un ejemplo con Clases. (Consulte este artículo para conocer las 4 formas diferentes de utilizar la programación orientada a objetos).

Así es como escribirías Humansi usaras un Constructor en su lugar.

function Human (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}

Human.prototype.sayHello = function () {
console.log(`Hi, I'm ${this.firstName}`)
}

Aquí está el código de Developer.

class Developer extends Human {
code (thing) {
console.log(`${this.firstName} coded ${thing}`)
}
}

Una Developerinstancia puede usar ambos codesayHelloporque estos métodos están ubicados en la cadena de prototipos de la instancia.

const zell = new Developer('Zell', 'Liew')
zell.sayHello() // Hi, I'm Zell
zell.code('website') // Zell coded website

Si tiene console.logla instancia, puede ver los métodos en la cadena de prototipos.

`code` y` sayHello` en la cadena de prototipos.

Delegación prototípica / herencia prototípica

La delegación prototípica y la herencia prototípica significan lo mismo.

Simplemente dicen que usamos el sistema prototipo, donde colocamos propiedades y métodos en el prototypeobjeto.

¿Deberíamos utilizar la delegación prototípica?

Dado que JavaScript es un lenguaje basado en prototipos, deberíamos usar la delegación de prototipos. ¿Derecho?

Realmente no.

Yo diría que depende de cómo se escriba la programación orientada a objetos. Tiene sentido usar Prototipos si usa clases porque son más convenientes.

class Blueprint {
method1 () {/* ... */}
method2 () {/* ... */}
method3 () {/* ... */}
}

Pero tiene sentido NO usar prototipos si usa funciones de fábrica.

function Blueprint {
return {
method1 () {/* ... */}
method2 () {/* ... */}
method3 () {/* ... */}
}
}

Nuevamente, lea este artículo para conocer cuatro formas diferentes de escribir programación orientada a objetos.

Implicaciones de rendimiento

El rendimiento entre los dos métodos no importa mucho, a menos que su aplicación requiera millones de operaciones. En esta sección, voy a compartir algunos experimentos para probar este punto.

Preparar

Podemos usar performance.nowpara registrar una marca de tiempo antes de ejecutar cualquier operación. Después de ejecutar las operaciones, usaremos performance.nowpara registrar la marca de tiempo nuevamente.

Luego, obtendremos la diferencia en las marcas de tiempo para medir cuánto tiempo tardaron las operaciones.

const start = performance.now()
// Do stuff
const end = performance.now()

const elapsed = end - start
console.log(elapsed)

Usé una perffunción para ayudar con mis pruebas:

function perf (message, callback, loops = 1) {
const startTime = performance.now()
for (let index = 0; index <= loops; index++) {
callback()
}
const elapsed = performance.now() - startTime
console.log(message + ':', elapsed)
}

Nota: Puede obtener más información performance.nowen este artículo .

Experimento n. ° 1: usar prototipos versus no usar prototipos

Primero, probé cuánto tiempo se tarda en acceder a un método a través de un prototipo frente a otro método que se encuentra en el propio objeto.

Aquí está el código:

class Blueprint () {
constructor () {
this.inObject = function () { return 1 + 1 }
}

inPrototype () { return 1 + 1 }
}

const count = 1000000
const instance = new Blueprint()
perf('In Object', _ => { instance.inObject() }, count)
perf('In Prototype', _ => { instance.inPrototype() }, count)

Los resultados promedio se resumen en esta tabla de la siguiente manera:

Prueba1,000,000 operaciones10,000,000 operaciones
En objeto3 ms15 ms
En prototipo2ms12 ms

Nota: Los resultados son de Devtools de Firefox. Lea esto para comprender por qué solo estoy haciendo evaluaciones comparativas con Firefox.

El veredicto: no importa si usa prototipos o no. No hará ninguna diferencia a menos que ejecute> 1 millón de operaciones.

Experimento 2: clases frente a funciones de fábrica

Tuve que ejecutar esta prueba ya que recomiendo usar prototipos cuando usa clases y no usar prototipos cuando usa funciones de fábrica.

Necesitaba probar si crear funciones de fábrica era significativamente más lento que crear clases.

Aquí está el código.

// Class blueprint
class HumanClass {
constructor (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}

sayHello () {
console.lg(`Hi, I'm ${this.firstName}}`)
}
}

// Factory blueprint
function HumanFactory (firstName, lastName) {
return {
firstName,
lastName,
sayHello () {
console.log(`Hi, I'm ${this.firstName}}`)
}
}
}

// Tests
const count = 1000000
perf('Class', _ => { new HumanClass('Zell', 'Liew') }, count)
perf('Factory', _ => { HumanFactory('Zell', 'Liew') }, count)

Los resultados promedio se resumen en la tabla de la siguiente manera:

Prueba1,000,000 operaciones10,000,000 operaciones
Clase5ms18 ms
Fábrica6ms18 ms

El veredicto: no importa si usa funciones de clase o de fábrica. No hará ninguna diferencia incluso si ejecuta> 1 millón de operaciones.

Conclusión sobre las pruebas de rendimiento

Puede utilizar las funciones Clases o Fábrica. Eliges usar prototipos o puedes optar por no hacerlo. Realmente depende de ti.

No hay necesidad de preocuparse por el rendimiento.

Si te gustó este artículo, ¡cuéntaselo a un amigo! Compártelo en Twitter . Si detecta un error tipográfico, le agradecería que lo corrija en GitHub . ¡Gracias!

Publicar un comentario

0 Comentarios