son que prototipos objetos multiple los herencia heredar create javascript oop inheritance language-design prototype-programming

que - prototype chaining in javascript



¿Beneficios de la herencia prototípica sobre la clásica? (5)

Así que finalmente dejé de arrastrar mis pies todos estos años y decidí aprender JavaScript "correctamente". Uno de los elementos más destacados del diseño de lenguajes es su implementación de la herencia. Teniendo experiencia en Ruby, me alegró mucho ver los cierres y la escritura dinámica; pero por mi vida no puedo averiguar qué beneficios se obtendrán de las instancias de objetos que usan otras instancias para la herencia.



En mi opinión, el principal beneficio de la herencia prototípica es su simplicidad.

La naturaleza prototípica del lenguaje puede confundir a las personas que tienen una formación clásica , pero resulta que en realidad este es un concepto realmente simple y poderoso, la herencia diferencial .

No necesita hacer clasificación , su código es más pequeño, menos redundante, los objetos heredados de otros objetos más generales.

Si piensas en un prototipo , pronto notarás que no necesitas clases ...

La herencia prototípica será mucho más popular en un futuro próximo, la especificación ECMAScript 5th Edition introdujo el método Object.create , que le permite producir una nueva instancia de objeto que hereda de otra de una manera realmente simple:

var obj = Object.create(baseInstance);

Esta nueva versión del estándar está siendo implementada por todos los proveedores de navegadores, y creo que comenzaremos a ver una herencia de prototipos más pura ...


Permítame realmente contestar la pregunta en línea.

La herencia del prototipo tiene las siguientes virtudes:

  1. Se adapta mejor a los lenguajes dinámicos porque la herencia es tan dinámica como el entorno en el que se encuentra. (La aplicabilidad a JavaScript debería ser obvia aquí). Esto le permite hacer cosas rápidamente sobre la marcha como personalizar clases sin grandes cantidades de código de infraestructura. .
  2. Es más fácil implementar un esquema de objeto de creación de prototipos que los esquemas clásicos de dicotomía clase / objeto.
  3. Elimina la necesidad de los complejos bordes afilados alrededor del modelo de objetos como "metaclases" (nunca me gustaron las metaclases ... ¡lo siento!) O "valores propios" o similares.

Tiene las siguientes desventajas sin embargo:

  1. La comprobación de tipos de un lenguaje prototipo no es imposible, pero es muy, muy difícil. La mayoría de las "comprobaciones de tipo" de los lenguajes prototípicos son verificaciones de estilo de "pato escribiendo" en el tiempo de ejecución. Esto no es adecuado para todos los entornos.
  2. Es igualmente difícil hacer cosas como optimizar el envío de métodos mediante análisis estático (o, a menudo, ¡incluso dinámico!). Puede (subrayo: puede ) ser muy ineficiente muy fácilmente.
  3. De manera similar, la creación de objetos puede ser (y suele ser) mucho más lenta en un lenguaje de creación de prototipos que en un esquema de dicotomía clase / objeto más convencional.

Creo que puede leer entre las líneas anteriores y llegar a las ventajas y desventajas correspondientes de los esquemas tradicionales de clase / objeto. Por supuesto, hay más en cada área, así que dejaré el resto para que otras personas respondan.


Realmente no hay mucho para elegir entre los dos métodos. La idea básica a comprender es que cuando el motor de JavaScript recibe una propiedad de un objeto para leer, primero verifica la instancia y si esa propiedad falta, verifica la cadena del prototipo. Aquí hay un ejemplo que muestra la diferencia entre prototípico y clásico:

Prototípico

var single = { status: "Single" }, princeWilliam = Object.create(single), cliffRichard = Object.create(single); console.log(Object.keys(princeWilliam).length); // 0 console.log(Object.keys(cliffRichard).length); // 0 // Marriage event occurs princeWilliam.status = "Married"; console.log(Object.keys(princeWilliam).length); // 1 (New instance property) console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)

Clásico con métodos de instancia (Ineficiente porque cada instancia almacena su propiedad)

function Single() { this.status = "Single"; } var princeWilliam = new Single(), cliffRichard = new Single(); console.log(Object.keys(princeWilliam).length); // 1 console.log(Object.keys(cliffRichard).length); // 1

Clasico eficiente

function Single() { } Single.prototype.status = "Single"; var princeWilliam = new Single(), cliffRichard = new Single(); princeWilliam.status = "Married"; console.log(Object.keys(princeWilliam).length); // 1 console.log(Object.keys(cliffRichard).length); // 0 console.log(cliffRichard.status); // "Single"

