¿Cómo funciona JavaScript .prototype?

2041

No estoy tan interesado en los lenguajes de programación dinámicos, pero he escrito mi parte justa de código JavaScript. Nunca entendí realmente esta programación basada en prototipos, ¿alguien sabe cómo funciona?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Recuerdo muchas conversaciones que tuve con la gente hace un tiempo (no estoy exactamente seguro de lo que estoy haciendo) pero, según tengo entendido, no hay un concepto de clase. Es solo un objeto, y las instancias de esos objetos son clones del original, ¿verdad?

Pero, ¿cuál es el propósito exacto de esta propiedad ".prototype" en JavaScript? ¿Cómo se relaciona con la creación de instancias de objetos?

Actualización: forma correcta

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Además, estas diapositivas realmente ayudaron mucho.

John Leidegren
fuente
78
John Resig tiene algunas diapositivas sobre los prototipos de funciones que me fueron útiles al analizar el tema (también puede hacer cambios en el código y ver qué sucede ...) http://ejohn.org/apps/learn/#64
John Foster
55
Gran material de referencia, con el fin de mantener esta pregunta informativa, quizás coloque algunos de los comentarios del sitio de John en su respuesta en caso de que su sitio cambie de una manera que su enlace ya no esté disponible. De cualquier manera +1, me ayudó.
Chris
95
+1 para su enlace a la diapositiva ninja 64 de JavaScript de John Resig . Comenzar desde allí fue realmente útil, y siento que entiendo los prototipos correctamente.
un nerd pagado el
44
¿Realmente necesitamos un objeto funcional para aplicar el prototipo? Si es así, ¿por qué?
Anshul
66
Esto podría ayudarlo: webdeveasy.com/javascript-prototype
Naor

Respuestas:

1007

Cada objeto de JavaScript tiene una "ranura" interna llamada [[Prototype]]cuyo valor es uno nullo un object. Puede pensar en una ranura como una propiedad de un objeto, interna al motor de JavaScript, oculta del código que escribe. Los corchetes alrededor [[Prototype]]son deliberados y son una convención de especificación ECMAScript para denotar ranuras internas.

El valor señalado por el [[Prototype]]de un objeto, se conoce coloquialmente como "el prototipo de ese objeto".

Si accede a una propiedad a través de la notación dot ( obj.propName) o corchete ( obj['propName']), y el objeto no tiene directamente dicha propiedad (es decir, una propiedad propia , comprobable mediante obj.hasOwnProperty('propName')), el tiempo de ejecución busca una propiedad con ese nombre en el objeto al que se hace referencia por el [[Prototype]]contrario. Si el [[Prototype]] también no tiene una propiedad tal, su [[Prototype]]se comprueba a su vez, y así sucesivamente. De esta manera, la cadena prototipo del objeto original se camina hasta que se encuentra una coincidencia o se alcanza su final. En la parte superior de la cadena de prototipos está el nullvalor.

Las implementaciones modernas de JavaScript permiten el acceso de lectura y / o escritura a [[Prototype]]las siguientes formas:

  1. El newoperador (configura la cadena de prototipo en el objeto predeterminado devuelto por una función de constructor),
  2. La extendspalabra clave (configura la cadena de prototipo cuando se usa la sintaxis de clase),
  3. Object.createestablecerá el argumento proporcionado como el [[Prototype]]del objeto resultante,
  4. Object.getPrototypeOfy Object.setPrototypeOf(obtener / establecer la creación [[Prototype]] posterior del objeto), y
  5. La propiedad de acceso estandarizada (es decir, getter / setter) denominada __proto__(similar a 4.)

Object.getPrototypeOfy Object.setPrototypeOfson preferibles __proto__, en parte porque el comportamiento de o.__proto__ es inusual cuando un objeto tiene un prototipo de null.

Un objeto [[Prototype]]se establece inicialmente durante la creación del objeto.

Si crea un nuevo objeto mediante new Func(), el objeto [[Prototype]], por defecto, se establecerá en el objeto al que hace referencia Func.prototype.

Tenga en cuenta que, por lo tanto, todas las clases y todas las funciones que se pueden usar con el newoperador tienen una propiedad nombrada .prototypeademás de su propia [[Prototype]]ranura interna. Este uso dual de la palabra "prototipo" es la fuente de confusión interminable entre los recién llegados al idioma.

El uso newcon funciones de constructor nos permite simular la herencia clásica en JavaScript; aunque el sistema de herencia de JavaScript es, como hemos visto, prototípico y no basado en clases.

Antes de la introducción de la sintaxis de clase a JavaScript, las funciones de constructor eran la única forma de simular clases. Podemos pensar en las propiedades del objeto referenciado por la .prototypepropiedad de la función constructora como miembros compartidos; es decir. miembros que son iguales para cada instancia. En los sistemas basados ​​en clases, los métodos se implementan de la misma manera para cada instancia, por lo que los métodos se agregan conceptualmente a la .prototypepropiedad; Sin embargo, los campos de un objeto son específicos de la instancia y, por lo tanto, se agregan al objeto mismo durante la construcción.

Sin la sintaxis de clase, los desarrolladores tuvieron que configurar manualmente la cadena de prototipos para lograr una funcionalidad similar a la herencia clásica. Esto condujo a una preponderancia de diferentes formas de lograr esto.

Aquí hay una manera:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... y aquí hay otra forma:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

La sintaxis de clase introducida en ES2015 simplifica las cosas, al proporcionar extendscomo la "única forma verdadera" de configurar la cadena de prototipos para simular la herencia clásica en JavaScript.

Entonces, similar al código anterior, si usa la sintaxis de clase para crear un nuevo objeto de esta manera:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... el objeto resultante [[Prototype]]se establecerá en una instancia de Parent, que [[Prototype]], a su vez, es Parent.prototype.

Finalmente, si crea un nuevo objeto vía Object.create(foo), el objeto resultante [[Prototype]]se establecerá en foo.

Christoph
fuente
1
Entonces, ¿estoy haciendo algo mal al definir nuevas propiedades en la propiedad del prototipo en mi fragmento corto?
John Leidegren
3
Creo que esto es lo que significa tener objetos funcionales como ciudadanos de primera clase.
John Leidegren
8
Odio las cosas no estándar, especialmente en los lenguajes de programación, ¿por qué hay incluso un protocolo cuando claramente no es necesario?
John Leidegren
1
@John __proto__ es necesario para uso interno solo por el intérprete JS. Cada Objeto necesita saber qué propiedades y métodos son parte del prototipo y cuáles son parte del Objeto mismo. El intérprete JS debe poder llamar a métodos prototipados en un objeto.
BMiner
17
tenga en cuenta que el uso de [[Prototipo]] es deliberado - ECMA-262 incluye nombres de propiedades internas con corchetes dobles
Christoph
1798

En un lenguaje que implementa herencia clásica como Java, C # o C ++, comienza creando una clase, un plano para sus objetos, y luego puede crear nuevos objetos a partir de esa clase o puede extender la clase, definiendo una nueva clase que aumente La clase original.

En JavaScript, primero crea un objeto (no hay un concepto de clase), luego puede aumentar su propio objeto o crear nuevos objetos a partir de él. No es difícil, pero un poco extraño y difícil de metabolizar para alguien acostumbrado a la forma clásica.

Ejemplo:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

Hasta ahora he estado extendiendo el objeto base, ahora creo otro objeto y luego heredo de Person.

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Mientras que como dije no puedo llamar a setAmountDue (), getAmountDue () en una Persona.

