No se puede resolver el misterio de las funciones en Javascript

16

Estoy tratando de entender detrás de las escenas de la cortina de Javascript y estoy atascado en la comprensión de la creación de objetos integrados, especialmente Objeto y Función y la relación entre ellos.

Cuando leí que todos los objetos integrados como Array, String, etc. son extensiones (heredadas) de Object, asumí que Object es el primer objeto incorporado que se crea y el resto de los objetos hereda de él. Pero no tiene sentido cuando se llega a saber que los Objetos solo pueden ser creados por funciones, sino que las funciones no son más que objetos de Función. Comenzó a sonar como un dilema de gallina y pollo.

La otra cosa extremadamente confusa es si console.log(Function.prototype)imprimo una función pero cuando imprimo console.log(Object.prototype)imprime un objeto. ¿Por qué es Function.prototypeuna función cuando estaba destinada a ser un objeto?

Además, de acuerdo con la documentación de Mozilla, cada javascript functiones una extensión de Functionobjeto, pero cuando console.log(Function.prototype.constructor)se trata nuevamente de una función. Ahora, ¿cómo puedes usar algo para crearlo tú mismo? (Mente = soplado).

Lo último, Function.prototypees una función, pero puedo acceder a la constructorfunción usando Function.prototype.constructor¿eso significa que Function.prototypees una función que devuelve el prototypeobjeto

Umair Abid
fuente
Como una función es un objeto, eso significa que Function.prototypepuede ser una función y tener campos internos. Entonces no, no ejecutas la función prototipo cuando pasas por su estructura. Por último, recuerde que hay un motor de interpretación de Javascript, por lo Objeto y función se crearon probablemente dentro del motor y no de Javascript y referencia especial como Function.prototypey Object.prototypesólo podrían ser interpretados de una manera especial por el motor.
Walfrat
1
A menos que esté buscando implementar un compilador de JavaScript que cumpla con los estándares, realmente no necesita preocuparse por estas cosas. Si está buscando hacer algo útil, está fuera de curso.
Jared Smith
55
Para su información, la frase usual en inglés para "el dilema de la gallina y el pollo" es "el problema del huevo y la gallina", a saber, "¿qué fue primero, la gallina o el huevo?" (Por supuesto, la respuesta es el huevo animales ovíparos eran existido durante millones de años antes de pollos..)
Eric Lippert

Respuestas:

32

Estoy tratando de entender detrás de las escenas de la cortina de Javascript y estoy atascado en la comprensión de la creación de objetos integrados, especialmente Objeto y Función y la relación entre ellos.

Es complicado, es fácil de entender mal, y muchos libros de JavaScript para principiantes se equivocan, así que no confíes en todo lo que lees.

Fui uno de los implementadores del motor JS de Microsoft en la década de 1990 y en el comité de estandarización, y cometí varios errores al reunir esta respuesta. (Aunque como no he trabajado en esto durante más de 15 años, tal vez pueda ser perdonado). Es algo complicado. Pero una vez que entiendes la herencia del prototipo, todo tiene sentido.

Cuando leí que todos los objetos integrados como Array, String, etc. son extensiones (heredadas) de Object, supuse que Object es el primer objeto incorporado que se crea y el resto de los objetos hereda de él.

Comience desechando todo lo que sabe sobre la herencia basada en clases. JS utiliza herencia basada en prototipos.

Luego, asegúrese de tener una definición muy clara en su cabeza de lo que significa "herencia". Las personas acostumbradas a lenguajes OO como C # o Java o C ++ piensan que la herencia significa subtipo, pero la herencia no significa subtipo. La herencia significa que los miembros de una cosa también son miembros de otra . ¡No significa necesariamente que haya una relación de subtipo entre esas cosas! Tantos malentendidos en la teoría de tipos son el resultado de que las personas no se dan cuenta de que hay una diferencia.

Pero no tiene sentido cuando se llega a saber que los Objetos solo pueden ser creados por funciones, sino que las funciones no son más que objetos de Función.