Como puede ver, dado que es posible manipular el prototipo de "clases" declaradas en el estilo clásico, realmente no hay beneficio en el uso de la herencia prototípica. Es un subconjunto del método clásico.


Sé que esta respuesta tiene 3 años de retraso, pero realmente creo que las respuestas actuales no brindan suficiente información sobre cómo la herencia prototípica es mejor que la herencia clásica .

Primero veamos los argumentos más comunes que los programadores de JavaScript declaran en defensa de la herencia prototípica (estoy tomando estos argumentos del conjunto de respuestas actual):

  1. Es sencillo.
  2. Es poderoso
  3. Conduce a código más pequeño, menos redundante.
  4. Es dinámico y por lo tanto es mejor para los lenguajes dinámicos.

Ahora todos estos argumentos son válidos, pero nadie se ha molestado en explicar por qué. Es como decirle a un niño que estudiar matemáticas es importante. Claro que sí, pero al niño ciertamente no le importa; y no puedes hacer que a un niño le gusten las matemáticas diciendo que es importante.

Creo que el problema con la herencia prototípica es que se explica desde la perspectiva de JavaScript. Me encanta JavaScript, pero la herencia prototípica en JavaScript está mal. A diferencia de la herencia clásica, hay dos patrones de herencia prototípica:

  1. El patrón prototípico de la herencia prototípica.
  2. El patrón constructor de la herencia prototípica.

Desafortunadamente, JavaScript usa el patrón constructor de la herencia prototípica. Esto se debe a que cuando se creó JavaScript, Brendan Eich (el creador de JS) quería que se pareciera a Java (que tiene herencia clásica):

Y lo impulsábamos como un hermano pequeño a Java, ya que un lenguaje complementario como Visual Basic era para C ++ en las familias de lenguaje de Microsoft en ese momento.

Esto es malo porque cuando las personas usan constructores en JavaScript piensan que los constructores heredan de otros constructores. Esto está mal. En la herencia prototípica, los objetos heredan de otros objetos. Los constructores nunca entran en escena. Esto es lo que confunde a la mayoría de la gente.

Las personas de lenguajes como Java, que tienen herencia clásica, se confunden aún más porque aunque los constructores parecen clases no se comportan como clases. Como Douglas Crockford declaró:

Esta orientación estaba destinada a hacer que el lenguaje parezca más familiar para los programadores entrenados clásicamente, pero no lo hizo, como podemos ver en la muy baja opinión que los programadores de Java tienen de JavaScript. El patrón de construcción de JavaScript no atrajo a la multitud clásica. También oscureció la verdadera naturaleza prototípica de JavaScript. Como resultado, hay muy pocos programadores que saben cómo usar el lenguaje de manera efectiva.

Ahí tienes. Directo de la boca del caballo.

Herencia Prototípica Verdadera

La herencia prototípica tiene que ver con los objetos. Los objetos heredan propiedades de otros objetos. Eso es todo al respecto. Hay dos formas de crear objetos utilizando la herencia prototípica:

  1. Crea un nuevo objeto.
  2. Clona un objeto existente y extiéndelo.

Nota: JavaScript ofrece dos formas de clonar un objeto: delegation y concatenation . De aquí en adelante usaré la palabra "clon" para referirse exclusivamente a la herencia a través de la delegación, y la palabra "copiar" para referirse exclusivamente a la herencia a través de la concatenación.

Basta de hablar. Veamos algunos ejemplos. Digamos que tengo un círculo de radio 5 :

var circle = { radius: 5 };

Podemos calcular el área y la circunferencia del círculo a partir de su radio:

circle.area = function () { var radius = this.radius; return Math.PI * radius * radius; }; circle.circumference = function () { return 2 * Math.PI * this.radius; };

Ahora quiero crear otro círculo de radio 10 . Una forma de hacer esto sería:

var circle2 = { radius: 10, area: circle.area, circumference: circle.circumference };

Sin embargo, JavaScript proporciona una mejor manera - delegation . La función Object.create de Crockford se usa para hacer esto:

var circle2 = Object.create(circle); circle2.radius = 10;

Eso es todo. Acabas de hacer herencia prototípica en JavaScript. ¿No era eso simple? Coges un objeto, lo clonas, cambias lo que necesitas y, listo, tienes un nuevo objeto.

Ahora puede preguntar: "¿Cómo es esto simple? Cada vez que quiero crear un nuevo círculo, necesito clonar un circle y asignarle un radio manualmente". Bueno, la solución es usar una función para hacer el trabajo pesado para usted:

function createCircle(radius) { var newCircle = Object.create(circle); newCircle.radius = radius; return newCircle; } var circle2 = createCircle(10);

De hecho, puede combinar todo esto en un único objeto literal de la siguiente manera:

var circle = { radius: 5, create: function (radius) { var circle = Object.create(this); circle.radius = radius; return circle; }, area: function () { var radius = this.radius; return Math.PI * radius * radius; }, circumference: function () { return 2 * Math.PI * this.radius; } }; var circle2 = circle.create(10);

Herencia prototípica en JavaScript

Si observa en el programa anterior, la función create crea un clon de circle , le asigna un nuevo radius y luego lo devuelve. Esto es exactamente lo que hace un constructor en JavaScript:

function Circle(radius) { this.radius = radius; } Circle.prototype.area = function () { var radius = this.radius; return Math.PI * radius * radius; }; Circle.prototype.circumference = function () { return 2 * Math.PI * this.radius; }; var circle = new Circle(5); var circle2 = new Circle(10);

El patrón constructor en JavaScript es el patrón prototípico invertido. En lugar de crear un objeto, creas un constructor. La new palabra clave une this puntero dentro del constructor a un clon del prototype del constructor.

¿Suena confuso? Es porque el patrón del constructor en JavaScript complica innecesariamente las cosas. Esto es lo que la mayoría de los programadores encuentran difícil de entender.

En lugar de pensar en objetos heredados de otros objetos, piensan en constructores que heredan de otros constructores y luego se confunden por completo.

Hay muchas otras razones por las que se debe evitar el patrón de construcción en JavaScript. Puedes leer sobre ellos en la publicación de mi blog aquí: Constructores vs Prototipos

Entonces, ¿cuáles son los beneficios de la herencia prototípica sobre la herencia clásica? Repasemos de nuevo los argumentos más comunes y expliquemos por qué .

1. La herencia prototípica es simple

CMS declara en su respuesta:

En mi opinión, el beneficio principal de la herencia prototípica es su simplicidad.

Consideremos lo que acabamos de hacer. Creamos un circle objetos que tenía un radio de 5 . Luego lo clonamos y le dimos al clon un radio de 10 .

Por lo tanto, solo necesitamos dos cosas para hacer que la herencia prototípica funcione:

  1. Una forma de crear un nuevo objeto (por ejemplo, literales de objeto).
  2. Una forma de extender un objeto existente (por ejemplo, Object.create ).

En contraste la herencia clásica es mucho más complicada. En herencia clásica tienes:

  1. Clases
  2. Objeto.
  3. Interfaces.
  4. Clases abstractas.
  5. Clases finales.
  6. Clases de base virtual.
  7. Constructores.
  8. Destructores.

Tienes la idea El punto es que la herencia prototípica es más fácil de entender, de implementar y de razonar.

Como Steve Yegge lo pone en su blog clásico " Retrato de un N00b ":

Metadatos es cualquier tipo de descripción o modelo de otra cosa. Los comentarios en su código son solo una descripción en lenguaje natural del cálculo. Lo que hace metadatos metadatos es que no es estrictamente necesario. Si tengo un perro con algún papeleo de pedigrí, y pierdo el papeleo, todavía tengo un perro perfectamente válido.

En el mismo sentido, las clases son solo metadatos. Las clases no son estrictamente necesarias para la herencia. Sin embargo, algunas personas (generalmente n00bs) encuentran las clases más cómodas para trabajar. Les da una falsa sensación de seguridad.

Bueno, también sabemos que los tipos estáticos son solo metadatos. Son un tipo de comentario especializado dirigido a dos tipos de lectores: programadores y compiladores. Los tipos estáticos cuentan una historia sobre el cálculo, presumiblemente para ayudar a ambos grupos de lectores a comprender la intención del programa. Pero los tipos estáticos se pueden tirar en tiempo de ejecución, porque al final solo son comentarios estilizados. Son como papeles de pedigrí: pueden hacer que un tipo de personalidad insegura sea más feliz con respecto a su perro, pero al perro no le importa.

Como dije antes, las clases le dan a las personas una falsa sensación de seguridad. Por ejemplo, obtienes demasiadas NullPointerException en Java incluso cuando tu código es perfectamente legible. Encuentro que la herencia clásica generalmente interfiere con la programación, pero tal vez eso sea solo Java. Python tiene un increíble sistema de herencia clásica.

2. La herencia prototípica es poderosa

