Prototipo en JavaScript: es peculiar, pero así es como funciona

Las siguientes cuatro líneas son suficientes para confundir a la mayoría de los desarrolladores de JavaScript:

Object instanceof Function//true
Object instanceof Object//true
Function instanceof Object//true
Function instanceof Function//true

El prototipo en JavaScript es uno de los conceptos más alucinantes, pero no puede evitarlo. No importa cuánto lo ignore, encontrará el rompecabezas prototipo durante su vida de JavaScript.

Así que enfrentémoslo de frente.

Comenzando con lo básico, existen los siguientes tipos de datos en JavaScript:

  1. indefinido
  2. nulo
  3. número
  4. cuerda
  5. booleano
  6. objeto

Los primeros cinco son tipos de datos primitivos. Estos almacenan un valor de su tipo, como un booleano, y pueden ser verdaderos o falsos .

El último "objeto" es un tipo de referencia que podemos describir como una colección de pares clave-valor (pero es mucho más).

En JavaScript, los nuevos objetos se crean utilizando la función de constructor de objetos (o literal de objeto ) que proporciona métodos genéricos como y .{}toString()valueOf()

Las funciones en JavaScript son objetos especiales que se pueden " llamar" . Los hacemos y usando la función constructora de Función (o función literal). El hecho de que estos constructores sean objetos además de funciones siempre me ha confundido, de la misma manera que el acertijo del huevo y la gallina confunde a todos.

Antes de comenzar con Prototipos, quiero aclarar que existen dos prototipos en JavaScript:

  1. prototipo : este es un objeto especial que se asigna como propiedad de cualquier función que realice en JavaScript. Déjame ser claro aquí, ya está presente para cualquier función que hagas, pero no es obligatorio para las funciones internas proporcionadas por JavaScript (y la función devuelta por bind). Este prototypees el mismo objeto al que apunta el[[Prototype]](ver más abajo) de un objeto recién creado a partir de esa función (usando la newpalabra clave).
  2. [[Prototipo]]: Esta es una propiedad de alguna manera oculta en cada objeto al que accede el contexto en ejecución si alguna propiedad que se está leyendo en el objeto no está disponible. Esta propiedad es simplemente una referencia a laprototypede la función a partir de la cual se hizo el objeto. Se puede acceder a él en un script usando un getter-setter especial (tema para otro día) llamado __proto__. Hay otras formas nuevas de acceder a este prototipo, pero en aras de la brevedad, me referiré a[[Prototype]]usando __proto__.
var obj = {}var obj1 = new Object()

Las dos declaraciones anteriores son declaraciones iguales cuando se utilizan para crear un nuevo objeto, pero suceden muchas cosas cuando ejecutamos cualquiera de estas declaraciones.

Cuando hago un objeto nuevo, está vacío. En realidad, no está vacío porque es una instancia delObjectconstructor, y obtiene inherentemente una referencia de prototypede Object,que es señalado por el __proto__del objeto recién creado.

Si miramos la función constructora prototypeof Object, se ve igual que la __proto__of obj. De hecho, son dos punteros que se refieren al mismo objeto.

obj.__proto__ === Object.prototype//true

Cada prototypede una funcióntiene una propiedad inherente llamada constructorque es un puntero a la función en sí. En el caso de la Objectfunción , el prototypetieneconstructorque apunta de nuevo a Object.

Object.prototype.constructor === Object//true

En la imagen de arriba, el lado izquierdo es la vista ampliada del Objectconstructor. Debes preguntarte cuáles son todas estas otras funciones sobre él. Bueno, las funciones son objetos , por lo que pueden tener propiedades sobre ellas como otros objetos.

Si miras de cerca, el Object(a la izquierda) en sí tiene un__proto__Lo que significa que Objectdebe haber sido hecho de algún otro constructor que tiene un prototype. AsObjectes un objeto de función, debe haber sido creado usando Functionconstructor.

__proto__de Objectse ve igual que prototypede Function. Cuando verifico la igualdad de ambos, resultan ser los mismos objetos.

Object.__proto__ === Function.prototype//true

Si miras de cerca, verás el Functionsí mismo tiene un __proto__Lo que significa que Functionfunción constructoradebe haberse hecho a partir de alguna función constructora que tenga una extensión prototype. ComoFunctionen sí es una función , debe haber sido hecha usandoFunctionconstructor, es decir, a sí mismo. Sé que suena extraño, pero cuando lo revisas, resulta ser cierto.

los __proto__de Functionyprototypede Functionsonde hecho, dos punteros se refieren al mismo objeto.

Function.prototype === Function.__proto__\\true

Como se mencionó anteriormente, el constructorde cualquierprototypedebe apuntar a la función que posee ese prototype.los constructorde prototypede Functionapunta de nuevo a Functionsí mismo.

Function.prototype.constructor === Function\\true

De nuevo, el prototypede Functiontiene un __proto__Bueno, no es de extrañar ... prototypees un objeto, puede tener uno. Pero observe también que apunta a laprototypede Object.

Function.prototype.__proto__ == Object.prototype\\true

Entonces podemos tener un mapa maestro aquí:

instanceof Operatora instanceof b

losinstanceofel operador busca el objeto bseñalado porcualquiera de las constructor(s) de encadenado__proto__en a. ¡Lee eso de nuevo! Si encuentra alguna referencia, regresatrueotra cosa false.

Ahora volvemos a nuestros primeros cuatro instanceofdeclaraciones. He escrito declaraciones correspondientes que haceninstanceofregreso truepara el siguiente:

Object instanceof FunctionObject.__proto__.constructor === Function
Object instanceof ObjectObject.__proto__.__proto__.constructor === Object
Function instanceof FunctionFunction.__proto__.constructor === Function
Function instanceof ObjectFunction.__proto__.__proto__.constructor === Object

¡¡Uf!! Incluso los espaguetis están menos enredados, pero espero que las cosas estén más claras ahora.

Aquí tengo algo que no señalé antes que prototypedeObjectno tiene un __proto__.

En realidad tiene un __proto__pero eso es igual a null. La cadena tenía que terminar en algún lugar y termina aquí.

Object.prototype.__proto__\\null

Nuestra Object, Function,Object.prototype yFunction.prototypetambién tienen propiedades que son funciones, como Object.assign,Object.prototype.hasOwnProperty yFunction.prototype.call. Estas son funciones internas que no tienen prototypey también son instancias de Functiony tienen un__proto__que es un puntero a Function.prototype.

Object.create.__proto__ === Function.prototype\\true

Puede explorar otras funciones de constructor como ArrayyDate, o tomar sus objetos y buscar el prototypey__proto__. Estoy seguro de que podrás ver cómo está todo conectado.

Consultas adicionales:

Hay una pregunta más que me molestó durante un tiempo: ¿Por qué es que prototypede Objectes objeto y prototypede Functiones objeto de la función ?

aquíes una buena explicación si pensaras lo mismo.

Otra pregunta que podría ser un misterio para usted hasta ahora es: ¿Cómo obtienen los tipos de datos primitivos funciones como toString(),substr() y toFixed()?Esto está bien explicado aquí .

Utilizando prototype, podemos hacer que la herencia funcione con nuestros objetos personalizados en JavaScript. Pero ese es un tema para otro día.

¡Gracias por leer!