Esto es simplemente falso. Algunos objetos no se crean llamando new Fa alguna función F. Algunos objetos son creados por el tiempo de ejecución JS de la nada. Hay huevos que no fueron puestos por ningún pollo . Fueron creados por el tiempo de ejecución cuando se inició.

Digamos cuáles son las reglas y quizás eso ayude.

  • Cada instancia de objeto tiene un objeto prototipo.
  • En algunos casos ese prototipo puede ser null.
  • Si accede a un miembro en una instancia de objeto y el objeto no tiene ese miembro, el objeto se desvía a su prototipo o se detiene si el prototipo es nulo.
  • El prototypemiembro de un objeto generalmente no es el prototipo del objeto.
  • Más bien, el prototypemiembro de un objeto de función F es el objeto que se convertirá en el prototipo del objeto creado por new F().
  • En algunas implementaciones, las instancias obtienen un __proto__miembro que realmente da su prototipo. (Esto ahora está en desuso. No confíe en ello).
  • Los objetos de función obtienen un nuevo objeto predeterminado asignado prototypecuando se crean.
  • El prototipo de un objeto de función es, por supuesto Function.prototype.

Resumamos

  • El prototipo de ObjectesFunction.prototype
  • Object.prototype es el objeto prototipo de objeto.
  • El prototipo de Object.prototypeesnull
  • El prototipo de Functiones Function.prototype: ¡esta es una de las raras situaciones en las Function.prototypeque en realidad es el prototipo de Function!
  • Function.prototype es el objeto prototipo de la función.
  • El prototipo de Function.prototypeesObject.prototype

Supongamos que hacemos una función Foo.

  • El prototipo de Fooes Function.prototype.
  • Foo.prototype es el prototipo de objeto Foo.
  • El prototipo de Foo.prototypees Object.prototype.

Supongamos que decimos new Foo()

  • El prototipo del nuevo objeto es Foo.prototype

Asegúrate de que tenga sentido. Dibujémoslo. Los óvalos son instancias de objeto. Los bordes __proto__significan "el prototipo de" o prototype" prototypepropiedad de".

ingrese la descripción de la imagen aquí

Todo lo que el tiempo de ejecución tiene que hacer es crear todos esos objetos y asignar sus diversas propiedades en consecuencia. Estoy seguro de que puedes ver cómo se haría eso.

Ahora veamos un ejemplo que pone a prueba sus conocimientos.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

¿Qué imprime esto?

Bueno, que instanceofsignifica? honda instanceof Carsignifica "es Car.prototypeigual a cualquier objeto en hondala cadena de prototipo?"

Sí lo es. hondaEl prototipo es Car.prototype, así que hemos terminado. Esto imprime cierto.

¿Qué hay del segundo?

honda.constructorno existe, por lo que consultamos el prototipo, que es Car.prototype. Cuando Car.prototypese creó el objeto, se le asignó automáticamente una propiedad constructorigual a Car, por lo que esto es cierto.

¿Y qué hay de esto?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

¿Qué imprime este programa?

Nuevamente, lizard instanceof Reptilesignifica "¿es Reptile.prototypeigual a cualquier objeto en lizardla cadena de prototipo?"

Sí lo es. lizardEl prototipo es Reptile.prototype, así que hemos terminado. Esto imprime cierto.

Ahora, que hay de

print(lizard.constructor == Reptile);

Puede pensar que esto también se imprime como cierto, ya que lizardfue construido con, new Reptilepero estaría equivocado. Razonarlo.

  • ¿ lizardTiene una constructorpropiedad? No. Por lo tanto, miramos el prototipo.
  • El prototipo de lizardes Reptile.prototype, que es Animal.
  • ¿ AnimalTiene una constructorpropiedad? No. Entonces miramos su prototipo.
  • El prototipo de Animales Object.prototype, y Object.prototype.constructores creado por el tiempo de ejecución e igual a Object.
  • Entonces esto imprime falso.

Deberíamos haber dicho Reptile.prototype.constructor = Reptile;en algún momento, ¡pero no nos acordamos!

Asegúrate de que todo tenga sentido para ti. Dibuja algunos cuadros y flechas si todavía es confuso.