La mayoría de los programadores que provienen de un fondo clásico argumentan que la herencia clásica es más poderosa que la herencia prototípica porque tiene:

  1. Variables privadas.
  2. Herencia múltiple.

Esta afirmación es falsa. Ya sabemos que JavaScript admite variables privadas a través de cierres , pero ¿qué pasa con la herencia múltiple? Los objetos en JavaScript solo tienen un prototipo.

La verdad es que la herencia prototípica admite la herencia de múltiples prototipos. La herencia prototípica simplemente significa que un objeto se hereda de otro objeto. En realidad, hay dos formas de implementar la herencia prototípica :

  1. Delegación o herencia diferencial
  2. Clonación o herencia concatenativa

Sí, JavaScript solo permite que los objetos se deleguen a otro objeto. Sin embargo, le permite copiar las propiedades de un número arbitrario de objetos. Por ejemplo _.extend hace justamente esto.

Por supuesto, muchos programadores no consideran que esta sea una verdadera herencia porque instanceof y isPrototypeOf dicen lo contrario. Sin embargo, esto puede remediarse fácilmente almacenando una serie de prototipos en cada objeto que hereda de un prototipo a través de la concatenación:

function copyOf(object, prototype) { var prototypes = object.prototypes; var prototypeOf = Object.isPrototypeOf; return prototypes.indexOf(prototype) >= 0 || prototypes.some(prototypeOf, prototype); }

Por lo tanto, la herencia prototípica es tan poderosa como la herencia clásica. De hecho, es mucho más poderoso que la herencia clásica porque en la herencia prototípica puede elegir qué propiedades copiar y qué propiedades omitir de diferentes prototipos.

En la herencia clásica es imposible (o al menos muy difícil) elegir qué propiedades desea heredar. Utilizan clases base virtuales e interfaces para resolver el problema del diamante .

Sin embargo, en JavaScript es muy probable que nunca escuche sobre el problema del diamante porque puede controlar exactamente qué propiedades desea heredar y de qué prototipos.

3. La herencia prototípica es menos redundante

Este punto es un poco más difícil de explicar porque la herencia clásica no conduce necesariamente a un código más redundante. De hecho, la herencia, ya sea clásica o prototípica, se utiliza para reducir la redundancia en el código.

Un argumento podría ser que la mayoría de los lenguajes de programación con herencia clásica están tipificados estáticamente y requieren que el usuario declare explícitamente los tipos (a diferencia de Haskell, que tiene tipografía estática implícita). Por lo tanto, esto conduce a un código más detallado.

Java es notoria por este comportamiento. Recuerdo claramente que Bob Nystrom mencionó la siguiente anécdota en su blog sobre Pratt Parsers :

Tienes que amar el nivel de burocracia de "por favor firmalo por cuadruplicado" de Java aquí.

Una vez más, creo que eso es solo porque Java apesta tanto.

Un argumento válido es que no todos los idiomas que tienen herencia clásica admiten herencia múltiple. Una vez más, Java viene a la mente. Sí, Java tiene interfaces, pero eso no es suficiente. A veces realmente necesitas herencia múltiple.

Dado que la herencia prototípica permite la herencia múltiple, el código que requiere herencia múltiple es menos redundante si se escribe utilizando herencia prototípica en lugar de en un lenguaje que tiene herencia clásica pero no herencia múltiple.

4. La herencia prototípica es dinámica.

Una de las ventajas más importantes de la herencia prototípica es que puede agregar nuevas propiedades a los prototipos una vez creados. Esto le permite agregar nuevos métodos a un prototipo que se pondrá automáticamente a disposición de todos los objetos que deleguen en ese prototipo.

Esto no es posible en la herencia clásica porque una vez que se crea una clase no se puede modificar en tiempo de ejecución. Esta es probablemente la mayor ventaja de la herencia prototípica sobre la herencia clásica, y debería haber estado en la parte superior. Sin embargo me gusta guardar lo mejor para el final.

Conclusión

La herencia prototípica importa. Es importante educar a los programadores de JavaScript sobre por qué abandonar el patrón constructor de la herencia prototípica a favor del patrón prototípico de la herencia prototípica.

Necesitamos comenzar a enseñar JavaScript correctamente y eso significa mostrar a los nuevos programadores cómo escribir código usando el patrón prototípico en lugar del patrón del constructor.

No solo será más fácil explicar la herencia prototípica utilizando el patrón prototípico, sino que también hará que sean mejores programadores.

Si le gustó esta respuesta, entonces también debería leer la publicación de mi blog sobre " Por qué es importante la herencia prototípica ". Créame, no se decepcionará.