//The following statement generates an error.
john.setAmountDue(1000);
stivlo
fuente
352
Creo que las respuestas en stackoverflow no solo son interesantes para el póster original, sino también para una gran comunidad de otras personas que acechan o provienen de búsquedas. Y he sido uno de ellos y me he beneficiado de las publicaciones antiguas. Creo que podría contribuir a las otras respuestas agregando algunos ejemplos de código. Sobre su pregunta: si deja de lado lo nuevo, no funciona. cuando llamo myCustomer.sayMyName () devuelve "myCustomer.sayMyName no es una función". La forma más fácil es experimentar con firebug y ver qué pasa.
stivlo
77
Hasta donde entiendo var Person = function (name) {...}; es definir una función constructora capaz de construir Objetos Personales. Por lo tanto, todavía no hay un Objeto, solo la función de constructor anónimo se asigna a Persona. Esta es una muy buena explicación: helephant.com/2008/08/how-javascript-objects-work
stivlo
17
ADVERTENCIA: Esta respuesta descuida el hecho de que el constructor de la clase principal no se llama por instancia. La única razón por la que funciona es porque hizo exactamente lo mismo (establecer el nombre) tanto en el constructor secundario como en el primario. Para obtener una explicación más detallada sobre los errores comunes cometidos al intentar la herencia en JavaScript (y una solución final), consulte: esta publicación de desbordamiento de pila
Aaren Cordova
3
Noté que esta respuesta tampoco menciona que al usar "new Person ()" como prototipo, en realidad está configurando la propiedad de instancia "name" de "Person" para que sea una propiedad estática de "Customer" (por lo tanto, todos los Customer las instancias tendrán la misma propiedad). Si bien es un buen ejemplo básico, NO HAGAS ESO. :) Cree una nueva función anónima para actuar como un "puente" estableciendo su prototipo en "Person.prototype", luego cree una instancia a partir de ella y establezca "Customer.prototype" en esa instancia anónima.
James Wilkins
10
Acerca de la Customer.prototype = new Person();línea, MDN muestra un ejemplo usando Customer.prototype = Object.create(Person.prototype), y afirma que 'Un error común aquí es usar "new Person ()"' . fuente
Rafael Eyng
186

Este es un modelo de objeto basado en un prototipo muy simple que se consideraría como una muestra durante la explicación, sin ningún comentario todavía:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Hay algunos puntos cruciales que tenemos que considerar antes de pasar por el concepto del prototipo.

1- Cómo funcionan realmente las funciones de JavaScript:

Para dar el primer paso, tenemos que descubrir cómo funcionan realmente las funciones de JavaScript, como una clase como función usando this palabra clave o simplemente como una función regular con sus argumentos, qué hace y qué devuelve.

Digamos que queremos crear un Personmodelo de objeto. pero en este paso voy a estar tratando de hacer exactamente lo mismo sin utilizar prototypey newpalabra clave .

Entonces, en este paso functions, objectsy thispalabra clave, es todo lo que tenemos.

La primera pregunta sería cómo la thispalabra clave podría ser útil sin usar la newpalabra clave .

Entonces, para responder eso, digamos que tenemos un objeto vacío y dos funciones como:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

y ahora sin usar newpalabras clave, cómo podríamos usar estas funciones. Entonces JavaScript tiene 3 formas diferentes de hacer eso:

a. La primera forma es llamar a la función como una función regular:

Person("George");
getName();//would print the "George" in the console

en este caso, este sería el objeto de contexto actual, que generalmente es el windowobjeto global en el navegador o GLOBALenNode.js . Significa que tendríamos window.name en el navegador o GLOBAL.name en Node.js, con "George" como su valor.

si. Podemos adjuntarlos a un objeto, como sus propiedades

- La forma más fácil de hacer esto es modificar el personobjeto vacío , como:

person.Person = Person;
person.getName = getName;

de esta manera podemos llamarlos como:

person.Person("George");
person.getName();// -->"George"

y ahora el personobjeto es como:

Object {Person: function, getName: function, name: "George"}

- La otra forma de adjuntar una propiedad a un objeto es usar prototypeese objeto que se puede encontrar en cualquier objeto JavaScript con el nombre de __proto__, y he tratado de explicarlo un poco en la parte de resumen. Entonces podríamos obtener el resultado similar haciendo:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Pero de esta manera lo que estamos haciendo es modificar el Object.prototype, porque cada vez que creamos un objeto JavaScript usando literales ( { ... }), se crea en función de Object.prototype, lo que significa que se adjunta al objeto recién creado como un atributo llamado __proto__, así que si lo cambiamos , como hemos hecho en nuestro fragmento de código anterior, todos los objetos de JavaScript se cambiarían, no es una buena práctica. Entonces, ¿cuál podría ser la mejor práctica ahora:

person.__proto__ = {
    Person: Person,
    getName: getName
};

y ahora otros objetos están en paz, pero todavía no parece ser una buena práctica. Así que todavía tenemos una solución más, pero para usar esta solución, debemos volver a esa línea de código donde personse creó el objeto ( var person = {};) y luego cambiarlo como:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

lo que hace es crear un nuevo JavaScript Objecty adjuntarlo propertiesObjectal __proto__atributo. Entonces, para asegurarse de que puede hacer:

console.log(person.__proto__===propertiesObject); //true

Pero el punto difícil aquí es que tiene acceso a todas las propiedades definidas en __proto__el primer nivel del personobjeto (lea la parte de resumen para obtener más detalles).


Como puede ver, el uso de cualquiera de estas dos vías thisapuntaría exactamente al personobjeto.

C. JavaScript tiene otra forma de proporcionar la función this, que es llamar o aplicar para invocar la función.

El método apply () llama a una función con un valor dado y argumentos proporcionados como una matriz (o un objeto similar a una matriz).

y

El método call () llama a una función con un valor dado y argumentos proporcionados individualmente.

de esta manera, mi favorita, podemos llamar fácilmente a nuestras funciones como:

Person.call(person, "George");

o

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

Estos 3 métodos son los pasos iniciales importantes para descubrir la funcionalidad .prototype.


2- ¿Cómo funciona la newpalabra clave?

Este es el segundo paso para comprender la .prototypefuncionalidad. Esto es lo que uso para simular el proceso:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

en esta parte voy a tratar de seguir todos los pasos que toma JavaScript, sin usar la newpalabra clave y prototype, cuando use la newpalabra clave. así que cuando lo hacemos new Person("George"), la Personfunción sirve como un constructor. Esto es lo que hace JavaScript, uno por uno:

a. En primer lugar, crea un objeto vacío, básicamente un hash vacío como:

var newObject = {};

si. el siguiente paso que toma JavaScript es adjuntar todos los objetos prototipo al objeto recién creado

Tenemos my_person_prototypeaquí similar al objeto prototipo.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

No es la forma en que JavaScript realmente une las propiedades que se definen en el prototipo. La forma real está relacionada con el concepto de cadena prototipo.


a. y b. En lugar de estos dos pasos, puede obtener exactamente el mismo resultado haciendo:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

ahora podemos llamar a la getNamefunción en nuestro my_person_prototype:

newObject.getName();

C. entonces le da ese objeto al constructor,

podemos hacer esto con nuestra muestra como:

Person.call(newObject, "George");

o

Person.apply(newObject, ["George"]);

entonces el constructor puede hacer lo que quiera, porque este dentro de ese constructor es el objeto que se acaba de crear.

ahora el resultado final antes de simular los otros pasos: Object {name: "George"}


Resumen:

Básicamente, cuando usa la nueva palabra clave en una función, está invocando eso y esa función sirve como un constructor, por lo que cuando dice:

new FunctionName()

