Siempre me surge la misma duda, la miro, me entero, y me vuelvo a olvidar.
¿Cuál es la diferencia entre prototype y __proto__?
Tras leer el artículo JavaScript. The Core por Dmitry Soshnikov para conocer la diferencia (como hice yo), este diagrama de dicho artículo lo resume bien:
Mi explicación simplicada:
- __proto__ es el atributo que se consulta en tiempo de ejecución para ver a qué se accede si la instancia no posee dicho atributo. Por así decirlo, es la cadena de mando en la ejecución de métodos (y acceder a atributos) de instancias.
En el diagrama, la instancia “b” hereda de “Foo” (__proto__ -> Foo.prototype) que a su vez hereda de “Object” (__proto__ -> Object.prototype). - prototype es un atributo que poseen las funciones que van a servir como constructor de objetos, y dicho atributo es el que se establecerá a __proto__ a las instancias. Por así decirlo, prototype es la definición de la clase.
- new lo que hace es: crea la variable (por ejemplo “b”), y asigna “__proto__ = Clase.prototype” (pongo clase en cursiva porque en javascript no son clases)
De este modo vemos en el diagrama que Foo tiene prototype y __proto__:
- Como Foo es una función, su __proto__ hace referencia a Function.
- pero como Foo se va a utilizar como si fuera una clase, para instanciar objetos, tiene un prototype que va a ser la definición de la “clase“, a la que hacen referencia __proto__ de las instancias.
Detalles a mayores:
- Los atributos/métodos en prototype son compartidos en modo lectura por todas las instancias. El motor de javascript se encarga de “convertir” b.x en b.__proto__.x cuando b no posee él mismo el atributo x.
- Cuando modificas b.x del diagrama, el motor de javascript hace una copia (copy-on-write), de manera que b pasa a tener él mismo un atributo “x” diferente a __proto__.x. Las siguientes veces que accedas a b.x, el motor no lo transformará en b.__proto__.x, sino que tomara el valor del propio atributo de b.
- Si se quiere tratar un atributo de clase (atributos estáticos, vamos) como tal, hay que hacer referencia siempre a b.__proto__.x, y al modificar esta variable de esta manera sí que se modifica para todas las instancias (que también han de acceder al valor mediante b.__proto__.x).