La otra cosa extremadamente confusa es si console.log(Function.prototype)imprimo una función pero cuando imprimo console.log(Object.prototype)imprime un objeto. ¿Por qué es Function.prototypeuna función cuando estaba destinada a ser un objeto?

El prototipo de la función se define como una función que, cuando se llama, regresa undefined. Ya sabemos que ese Function.prototypees el Functionprototipo, por extraño que parezca. Por lo tanto, Function.prototype()es legal, y cuando lo haces, undefinedregresas. Entonces es una función.

El Objectprototipo no tiene esta propiedad; No es invocable. Es solo un objeto.

cuando de console.log(Function.prototype.constructor)nuevo es una función.

Function.prototype.constructores solo Function, obviamente. Y Functiones una función.

Ahora, ¿cómo puedes usar algo para crearlo tu mismo? (Mente = soplado).

Estás pensando demasiado en esto . Todo lo que se requiere es que el tiempo de ejecución cree un montón de objetos cuando se inicie. Los objetos son solo tablas de búsqueda que asocian cadenas con objetos. Cuando el tiempo de ejecución se pone en marcha, todo lo que tiene que hacer es crear algunos objetos docena de blanco, y luego comenzar a asignar el prototype, __proto__, constructor, y así sucesivamente propiedades de cada objeto hasta que se haga la gráfica que tienen que hacer.

Será útil si tomas el diagrama que te di arriba y le agregas constructorbordes. Verá rápidamente que este es un gráfico de objetos muy simple y que el tiempo de ejecución no tendrá problemas para crearlo.

Un buen ejercicio sería hacerlo usted mismo. Aquí, te comenzaré. Usaremos my__proto__para significar "el objeto prototipo de" y myprototypepara significar "la propiedad prototipo de".

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

Y así. ¿Puede completar el resto del programa para construir un conjunto de objetos que tenga la misma topología que los objetos incorporados "reales" de Javascript? Si lo hace, encontrará que es extremadamente fácil.

Los objetos en JavaScript son solo tablas de búsqueda que asocian cadenas con otros objetos . ¡Eso es! No hay magia aquí. Te estás haciendo nudos porque estás imaginando restricciones que en realidad no existen, como si cada objeto tuviera que ser creado por un constructor.

Las funciones son solo objetos que tienen una capacidad adicional: ser llamados. Así que revise su pequeño programa de simulación y agregue una .mycallablepropiedad a cada objeto que indique si es invocable o no. Es tan simple como eso.

Eric Lippert
fuente
9
Finalmente, una explicación breve, concisa y fácil de entender de JavaScript. ¡Excelente! ¿Cómo podría alguno de nosotros estar confundido? :) Aunque con toda seriedad, la última clave sobre los objetos como tablas de búsqueda es realmente la clave. Hay un método para la locura --- pero sigue siendo una locura ...
Greg Burghardt
44
@GregBurghardt: Estoy de acuerdo en que parece complejo al principio, pero la complejidad es la consecuencia de reglas simples. Cada objeto tiene un __proto__. El __proto__prototipo del objeto es nulo. El __proto__de new X()es X.prototype. Todos los objetos de función tienen el prototipo de función, __proto__excepto el propio prototipo de función. Objecty Functiony el prototipo de la función son funciones. Todas esas reglas son sencillas y determinan la topología de la gráfica de los objetos iniciales.
Eric Lippert el
6

Ya tiene muchas respuestas excelentes, pero solo quiero darle una respuesta breve y clara a su respuesta sobre cómo funciona todo esto, y esa respuesta es:

¡¡¡MAGIA!!!

Realmente, eso es todo.

Las personas que implementan motores de ejecución de ECMAScript tienen que aplicar las reglas de ECMAScript, pero no cumplir por ellos dentro de su aplicación.

La especificación ECMAScript dice que A hereda de B pero B es una instancia de A? ¡No hay problema! Cree A primero con un puntero prototipo de NULL, cree B como una instancia de A, luego arregle el puntero prototipo de A para señalar B después. Pan comido.