JavaScript crea internamente un objeto, un hash vacío y luego le da ese objeto al constructor, luego el constructor puede hacer lo que quiera, porque esto dentro de ese constructor es el objeto que acaba de crear y luego le da ese objeto, por supuesto. si no ha usado la declaración return en su función o si ha puesto un return undefined;al final del cuerpo de su función.

Entonces, cuando JavaScript busca una propiedad en un objeto, lo primero que hace es buscarlo en ese objeto. Y luego hay una propiedad secreta [[prototype]]que usualmente tenemos __proto__y esa propiedad es lo que JavaScript ve a continuación. Y cuando mira a través de __proto__, en la medida en que es de nuevo otro objeto JavaScript, tiene su propio __proto__atributo, sube y sube hasta llegar al punto donde el siguiente __proto__es nulo. El punto es que el único objeto en JavaScript que su __proto__atributo es nulo es el Object.prototypeobjeto:

console.log(Object.prototype.__proto__===null);//true

y así es como funciona la herencia en JavaScript.

La cadena prototipo

En otras palabras, cuando tiene una propiedad prototipo en una función y llama a una nueva sobre eso, después de que JavaScript termina de buscar propiedades en ese objeto recién creado, verá las funciones de la función .prototypey también es posible que este objeto tenga su Propio prototipo interno. y así.

Mehran Hatami
fuente
66
a) No explique los prototipos copiando las propiedades b) La configuración del [[prototipo]] interno ocurre antes de que se aplique la función de constructor en la instancia, cambie ese orden c) jQuery está totalmente fuera de tema en esta pregunta
Bergi
1
@ Bergi: gracias por señalarme, te agradecería si me avisas si está bien ahora
Mehran Hatami
77
¿Puedes por favor hacerlo simple? Tienes razón en todos los puntos, pero los estudiantes que leen esta explicación pueden estar realmente confundidos por primera vez. elija un ejemplo más simple y deje que el código se explique por sí mismo o agregue un montón de comentarios para aclarar lo que quiere decir.
PM
2
@PM: Gracias por tus comentarios. He tratado de hacerlo lo más simple posible, pero creo que tienes razón, todavía tiene algunos puntos vagos. Así que intentaré modificarlo y también ser más descriptivo. :)
Mehran Hatami
1
@AndreaMattioli porque de esta manera estás creando un objeto totalmente nuevo y anulando el antiguo que también podría compartirse entre otros objetos. Al reemplazarlo __proto__, primero eliminará todas las propiedades de prototipo de nivel superior y luego tendrá una nueva base de prototipos que ya no se comparte a menos que la comparta.
Mehran Hatami
77

Los siete Koans del prototipo

Mientras Ciro San descendía del Monte Fire Fox después de una profunda meditación, su mente estaba clara y pacífica.

Sin embargo, su mano estaba inquieta, y por sí misma agarró un pincel y anotó las siguientes notas.


0) Dos cosas diferentes se pueden llamar "prototipo":

  • la propiedad prototipo, como en obj.prototype

  • la propiedad interna del prototipo, denotada como [[Prototype]] en ES5 .

    Se puede recuperar a través del ES5 Object.getPrototypeOf() .

    Firefox lo hace accesible a través de la __proto__propiedad como una extensión. ES6 ahora menciona algunos requisitos opcionales para __proto__.


1) Esos conceptos existen para responder la pregunta:

Cuando lo hago obj.property, ¿dónde busca JS.property ?

Intuitivamente, la herencia clásica debería afectar la búsqueda de propiedades.


2)

  • __proto__se usa para la .búsqueda de propiedades de puntos como en obj.property.
  • .prototypese no se utiliza para la búsqueda directamente, sólo de manera indirecta, ya que determina __proto__en la creación de objetos con new.

El orden de búsqueda es:

  • objpropiedades agregadas con obj.p = ...oObject.defineProperty(obj, ...)
  • propiedades de obj.__proto__
  • propiedades de obj.__proto__.__proto__ , etc.
  • si alguno __proto__es null, regrese undefined.

Esta es la llamada cadena prototipo .

Puede evitar la .búsqueda con obj.hasOwnProperty('key')yObject.getOwnPropertyNames(f)


3) Hay dos formas principales de configurar obj.__proto__:

  • new:

    var F = function() {}
    var f = new F()

    luego newha establecido:

    f.__proto__ === F.prototype

    Aquí es donde .prototypese acostumbra.

  • Object.create:

     f = Object.create(proto)

    establece:

    f.__proto__ === proto

4) El código:

var F = function(i) { this.i = i }
var f = new F(1)

Corresponde al siguiente diagrama ( Numberse omiten algunas cosas):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

Este diagrama muestra muchos nodos de objeto predefinidos de lenguaje:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(se puede encontrar con (1).__proto__paréntesis obligatorio para satisfacer la sintaxis)

Nuestras 2 líneas de código solo crearon los siguientes objetos nuevos:

  • f
  • F
  • F.prototype

iahora es una propiedad de fporque cuando lo haces:

var f = new F(1)

se evalúa Fcon thisser el valor que newva a volver, que luego se asigna a f.


5) .constructor normalmente proviene de F.prototypela .búsqueda:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

Cuando escribimos f.constructor, JavaScript realiza la .búsqueda como:

  • f no tiene .constructor
  • f.__proto__ === F.prototypetiene .constructor === F, así que tómalo

El resultado f.constructor == Fes intuitivamente correcto, ya que Fse utiliza para construir f, por ejemplo, establecer campos, como en los lenguajes clásicos de OOP.


6) La sintaxis de herencia clásica se puede lograr manipulando cadenas de prototipos.

ES6 agrega las palabras clave classy extends, que son principalmente azúcar de sintaxis para la locura de manipulación de prototipos previamente posible.

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Diagrama simplificado sin todos los objetos predefinidos:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

Tomemos un momento para estudiar cómo funciona lo siguiente:

c = new C(1)
c.inc() === 2

Los primeros conjuntos de línea c.ia 1como se explica en "4)".

En la segunda línea, cuando hacemos:

c.inc()
  • .incse encuentra a través de la [[Prototype]]cadena: c-> C-> C.prototype->inc
  • cuando llamamos a una función en Javascript como X.Y(), ¡JavaScript se establece automáticamente thisen igual Xdentro de la Y()llamada a la función!

La misma lógica exacta también explica d.incy d.inc2.

Este artículo https://javascript.info/class#not-just-a-syntax-sugar menciona otros efectos que classvale la pena conocer. Algunos de ellos pueden no ser alcanzables sin la classpalabra clave (TODO verifique cuál):

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
@tomasb gracias! "No sé de dónde sacaste esto": después de haber visto algunos de esos lenguajes dinámicos, noté que lo más importante sobre su sistema de clases es cómo funciona la .búsqueda (y cuántas copias de los datos se hacen) . Así que me propuse entender ese punto. El resto es Google + publicaciones de blog + un intérprete Js a la mano. :)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Todavía no entiendo por qué g.constructor === Object porque dijiste que "4) Cuando haces f = new F, new también establece f.constructor = F". ¿Podrías explicarme más? De todos modos, esta es la mejor respuesta que estoy buscando. Muchas gracias!
nguyenngoc101
@ nguyenngoc101 gracias! La sets f.constructor = Fparte era descaradamente incorrecta y contradecía con otras secciones: .constructorse encuentra a través de la .búsqueda en la cadena de prototipos. Lo arregló ahora.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
De toda la discusión, lo que obtengo (proviene de la herencia clásica) si creo una función de constructor e intento crear una instancia con un nuevo operador, solo obtendré métodos y propiedades que se adjuntaron al prototipo, por lo tanto, es necesario adjuntar todo el método y propiedades para protoobjetar si queremos heredar, ¿no?
blackHawk
1
@CiroSantilli 刘晓波 死 六四 事件 法轮功 No creo que sea un error en Chromium. Creo que es solo un síntoma que fel prototipo se establece Fsolo en el momento de la construcción; fno lo sabrá ni le importará F.prototypeen ningún momento después de su primera construcción.
John Glassmyer
76

prototypete permite hacer clases. si no lo usa prototype, se convierte en estático.

Aquí hay un breve ejemplo.

var obj = new Object();
obj.test = function() { alert('Hello?'); };

En el caso anterior, tiene una prueba de llamada de función estática. A esta función solo se puede acceder mediante obj.test, donde puede imaginar que obj sea una clase.

donde como en el siguiente código

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

El obj se ha convertido en una clase que ahora se puede instanciar. Pueden existir múltiples instancias de obj y todas tienen latest función.

Lo anterior es mi entendimiento. Lo estoy haciendo un wiki comunitario, para que la gente pueda corregirme si me equivoco.

Ramesh
fuente
13
-1: prototypees una propiedad de las funciones de constructor, no de instancias, es decir, ¡su código está equivocado! Tal vez usted significó la propiedad de no-estándar __proto__de los objetos, pero eso es una bestia completamente diferente ...
Christoph
@ Christoph - Gracias por señalarlo. He actualizado el código de muestra.
Ramesh
3
Hay mucho más ... Además, JavaScript no es un lenguaje basado en clases: trata la herencia a través de prototipos, ¡debe cubrir las diferencias con más detalle!
James
55
Creo que esta respuesta es un poco equivocada.
Armin Cifuentes
Tal vez la respuesta es "equivocada", pero explica para qué se utiliza el prototipo y ahora está todo claro para mí, después de todas esas "respuestas" con cientos de votos "arriba". Gracias.
Aleks
66

Después de leer este hilo, me siento confundido con JavaScript Prototype Chain, luego encontré estos gráficos

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance * [[protytype]] * y propiedad <code> prototype </code> de objetos de función

es un gráfico claro para mostrar la herencia de JavaScript por cadena de prototipo

y

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

Este contiene un ejemplo con código y varios diagramas agradables.

La cadena de prototipos finalmente vuelve a Object.prototype.

La cadena de prototipos puede ampliarse técnicamente todo el tiempo que desee, cada vez estableciendo el prototipo de la subclase igual a un objeto de la clase padre.

Espero que también sea útil para que entiendas la cadena de prototipos de JavaScript.

rockXrock
fuente
¿Es posible tener herencia múltiple en Javascript?
¿Es Foo un objeto literal aquí u objeto de función? Si es un objeto literal, creo que Foo.prototype no volverá a apuntar a Foo a través del constructor.
Madhur Ahuja
@ user3376708 JavaScript solo admite herencia única ( fuente )
Rafael Eyng
@ Nuno_147 Al principio no está claro, pero si miras lo suficiente, podrías sacar algo de eso.
marcelocra
3
¿Puedes explicar qué [[Prototype]]significa?
CodyBugstein
40

Cada objeto tiene una propiedad interna, [[Prototipo]] , que lo vincula a otro objeto:

object [[Prototype]]  anotherObject

En javascript tradicional, el objeto vinculado es prototypepropiedad de una función:

object [[Prototype]]  aFunction.prototype

Algunos entornos exponen [[Prototipo]] como __proto__:

anObject.__proto__ === anotherObject

Usted crea el enlace [[Prototipo]] cuando crea un objeto.

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

Entonces estas declaraciones son equivalentes:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

En realidad no puede ver el destino del enlace ( Object.prototype) en una nueva declaración; en cambio, el objetivo está implícito por el constructor (Object ).

Recuerda:

  • Cada objeto tiene un enlace, [[Prototipo]] , a veces expuesto como __proto__ .
  • Cada función tiene una prototypepropiedad, inicialmente sosteniendo un objeto vacío.
  • Los objetos creados con new están vinculados a la prototypepropiedad de su constructor.
  • Si una función nunca se usa como constructor, su prototypepropiedad no se usará.
  • Si no necesita un constructor, use Object.create en lugar de new.
sam
fuente
1
La revisión 5 eliminó información útil, incluida información sobre Object.create (). Ver revisión 4 .
Palec
@Palec, ¿qué debo agregar?
sam
2
OMI al menos el enlace a Object.create()documentos , @sam. Enlaces __proto__y Object.prototypeserían buenas mejoras. Y me gustaron tus ejemplos de cómo funcionan los prototipos con los constructores y Object.create(), pero probablemente fueron la parte larga y menos relevante de la que querías deshacerte.
Palec
De toda la discusión, lo que obtengo (vino de la herencia clásica) si creo una función de constructor e intento crear una instancia de él usando un nuevo operador, solo obtendré métodos y propiedades que se adjuntaron al prototipo, por lo tanto, es necesario adjuntar todo el método y propiedades para protoobjetar si queremos heredar, ¿no?
blackHawk
29

Javascript no tiene herencia en el sentido habitual, pero tiene la cadena de prototipo.

cadena prototipo

Si no se puede encontrar un miembro de un objeto en el objeto, lo busca en la cadena de prototipos. La cadena consta de otros objetos. Se puede acceder al prototipo de una instancia dada con la __proto__variable. Cada objeto tiene uno, ya que no hay diferencia entre clases e instancias en javascript.

La ventaja de agregar una función / variable al prototipo es que tiene que estar en la memoria solo una vez, no para cada instancia.

También es útil para la herencia, porque la cadena del prototipo puede consistir en muchos otros objetos.

Georg Schölly
fuente
1
FF y Chrome admiten proto , pero no IE ni Opera.
algunos
Georg, aclara para un novato: "no hay diferencia entre clases e instancias en javascript". - ¿Podrías dar más detalles? ¿Como funciona esto?
Hamish Grubijan, el
De toda la discusión, lo que obtengo (vino de la herencia clásica) si creo una función de constructor e intento crear una instancia de él usando un nuevo operador, solo obtendré métodos y propiedades que se adjuntaron al prototipo, por lo tanto, es necesario adjuntar todo el método y propiedades para protoobjetar si queremos heredar, ¿no?
blackHawk
28

Este artículo es largo. Pero estoy seguro de que eliminará la mayoría de sus consultas sobre la naturaleza "prototípica" de la herencia de JavaScript. Y aun más. Por favor lea el artículo completo.

JavaScript básicamente tiene dos tipos de tipos de datos

  • No objetos
  • Objetos

No objetos

Los siguientes son el no objeto tipos de datos

  • cuerda
  • número (incluidos NaN e Infinity)
  • valores booleanos (verdadero, falso)
  • indefinido

Estos tipos de datos devuelven los siguientes cuando utiliza el operador typeof

typeof "string literal" (o una variable que contiene string literal) === 'string'

typeof 5 (o cualquier literal numérico o una variable que contenga literal numérico o NaN o Infynity ) === 'número'

tipo de verdadero (o falso o una variable que contiene verdadero o falso ) === 'boolean'

tipo de indefinido (o una variable indefinida o una variable que contiene indefinido ) === 'undefined'

Los tipos de datos de cadena , número y booleanos se pueden representar como objetos y no objetos . Cuando se representan como objetos, su tipo de siempre es === 'objeto'. Volveremos a esto una vez que comprendamos los tipos de datos del objeto.

Objetos

Los tipos de datos del objeto se pueden dividir en dos tipos.

  1. Objetos de tipo de función
  2. Objetos de tipo no funcional