Usted dice, pero espere, ¡no hay forma de cambiar el puntero prototipo en ECMAScript! Pero, aquí está la cosa: este código no se está ejecutando en el motor ECMAScript, este código es el motor ECMAScript. Que no tienen acceso a la parte interna de los objetos que el código ECMAScript que se ejecuta en el motor no tiene. En resumen: puede hacer lo que quiera.

Por cierto, si realmente lo desea, solo tiene que hacer esto una vez: luego, por ejemplo, puede volcar su memoria interna y cargar este volcado cada vez que inicie su motor ECMAScript.

Tenga en cuenta que todo esto aún se aplica, incluso si el motor ECMAScript en sí mismo se escribió en ECMAScript (como es el caso de Mozilla Narcissus, por ejemplo). Incluso entonces, el código ECMAScript que implementa el motor todavía tiene acceso completo al motor que está implementando , aunque por supuesto no tiene acceso al motor en el que se está ejecutando .

Jörg W Mittag
fuente
3

De la especificación 1 de ECMA

ECMAScript no contiene clases adecuadas como las de C ++, Smalltalk o Java, sino que admite constructores que crean objetos ejecutando código que asigna almacenamiento para los objetos e inicializa todos o parte de ellos asignando valores iniciales a sus propiedades. Todas las funciones, incluidos los constructores, son objetos, pero no todos los objetos son constructores.

¡No veo cómo podría estar más claro! </sarcasm>

Más abajo vemos:

Prototipo Un prototipo es un objeto utilizado para implementar la herencia de estructura, estado y comportamiento en ECMAScript. Cuando un constructor crea un objeto, ese objeto hace referencia implícita al prototipo asociado del constructor con el fin de resolver referencias de propiedad. El prototipo asociado al constructor puede ser referenciado por la expresión del programa constructor.prototype, y todos los objetos que comparten el prototipo comparten las propiedades agregadas al prototipo de un objeto, a través de la herencia.

Entonces podemos ver que un prototipo es un objeto, pero no necesariamente un objeto de función.

Además, tenemos este interesante titbit

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

El constructor de objetos es el objeto intrínseco% Object% y el valor inicial de la propiedad Object del objeto global.

y

El constructor de funciones es el objeto intrínseco% Function% y el valor inicial de la propiedad Function del objeto global.

Ewan
fuente
Ahora lo hace ECMA6 le permite crear clases e instanciar objetos a partir de ellas.
ncmathsadist
2
Las clases de @ncmathsadist ES6 son solo un azúcar sintáctico, la semántica es la misma.
Hamza Fatmi
1
Su sarcasmapodo de lo contrario, este texto realmente es bastante opaco para un principiante.
Robert Harvey
es cierto, agregaré más tarde, necesito cavar un poco
Ewan
1
erm? señalar que no está claro en el documento
Ewan
1

Los siguientes tipos abarcan todos los valores en JavaScript:

  • boolean
  • number
  • undefined(que incluye el valor único undefined)
  • string
  • symbol ("cosas" abstractas únicas que se comparan por referencia)
  • object

Cada objeto (es decir, todo) en JavaScript tiene un prototipo, que es un tipo de objeto.

El prototipo contiene funciones, que también son una especie de objeto 1 .

Los objetos también tienen un constructor, que es una función y, por lo tanto, un tipo de objeto.

anidado

Todo es recursivo, pero la implementación puede hacerlo de forma automática porque, a diferencia del código JavaScript, puede crear objetos sin tener que llamar a funciones JavaScript (ya que los objetos son solo memoria que controla la implementación).

La mayoría de los sistemas de objetos en muchos lenguajes de tipo dinámico son circulares 2 como este. Por ejemplo, en Python, las clases son objetos, y la clase de clases es type, por typelo tanto , es una instancia de sí misma.

La mejor idea es usar las herramientas que proporciona el lenguaje y no pensar demasiado en cómo llegaron allí.

1 Las funciones son bastante especiales porque son invocables y son los únicos valores que pueden contener datos opacos (su cuerpo y posiblemente un cierre).

2 En realidad es más una cinta de ramificación torturada doblada hacia atrás sobre sí misma, pero "circular" está lo suficientemente cerca.

Challenger5
fuente