Los objetos de tipo Function son los que devuelven la cadena 'function' con el operador typeof . Todas las funciones definidas por el usuario y todos los objetos integrados de JavaScript que pueden crear nuevos objetos mediante el uso de un nuevo operador entran en esta categoría. Por ej.

  • Objeto
  • Cuerda
  • Número
  • Booleano
  • Formación
  • Matrices Mecanografiadas
  • RegExp
  • Función
  • Todos los demás objetos integrados que pueden crear nuevos objetos utilizando un nuevo operador
  • function UserDefinedFunction () {/ * código definido por el usuario * /}

Entonces, typeof (Object) === typeof (String) === typeof (Number) === typeof (Boolean) === typeof (Array) === typeof (RegExp) === typeof (Function) == = typeof (UserDefinedFunction) === 'función'

Todos los objetos de tipo Function son en realidad instancias de la función de objeto JavaScript incorporada (incluido el objeto Function, es decir, se define de forma recursiva). Es como si estos objetos se hubieran definido de la siguiente manera

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

Como se mencionó, los objetos de tipo Función pueden crear nuevos objetos usando el nuevo operador . Por ejemplo, un objeto de tipo Object , String , Number , Boolean , Array , RegExp Or UserDefinedFunction se puede crear utilizando

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

Los objetos así creados son todos los objetos que no son de tipo de funciones y devuelven su typeof === 'objeto' . En todos estos casos, el objeto "a" no puede crear más objetos usando el operador new. Entonces lo siguiente está mal

var b=new a() //error. a is not typeof==='function'

El objeto incorporado Math es typeof === 'objeto' . Por lo tanto, un nuevo objeto de tipo Math no puede ser creado por un nuevo operador.

var b=new Math() //error. Math is not typeof==='function'

Observe también que las funciones Object , Array y RegExp pueden crear un nuevo objeto sin siquiera usar el operador new . Sin embargo, los siguientes no lo hacen.

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

Las funciones definidas por el usuario son casos especiales.

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

Como los objetos de tipo Función pueden crear nuevos objetos, también se denominan Constructores .

Cada Constructor / Función (ya sea integrado o definido por el usuario) cuando se define automáticamente tiene una propiedad llamada "prototipo" cuyo valor por defecto se establece como un objeto. Este objeto tiene una propiedad llamada "constructor" que por defecto hace referencia al Constructor / Función .

Por ejemplo cuando definimos una función

function UserDefinedFunction()
{
}

siguiente sucede automáticamente

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

Esta propiedad "prototipo" solo está presente en los objetos de tipo Función (y nunca en los objetos de tipo No Función ).

Esto se debe a que cuando se crea un nuevo objeto (usando un nuevo operador) hereda todas las propiedades y métodos del objeto prototipo actual de la función Constructor, es decir, se crea una referencia interna en el objeto recién creado que hace referencia al objeto referenciado por el objeto prototipo actual de la función Constructor.

Esta "referencia interna" que se crea en el objeto para hacer referencia a propiedades heredadas se conoce como el prototipo del objeto (que hace referencia al objeto al que hace referencia la propiedad "prototipo" del Constructor pero es diferente de él). Para cualquier objeto (Función o No Función) esto se puede recuperar utilizando el método Object.getPrototypeOf () . Usando este método, se puede rastrear la cadena prototipo de un objeto.

Además, cada objeto que se crea ( tipo de función o tipo sin función ) tiene una propiedad "constructor" que se hereda del objeto al que hace referencia la propiedad prototipo de la función Constructor. Por defecto, esta propiedad "constructor" hace referencia a la función Constructor que la creó (si el "prototipo" predeterminado de la función Constructor no se modifica).

Para todos los objetos de tipo Function, la función constructora siempre es function Function () {}

Para los objetos de tipo Sin función (por ejemplo, Objeto matemático incorporado Javascript), la función constructora es la función que lo creó. Para el objeto Math es la función Object () {} .

Todo el concepto explicado anteriormente puede ser un poco desalentador de entender sin ningún código de soporte. Siga el siguiente código línea por línea para comprender el concepto. Intenta ejecutarlo para tener una mejor comprensión.

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

La cadena de prototipos de cada objeto finalmente se remonta a Object.prototype (que en sí mismo no tiene ningún objeto prototipo). El siguiente código se puede usar para rastrear la cadena prototipo de un objeto

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

La cadena de prototipos para varios objetos funciona de la siguiente manera.

  • Cada objeto Function (incluido el objeto Function incorporado) -> Function.prototype -> Object.prototype -> null
  • Objetos simples (creados por un nuevo objeto () o {} incluido el objeto matemático incorporado) -> Object.prototype -> null
  • Objeto creado con nuevo o Object.create -> Una o más cadenas de prototipos -> Object.prototype -> null

Para crear un objeto sin ningún prototipo, use lo siguiente:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

Uno podría pensar que establecer la propiedad prototipo del Constructor en nulo creará un objeto con un prototipo nulo. Sin embargo, en tales casos, el prototipo del objeto recién creado se establece en Object.prototype y su constructor se establece en función Object. Esto se demuestra con el siguiente código

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

Siguiendo en el resumen de este artículo

  • Hay dos tipos de objetos: Tipos de función y Tipos de no función.
  • Solo los objetos de tipo Función pueden crear un nuevo objeto utilizando el operador new . Los objetos así creados son objetos de tipo No Función . Los objetos de tipo Non Function no pueden crear más un objeto usando el operador new .

  • Todos los objetos de tipo Función tienen por defecto una propiedad "prototipo" . Esta propiedad "prototipo" hace referencia a un objeto que tiene una propiedad "constructora" que por defecto hace referencia al objeto Tipo de función en sí.

  • Todos los objetos ( tipo de función y tipo sin función ) tienen una propiedad "constructor" que por defecto hace referencia al objeto de tipo de función / constructor que lo creó.

  • Cada objeto que se crea internamente hace referencia al objeto al que hace referencia la propiedad "prototipo" del Constructor que lo creó. Este objeto se conoce como el prototipo del objeto creado (que es diferente de la propiedad "prototipo" de los objetos de tipo Function a la que hace referencia). De esta forma, el objeto creado puede acceder directamente a los métodos y propiedades definidos en el objeto referenciado por la propiedad "prototipo" del Constructor (en el momento de la creación del objeto).

  • El prototipo de un objeto (y, por lo tanto, sus nombres de propiedad heredados) se puede recuperar utilizando el método Object.getPrototypeOf () . De hecho, este método se puede utilizar para navegar por toda la cadena de prototipos del objeto.

  • La cadena de prototipo de cada objeto finalmente se remonta a Object.prototype (a menos que el objeto se cree usando Object.create (nulo) en cuyo caso el objeto no tiene prototipo).

  • typeof (new Array ()) === 'objeto' es por diseño de lenguaje y no un error como lo señala Douglas Crockford

  • Establecer la propiedad del prototipo del Constructor en nulo (o indefinido, número, verdadero, falso, cadena) no creará un objeto con un prototipo nulo. En tales casos, el prototipo del objeto recién creado se establece en Object.prototype y su constructor se establece en la función Object.

Espero que esto ayude.

Arup Hore
fuente
24

El concepto de prototypalherencia es uno de los más complicados para muchos desarrolladores. Tratemos de entender la raíz del problema para entender prototypal inheritancemejor. Comencemos con una plainfunción.

ingrese la descripción de la imagen aquí

Si usamos un newoperador en el Tree function, lo llamamos como una constructorfunción.

ingrese la descripción de la imagen aquí

Cada JavaScriptfunción tiene un prototype. Cuando inicias sesión Tree.prototype, obtienes ...

ingrese la descripción de la imagen aquí

Si observa el console.log()resultado anterior , podría ver una propiedad de constructor Tree.prototypey una __proto__propiedad también. El __proto__representa el hecho de prototypeque esto functionse basa, y dado que esto es simplemente un plano JavaScript functionsin inheritanceconfiguración aún, se refiere al Object prototypeque es algo que se ha incorporado a JavaScript ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

Esto tiene cosas como .toString, .toValue, .hasOwnPropertyetc.

__proto__que fue traído mi mozilla está en desuso y se reemplaza por el Object.getPrototypeOfmétodo para obtener el object's prototype.

ingrese la descripción de la imagen aquí

Object.getPrototypeOf(Tree.prototype); // Object {} 

Agreguemos un método a nuestro Tree prototype.

ingrese la descripción de la imagen aquí

Hemos modificado Rooty agregado una functionrama.

ingrese la descripción de la imagen aquí

Eso significa que cuando crea un instancede Tree, puede llamar a su branchmétodo.

ingrese la descripción de la imagen aquí

También podemos agregar primitiveso objectsa nuestro Prototype.

ingrese la descripción de la imagen aquí

Agreguemos un child-treea nuestro Tree.

ingrese la descripción de la imagen aquí

Aquí Childhereda prototypede Tree, lo que estamos haciendo aquí es usar un Object.create()método para crear un nuevo objeto basado en lo que pasas, aquí está Tree.prototype. En este caso, lo que estamos haciendo es configurar el prototipo de Child en un nuevo objeto que se ve idéntico al Treeprototipo. A continuación, estamos configurando el Child's constructor to Child, si no lo hacemos, apuntaría Tree().

ingrese la descripción de la imagen aquí

Childahora tiene el suyo prototype, sus __proto__puntos Treey Tree's prototypepuntos a la base Object.

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

Ahora crea un instanceof Childy call branchque originalmente está disponible en Tree. En realidad no hemos definido nuestro branchen el Child prototype. PERO, en el Root prototypeque el niño hereda.

ingrese la descripción de la imagen aquí

En JS no todo es un objeto, todo puede actuar como un objeto.

Javascripttiene primitivas como strings, number, booleans, undefined, null.No son object(i.e reference types), pero ciertamente pueden actuar como un object. Veamos un ejemplo aquí.

ingrese la descripción de la imagen aquí

En la primera línea de este listado, primitivese asigna un valor de cadena al nombre. La segunda línea trata el nombre como un objecty llama charAt(0)usando la notación de puntos.

Esto es lo que sucede detrás de escena: // lo que hace el JavaScriptmotor

ingrese la descripción de la imagen aquí

El String objectexiste sólo para una declaración antes de que sea destruido (un proceso llamado autoboxing). Volvamos de nuevo a nuestro prototypal inheritance.

  • Javascriptadmite herencia vía delegationbasada en prototypes.
  • Cada uno Functiontiene una prototypepropiedad, que se refiere a otro objeto.
  • properties/functionsse miran desde objectsí mismos o mediante prototypecadena si no existe

A prototypeen JS es un objeto que yieldsusted es el padre de otro object. [es decir, delegación] Delegation significa que si no puede hacer algo, le dirá a otra persona que lo haga por usted.

ingrese la descripción de la imagen aquí

https://jsfiddle.net/say0tzpL/1/

Si busca el violín anterior, el perro tiene acceso al toStringmétodo, pero no está disponible en él, pero está disponible a través de la cadena prototipo que delega aObject.prototype

ingrese la descripción de la imagen aquí

Si observa el siguiente, estamos tratando de acceder al callmétodo que está disponible en todos function.

ingrese la descripción de la imagen aquí

https://jsfiddle.net/rknffckc/

Si busca el violín anterior, ProfileFunction tiene acceso al callmétodo, pero no está disponible en él, pero está disponible a través de la cadena de prototipos que delega aFunction.prototype

ingrese la descripción de la imagen aquí

Nota: prototype es una propiedad del constructor de funciones, mientras que __proto__es una propiedad de los objetos construidos a partir del constructor de funciones. Cada función viene con una prototypepropiedad cuyo valor es un vacío object. Cuando creamos una instancia de la función, obtenemos una propiedad interna [[Prototype]]o __proto__cuya referencia es el prototipo de la Función constructor.

ingrese la descripción de la imagen aquí

El diagrama anterior parece un poco complicado, pero muestra la imagen completa de cómo prototype chainingfunciona. Pasemos por esto lentamente:

Hay dos instancias b1y b2, cuyo constructor es Bary padre es Foo y tiene dos métodos de cadena de prototipo identifyy speakvía BaryFoo

ingrese la descripción de la imagen aquí

https://jsfiddle.net/kbp7jr7n/

Si busca el código anterior, tenemos un Fooconstructor que tiene el método identify()y el Barconstructor que tiene el speakmétodo. Creamos dos Barinstancias b1y b2cuyo tipo principal es Foo. Ahora, mientras llamamos al speakmétodo de Bar, somos capaces de identificar quién llama al hablar por prototypecadena.

ingrese la descripción de la imagen aquí

Barahora tiene todos los métodos de los Foocuales se definen en su prototype. Vamos a profundizar más en la comprensión de la Object.prototypey Function.prototype, y cómo se relacionan. Si buscas el constructor de Foo, Bary Objectson Function constructor.

ingrese la descripción de la imagen aquí

El prototypede Bares Foo, prototypede Fooes Objecty si miras de cerca el prototypede Fooestá relacionado Object.prototype.

ingrese la descripción de la imagen aquí

Antes de cerrar esto, terminemos con un pequeño código aquí para resumir todo lo anterior . Estamos utilizando el instanceofoperador aquí para verificar si un objecttiene en su prototypecadena la prototypepropiedad de un constructorque a continuación resume todo el diagrama grande.

ingrese la descripción de la imagen aquí

Espero que esto agregue algo de información, sé que esto podría ser algo grande de entender ... en palabras simples , ¡son solo objetos vinculados a objetos!

Thalaivar
fuente
22

¿Cuál es el propósito exacto de esta propiedad ".prototype"?

La interfaz con las clases estándar se vuelve extensible. Por ejemplo, está utilizando la Arrayclase y también necesita agregar un serializador personalizado para todos sus objetos de matriz. ¿Pasaría tiempo codificando una subclase, o usaría composición o ... La propiedad prototipo resuelve esto al permitir que los usuarios controlen el conjunto exacto de miembros / métodos disponibles para una clase.

Piense en los prototipos como un puntero vtable adicional. Cuando faltan algunos miembros de la clase original, el prototipo se busca en tiempo de ejecución.

Dirkgently
fuente
21

Puede ser útil clasificar las cadenas de prototipos en dos categorías.

Considere el constructor:

 function Person() {}

El valor de Object.getPrototypeOf(Person)es una función. De hecho lo es Function.prototype. Como Personse creó como una función, comparte el mismo objeto de función prototipo que tienen todas las funciones. Es lo mismo que Person.__proto__, pero esa propiedad no debe usarse. De todos modos, con Object.getPrototypeOf(Person)usted efectivamente sube la escalera de lo que se llama la cadena prototipo.

La cadena en dirección ascendente se ve así:

    PersonFunction.prototypeObject.prototype(punto final)

Importante es que esta cadena de prototipos tiene poco que ver con los objetos que Personpueden construir . Esos objetos construidos tienen su propia cadena de prototipo, y esta cadena potencialmente no puede tener un ancestro cercano en común con el mencionado anteriormente.

Tome por ejemplo este objeto:

var p = new Person();

p no tiene una relación directa de cadena de prototipo con Person . Su relación es diferente. El objeto p tiene su propia cadena prototipo. Usando Object.getPrototypeOf, encontrará que la cadena es la siguiente:

    pPerson.prototypeObject.prototype(punto final)

No hay ningún objeto de función en esta cadena (aunque eso podría ser).

Por lo tanto, Personparece estar relacionado con dos tipos de cadenas, que viven sus propias vidas. Para "saltar" de una cadena a la otra, usa:

  1. .prototype: salta de la cadena del constructor a la cadena del objeto creado. Por lo tanto, esta propiedad solo se define para objetos de función (ya newque solo se puede usar en funciones).

  2. .constructor: salta de la cadena del objeto creado a la cadena del constructor.

Aquí hay una presentación visual de las dos cadenas prototipo involucradas, representadas como columnas:

ingrese la descripción de la imagen aquí

Resumir:

La prototypepropiedad no proporciona información de la cadena de prototipos del sujeto , sino de los objetos creados por el sujeto.

No sorprende que el nombre de la propiedad prototypepueda generar confusión. Quizás hubiera sido más claro si esta propiedad hubiera sido nombrada prototypeOfConstructedInstanceso algo así.

Puede saltar de un lado a otro entre las dos cadenas prototipo:

Person.prototype.constructor === Person

Esta simetría se puede romper asignando explícitamente un objeto diferente a la prototypepropiedad (más sobre eso más adelante).

Crear una función, obtener dos objetos

Person.prototypees un objeto que se creó al mismo tiempo que se creó la función Person. Tiene Personcomo constructor, a pesar de que ese constructor aún no se ejecutó. Entonces se crean dos objetos al mismo tiempo:

  1. La función en Person
  2. El objeto que actuará como prototipo cuando la función se llame como constructor.

Ambos son objetos, pero tienen diferentes roles: el objeto de la función construye , mientras que el otro objeto representa el prototipo de cualquier objeto que la función construya. El objeto prototipo se convertirá en el padre del objeto construido en su cadena prototipo.

Dado que una función también es un objeto, también tiene su propio padre en su propia cadena de prototipo, pero recuerde que estas dos cadenas son sobre cosas diferentes.

Aquí hay algunas igualdades que podrían ayudar a comprender el problema: todas estas se imprimen true:

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

Agregar niveles a la cadena prototipo

Aunque se crea un objeto prototipo cuando crea una función de constructor, puede ignorar ese objeto y asignar otro objeto que debería usarse como prototipo para cualquier instancia posterior creada por ese constructor.

Por ejemplo:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

Ahora la cadena prototipo de t es un paso más larga que la de p :

    tpPerson.prototypeObject.prototype(punto final)

La otra cadena de prototipos ya no es: Thiefy Personson hermanos que comparten el mismo padre en su cadena de prototipos:

    Person}
    Thief  } → Function.prototypeObject.prototype(punto final)

El gráfico presentado anteriormente se puede extender a esto (el original Thief.prototypese omite):

ingrese la descripción de la imagen aquí

Las líneas azules representan cadenas prototipo, las otras líneas de colores representan otras relaciones:

  • entre un objeto y su constructor
  • entre un constructor y el objeto prototipo que se usará para construir objetos
trincot
fuente
16

Me pareció útil explicar la "cadena de prototipo" como convención recursiva cuando obj_n.prop_Xse hace referencia a ella:

si obj_n.prop_Xno existe, verifique obj_n+1.prop_Xdóndeobj_n+1 = obj_n.[[prototype]]

Si prop_Xfinalmente se encuentra en el objeto prototipo k-ésimo, entonces

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

Puede encontrar un gráfico de la relación de los objetos Javascript por sus propiedades aquí:

gráfico de objetos js

http://jsobjects.org

BM
fuente
14

Cuando un constructor crea un objeto, ese objeto hace referencia implícita a la propiedad "prototipo" del constructor con el fin de resolver referencias de propiedad. La expresión de programa constructor.prototype puede hacer referencia a la propiedad del "prototipo" del constructor, y todos los objetos que comparten el prototipo comparten las propiedades agregadas al prototipo de un objeto, a través de la herencia.

Tom
fuente
11

Aquí hay dos entidades distintas pero relacionadas que deben explicarse:

  • La .prototypepropiedad de las funciones.
  • La propiedad [[Prototype]][1] de todos los objetos [2] .

Estas son dos cosas diferentes.

La [[Prototype]]propiedad:

Esta es una propiedad que existe en todos los objetos [2] .

Lo que se almacena aquí es otro objeto, que, como un objeto en sí, tiene uno [[Prototype]]propio que apunta a otro objeto. Ese otro objeto tiene uno [[Prototype]]propio. Esta historia continúa hasta llegar al objeto prototípico que proporciona métodos accesibles en todos los objetos (como .toString).

La [[Prototype]]propiedad es parte de lo que forma la [[Prototype]]cadena. Esta cadena de [[Prototype]]objetos es lo que se examina cuando, por ejemplo, [[Get]]o [[Set]]se realizan operaciones en un objeto:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

La .prototypepropiedad:

Esta es una propiedad que solo se encuentra en las funciones. Usando una función muy simple:

function Bar(){};

La .prototypepropiedad contiene un objeto al que se le asignará b.[[Prototype]]cuando lo haga var b = new Bar. Puedes examinar esto fácilmente:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

Uno de los más importantes .prototypees el de la Objectfunción . Este prototipo contiene el objeto prototípico que [[Prototype]]contienen todas las cadenas. En él, se definen todos los métodos disponibles para nuevos objetos:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

Ahora, como .prototypees un objeto, tiene una [[Prototype]]propiedad. Cuando no realiza ninguna asignación a Function.prototype, el .prototype's [[Prototype]]apunta al objeto prototípico ( Object.prototype). Esto se realiza automáticamente cada vez que crea una nueva función.

De esta manera, cada vez que haga new Bar;la cadena de prototipos está configurada para usted, obtendrá todo definido Bar.prototypey todo definido en Object.prototype:

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

Cuando haces asignaciones a Function.prototypetodo lo que estás haciendo es extender la cadena del prototipo para incluir otro objeto. Es como una inserción en una lista individualmente vinculada.

Básicamente, esto altera la [[Prototype]]cadena permitiendo que las propiedades definidas en el objeto asignado Function.prototypesean vistas por cualquier objeto creado por la función.


[1: Eso no confundirá a nadie; disponible a través de la __proto__propiedad en muchas implementaciones.
[2]: Todos excepto null.

Dimitris Fasarakis Hilliard
fuente
10

Déjame decirte mi comprensión de los prototipos. No voy a comparar la herencia aquí con otros idiomas. Desearía que la gente dejara de comparar idiomas y simplemente entendiera el idioma como tal. Comprender los prototipos y la herencia de prototipos es tan simple, como te mostraré a continuación.

El prototipo es como un modelo, en función del cual crea un producto. El punto crucial a entender es que cuando crea un objeto utilizando otro objeto como prototipo, el vínculo entre el prototipo y el producto es duradero. Por ejemplo:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

Cada objeto contiene una propiedad interna llamada [[prototipo]], a la que puede acceder el Object.getPrototypeOf() función. Object.create(model)crea un nuevo objeto y establece su propiedad [[prototipo]] en el modelo de objeto . Por lo tanto, cuando lo haga Object.getPrototypeOf(product), obtendrá el modelo de objeto .

Las propiedades en el producto se manejan de la siguiente manera:

  • Cuando se accede a una propiedad para leer su valor, se busca en la cadena de alcance. La búsqueda de la variable comienza desde el producto hacia arriba hasta su prototipo. Si se encuentra dicha variable en la búsqueda, la búsqueda se detiene allí mismo y se devuelve el valor. Si dicha variable no se puede encontrar en la cadena de alcance, se devuelve indefinido.
  • Cuando una propiedad se escribe (se modifica), la propiedad siempre se escribe en el producto objeto del . Si el producto aún no tiene dicha propiedad, se crea y escribe implícitamente.

Tal vinculación de objetos usando la propiedad prototipo se llama herencia prototípica. Ahí, es tan simple, ¿de acuerdo?

Aravind
fuente
No siempre está escrito en el producto asignado. No está dejando muy claro que los miembros específicos de la instancia deben inicializarse y que los miembros compartidos pueden ir al prototipo. Especialmente cuando tiene miembros mutables específicos de instancia: stackoverflow.com/questions/16063394/…
HMR
HMR: En su ejemplo en su respuesta, ben.food.push ("Hamburger"); la línea altera la propiedad del objeto prototipo debido a lo siguiente: 1.) Primero se busca ben.food, y cualquier acción de búsqueda simplemente buscará la cadena de alcance. 2.) Se ejecuta la función push de ese objeto ben.food. Al escribir el modo en mi respuesta, me refiero a cuando se establece explícitamente un valor, como en: ben.food = ['Idly']; Esto siempre creará una nueva propiedad (si aún no está allí) en el objeto del producto y luego le asignará el valor.
Aravind
HMR: Gracias por tu comentario, me hizo pensar y poner a prueba mi comprensión.
Aravind
Al volver a asignar ben.food, sombreará al miembro de comida a menos que la comida se cree usando Object.defineProperty, Object.defineProperties u Object.create con un segundo argumento (por lo que no siempre). Incluso puede cambiar el prototipo con (lo que parece) una reasignación cuando creó un setter getter. Cuando se trata de patrones de herencia, entiendo que la función del constructor es difícil de entender y tiene algunos problemas importantes, pero es bueno si la entiendes. La herencia en JavaScript no comienza y termina con la configuración de un prototipo, las inicializaciones (constructores) también se deben (re) utilizar.
HMR
Su respuesta es buena para explicar el prototipo, pero podría interpretarse erróneamente simplificando demasiado la herencia en JavaScript y miembros específicos de la instancia. Se han hecho muchas preguntas por qué la mutación de un miembro prototipo en una instancia afecta a otras instancias.
HMR
10

Considere el siguiente keyValueStoreobjeto:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Puedo crear una nueva instancia de este objeto haciendo esto:

kvs = keyValueStore.create();

Cada instancia de este objeto tendría las siguientes propiedades públicas:

  • data
  • get
  • set
  • delete
  • getLength

Ahora, supongamos que creamos 100 instancias de este keyValueStoreobjeto. A pesar de que get, set, delete,getLength va a hacer exactamente lo mismo para cada uno de estos 100 casos, cada caso tiene su propia copia de esta función.

Ahora, imagínese si usted podría tener una sola get, set, deleteygetLength copia, y cada instancia haría referencia a esa misma función. Esto sería mejor para el rendimiento y requeriría menos memoria.

Ahí es donde entran los prototipos. Un prototipo es un "plano" de propiedades que se hereda pero no se copia por instancias. Esto significa que existe solo una vez en la memoria para todas las instancias de un objeto y es compartido por todas esas instancias.

Ahora, considere el keyValueStoreobjeto nuevamente. Podría reescribirlo así:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Esto hace EXACTAMENTE lo mismo que la versión anterior del keyValueStoreobjeto, excepto que todos sus métodos ahora se colocan en un prototipo. Lo que esto significa es que las 100 instancias ahora comparten estos cuatro métodos en lugar de que cada uno tenga su propia copia.

John Slegers
fuente
9

Resumen:

  • Las funciones son objetos en JavaScript y, por lo tanto, pueden tener propiedades
  • Las funciones (Constructor) siempre tienen una propiedad prototipo
  • Cuando se utiliza una función como constructor con la newpalabra clave, el objeto obtiene el prototipo. Se puede encontrar una referencia a este prototipo en la __proto__propiedad del objeto recién creado.
  • Esta __proto__propiedad se refiere a la prototypepropiedad de la función constructora.

Ejemplo:

function Person (name) {
  this.name = name;
}

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

¿Por qué es útil esto?

Javascript tiene un mecanismo al buscar propiedades en Objetos que se llama 'herencia prototípica' , esto es lo que básicamente hace:

  • Primero se verifica si la propiedad se encuentra en el Objeto mismo. Si es así, se devuelve esta propiedad.
  • Si la propiedad no se encuentra en el objeto en sí, 'trepará por la protocadena'. Básicamente analiza el objeto al que hace referencia la propiedad proto . Allí comprueba si la propiedad está disponible en el objeto mencionado por proto
  • Si la propiedad no se encuentra en el protoobjeto , subirá al proto cadena de todo el camino hasta el objeto Object.
  • Si no puede encontrar la propiedad en ninguna parte del objeto y su cadena de prototipo, volverá indefinido.

Por ejemplo:

function Person(name) {
  this.name = name;
}

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

Actualizar:

La __proto__propiedad ha quedado en desuso, aunque se implementa en la mayoría de los navegadores modernos, una mejor manera de obtener la referencia del objeto prototipo sería:

Object.getPrototypeOf()

Willem van der Veen
fuente
7

Siempre me gustan las analogías cuando se trata de entender este tipo de cosas. La 'herencia prototípica' es bastante confusa en comparación con la herencia de graves de clase en mi opinión, a pesar de que los prototipos son un paradigma mucho más simple. De hecho, con los prototipos, realmente no hay herencia, por lo que el nombre en sí mismo es engañoso, es más un tipo de "delegación".

Imagina esto ....

Estás en la secundaria, estás en clase y tienes un examen que vence hoy, pero no tienes un bolígrafo para completar tus respuestas. Doh!

Estás sentado al lado de tu amigo Finnius, quien podría tener un bolígrafo. Usted pregunta, y él mira alrededor de su escritorio sin éxito, pero en lugar de decir "No tengo un bolígrafo", es un buen amigo que consulta con su otro amigo Derp si tiene un bolígrafo. De hecho, Derp tiene una lapicera de repuesto y se la pasa a Finnius, quien se la pasa a usted para completar su cuestionario. Derp le ha confiado la pluma a Finnius, quien le ha delegado la pluma para que la use.

Lo importante aquí es que Derp no te da la pluma, ya que no tienes una relación directa con él.

Este es un ejemplo simplificado de cómo funcionan los prototipos, donde se busca en un árbol de datos lo que está buscando.

Louis Moore
fuente
3

otro esquema que muestra las relaciones __proto__ , prototipo y constructor : ingrese la descripción de la imagen aquí

IvanM
fuente
1

Es solo que ya tienes un objeto Object.newpero aún no tienes un objeto cuando usas la sintaxis del constructor.

shiva kumar
fuente
1

Es importante comprender que existe una distinción entre el prototipo de un objeto (que está disponible a través de Object.getPrototypeOf(obj), o a través de la __proto__propiedad en desuso ) y la prototypepropiedad en las funciones de constructor. El primero es la propiedad en cada instancia, y el segundo es la propiedad en el constructor. Es decir, se Object.getPrototypeOf(new Foobar())refiere al mismo objeto que Foobar.prototype.

Referencia: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

Baraa Al-Tabbaa
fuente
0

El prototipo crea un nuevo objeto clonando un objeto existente . Entonces, cuando pensamos en el prototipo, realmente podemos pensar en clonar o hacer una copia de algo en lugar de inventarlo.

Arif
fuente