javascript - orientado - ¿Por qué es necesario configurar el constructor prototipo?
javascript orientado a objetos pdf (12)
¿Esto sirve algún propósito importante?
Si y no.
En ES5 y versiones anteriores, el propio JavaScript no usaba el constructor
para nada. Definió que el objeto predeterminado en la propiedad prototype
una función lo tendría y que se referiría a la función, y eso fue todo . Nada más en la especificación se refiere a ella en absoluto.
Eso cambió en ES2015 (ES6), que comenzó a usarlo en relación con las jerarquías de herencia. Por ejemplo, Promise#then
usa la propiedad de constructor
de la promesa a la que la llamas (a través de SpeciesConstructor ) al crear la nueva promesa de devolución. También participa en arreglos de ArraySpeciesCreate (a través de ArraySpeciesCreate ).
Fuera del lenguaje en sí, a veces las personas lo usan cuando intentan crear funciones genéricas de "clonación" o, en general, cuando quieren referirse a lo que creen que sería la función constructora del objeto. Mi experiencia es que usarlo es raro, pero a veces la gente lo usa.
¿Está bien omitirlo?
Está allí de forma predeterminada, solo debe volver a colocarlo cuando reemplace el objeto en la propiedad prototype
una función:
Student.prototype = Object.create(Person.prototype);
Si no haces esto:
Student.prototype.constructor = Student;
... entonces Student.prototype.constructor
hereda de Person.prototype
que (probablemente) tiene constructor = Person
. Así que es engañoso. Y, por supuesto, si estás subclasificando algo que lo usa (como Promise
o Array
) y no usas la class
¹ (que se encarga de esto por ti), querrás asegurarte de que lo configuraste correctamente. Así que básicamente: es una buena idea.
Está bien si nada en tu código (o el código de biblioteca que usas) lo usa. Siempre me aseguré de que estaba correctamente conectado.
Por supuesto, con la palabra clave de class
ES2015 (también conocida como ES6), la mayoría de las veces lo habríamos usado, no tenemos que hacerlo más, porque lo manejamos cuando lo hacemos.
class Student extends Person {
}
¹ "... si estás subclasificando algo que lo usa (como Promise
o Array
) y no usas class
..." - Es posible hacerlo, pero es un verdadero dolor (y un poco tonto). Tienes que usar Reflect.construct
.
En la sección sobre herencia en el artículo de MDN Introducción a Javascript orientado a objetos , noté que configuraron el prototipo.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
¿Esto sirve algún propósito importante? ¿Está bien omitirlo?
EDITAR, estaba realmente equivocado. Comentar la línea de salida no cambia su comportamiento en absoluto. (Lo probé)
Sí, es necesario. Cuando tu lo hagas
Student.prototype = new Person();
Student.prototype.constructor
convierte en Person
. Por lo tanto, llamar a Student()
devolvería un objeto creado por Person
. Si luego haces
Student.prototype.constructor = Student;
Student.prototype.constructor
se restablece a Student
. Ahora, cuando llama a Student()
, ejecuta Student
, que llama al constructor principal Parent()
, devuelve el objeto heredado correctamente. Si no restableció Student.prototype.constructor
antes de llamarlo, obtendría un objeto que no tendría ninguna de las propiedades establecidas en Student()
.
Dada la función constructora simple:
function Person(){
this.name = ''test'';
}
console.log(Person.prototype.constructor) // function Person(){...}
Person.prototype = { //constructor in this case is Object
sayName: function(){
return this.name;
}
}
var person = new Person();
console.log(person instanceof Person); //true
console.log(person.sayName()); //test
console.log(Person.prototype.constructor) // function Object(){...}
De manera predeterminada (de la especificación https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor ), todos los prototipos obtienen automáticamente una propiedad llamada constructor que apunta a la función Que es una propiedad. Dependiendo del constructor, se pueden agregar otras propiedades y métodos al prototipo, lo cual no es una práctica común, pero aún se permite para extensiones.
Así que simplemente respondiendo: necesitamos asegurarnos de que el valor en prototype.constructor esté configurado correctamente como se supone que debe ser por la especificación.
¿Tenemos que establecer siempre este valor correctamente? Ayuda con la depuración y hace que la estructura interna sea consistente con las especificaciones. Definitivamente deberíamos cuando nuestra API está siendo utilizada por terceros, pero no realmente cuando el código finalmente se ejecuta en el tiempo de ejecución.
Es necesario cuando se necesita una alternativa a toString
sin la aplicación de pareos:
//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.push("a");
foo.toUpperCase();
//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.push("a");
toUpperCase(foo);
//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.push("a");
foo.toUpperCase();
//toString alternative via Prototype constructor
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.push("a,b");
foo.constructor();
//toString override
var foo = [];
foo.push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();
//Object prototype as a function
Math.prototype = function(char){return Math.prototype[char]};
Math.prototype.constructor = function()
{
var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
while (i < max)
{
Math.prototype[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);
i = i + 1;
}
}
Math.prototype.constructor();
console.log(Math.prototype("a") );
console.log(Math.prototype["a"] );
console.log(Math.prototype("a") === Math.prototype["a"]);
Esto tiene el gran escollo que si escribieras
Student.prototype.constructor = Student;
pero entonces si había un Maestro cuyo prototipo era también Persona y escribiste
Teacher.prototype.constructor = Teacher;
entonces el constructor del estudiante es ahora profesor!
Editar: puede evitar esto asegurándose de haber configurado los prototipos de Alumno y Profesor utilizando nuevas instancias de la clase Persona creada con Object.create, como en el ejemplo de Mozilla.
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
Hasta ahora la confusión sigue ahí.
Siguiendo el ejemplo original, ya que tiene un objeto existente student1
como:
var student1 = new Student("Janet", "Applied Physics");
Supongamos que no quieres saber cómo se crea student1
, solo quieres otro objeto como este, puedes usar la propiedad de constructor de student1
como:
var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
Aquí no podrá obtener las propiedades de Student
si la propiedad del constructor no está establecida. Más bien creará un objeto Person
.
No es necesario. Es solo una de las muchas cosas tradicionales que hacen los campeones de OOP para tratar de convertir la herencia prototípica de JavaScript en herencia clásica. Lo único que lo siguiente.
Student.prototype.constructor = Student;
Sí, es que ahora tiene una referencia del "constructor" actual.
En la respuesta de Wayne, que se ha marcado como correcta, podría exactamente lo mismo que hace el siguiente código
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new this.constructor(this.name);
};
con el código a continuación (simplemente reemplace this.constructor con Person)
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new Person(this.name);
};
Gracias a Dios, con ES6 la herencia clásica puede utilizar los operadores nativos del lenguaje como class, extended y super y no tenemos que ver como correcciones prototipo.constructor y referencias de los padres.
No estoy de acuerdo No es necesario configurar el prototipo. Tome exactamente el mismo código pero elimine la línea prototype.constructor. ¿Cambia algo? No. Ahora, haz los siguientes cambios:
Person = function () {
this.favoriteColor = ''black'';
}
Student = function () {
Person.call(this);
this.favoriteColor = ''blue'';
}
y al final del código de prueba ...
alert(student1.favoriteColor);
El color será azul.
Un cambio en el prototype.constructor, en mi experiencia, no hace mucho a menos que esté haciendo cosas muy específicas y muy complicadas que probablemente no sean una buena práctica de todos modos :)
Editar: Después de buscar en la web un poco y hacer algo de experimentación, parece que las personas configuran el constructor de modo que se ''parezca'' a lo que se está construyendo con ''nuevo''. Supongo que diría que el problema con esto es que javascript es un lenguaje prototipo, no existe tal cosa como herencia. Pero la mayoría de los programadores provienen de un fondo de programación que empuja la herencia como "el camino". Así que ideamos todo tipo de cosas para probar y hacer de este lenguaje prototípico un lenguaje "clásico" ... como extender "clases". Realmente, en el ejemplo que dieron, un nuevo estudiante es una persona, no se está "extendiendo" de otro estudiante ... el estudiante es todo acerca de la persona, y cualquiera que sea la persona, el estudiante también lo es. Extienda al estudiante, y lo que haya extendido es un estudiante de corazón, pero está personalizado para adaptarse a sus necesidades.
Crockford es un poco loco y demasiado entusiasta, pero lee detenidamente algunas de las cosas que ha escrito ... te hará ver estas cosas de manera muy diferente.
No hay necesidad de usar "clases" de funciones azucaradas o usar "Nuevo" en estos días. Utilizar objetos literales.
El prototipo Objeto ya es una ''clase''. Cuando define un objeto literal, ya es una instancia del objeto prototipo. Estos también pueden actuar como prototipo de otro objeto, etc.
const Person = {
name: ''[Person.name]'',
greeting: function() {
console.log( `My name is ${ this.name || ''[Name not assigned]'' }` );
}
};
// Person.greeting = function() {...} // or define outside the obj if you must
// Object.create version
const john = Object.create( Person );
john.name = ''John'';
console.log( john.name ); // John
john.greeting(); // My name is John
// Define new greeting method
john.greeting = function() {
console.log( `Hi, my name is ${ this.name }` )
};
john.greeting(); // Hi, my name is John
// Object.assign version
const jane = Object.assign( Person, { name: ''Jane'' } );
console.log( jane.name ); // Jane
// Original greeting
jane.greeting(); // My name is Jane
// Original Person obj is unaffected
console.log( Person.name ); // [Person.name]
console.log( Person.greeting() ); // My name is [Person.name]
Los lenguajes orientados a objetos basados en clases, como Java y C ++, se basan en el concepto de dos entidades distintas: clases e instancias.
...
Un lenguaje basado en prototipos, como JavaScript, no hace esta distinción: simplemente tiene objetos. Un lenguaje basado en prototipos tiene la noción de un objeto prototípico, un objeto que se utiliza como una plantilla desde la cual se obtienen las propiedades iniciales para un nuevo objeto. Cualquier objeto puede especificar sus propias propiedades, ya sea cuando lo cree o en tiempo de ejecución. Además, cualquier objeto puede asociarse como prototipo de otro objeto, lo que permite que el segundo objeto comparta las propiedades del primer objeto.
No siempre es necesario, pero tiene sus usos. Supongamos que quisiéramos hacer un método de copia en la clase base Person
. Me gusta esto:
// define the Person Class
function Person(name) {
this.name = name;
}
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new this.constructor(this.name);
};
// define the Student class
function Student(name) {
Person.call(this, name);
}
// inherit Person
Student.prototype = Object.create(Person.prototype);
Ahora, ¿qué sucede cuando creamos un nuevo Student
y lo copiamos?
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => false
La copia no es una instancia de Student
. Esto se debe a que (sin controles explícitos), no tendríamos manera de devolver una copia de Student
de la clase "base". Solo podemos devolver una Person
. Sin embargo, si hubiéramos restablecido el constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
... entonces todo funciona como se espera:
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => true
Obtuve un buen ejemplo de código de por qué es realmente necesario configurar el constructor de prototipo.
function CarFactory(name){
this.name=name;
}
CarFactory.prototype.CreateNewCar = function(){
return new this.constructor("New Car "+ this.name);
}
CarFactory.prototype.toString=function(){
return ''Car Factory '' + this.name;
}
AudiFactory.prototype = new CarFactory(); // Here''s where the inheritance occurs
AudiFactory.prototype.constructor=AudiFactory; // Otherwise instances of Audi would have a constructor of Car
function AudiFactory(name){
this.name=name;
}
AudiFactory.prototype.toString=function(){
return ''Audi Factory '' + this.name;
}
var myAudiFactory = new AudiFactory('''');
alert(''Hay your new '' + myAudiFactory + '' is ready.. Start Producing new audi cars !!! '');
var newCar = myAudiFactory.CreateNewCar(); // calls a method inherited from CarFactory
alert(newCar);
/*
Without resetting prototype constructor back to instance, new cars will not come from New Audi factory, Instead it will come from car factory ( base class ).. Dont we want our new car from Audi factory ????
*/
TLDR; No es muy necesario, pero probablemente ayudará a largo plazo, y es más preciso hacerlo.
NOTA: editado mucho, ya que mi respuesta anterior estaba escrita de manera confusa y tuvo algunos errores que no pude responder rápidamente. Gracias a quienes señalaron algunos errores atroces.
Básicamente, se trata de cablear correctamente las subclases en Javascript. Cuando hacemos subclases, tenemos que hacer algunas cosas extravagantes para asegurarnos de que la delegación del prototipo funcione correctamente, incluida la sobrescritura de un objeto prototype
. La sobrescritura de un objeto prototype
incluye el constructor
, por lo que luego necesitamos corregir la referencia.
Veamos rápidamente cómo funcionan las ''clases'' en ES5.
Digamos que tienes una función constructora y su prototipo:
//Constructor Function
var Person = function(name, age) {
this.name = name;
this.age = age;
}
//Prototype Object - shared between all instances of Person
Person.prototype = {
species: ''human'',
}
Cuando llame al constructor para crear una instancia, diga Adam
:
// instantiate using the ''new'' keyword
var adam = new Person(''Adam'', 19);
La new
palabra clave invocada con ''Person'' básicamente ejecutará el constructor de Person con algunas líneas adicionales de código:
function Person (name, age) {
// This additional line is automatically added by the keyword ''new''
// it sets up the relationship between the instance and the prototype object
// So that the instance will delegate to the Prototype object
this = Object.create(Person.prototype);
this.name = name;
this.age = age;
return this;
}
/* So ''adam'' will be an object that looks like this:
* {
* name: ''Adam'',
* age: 19
* }
*/
Si console.log(adam.species)
, la búsqueda fallará en la instancia de adam
, y buscará la cadena prototípica a su .prototype
, que es Person.prototype
- y Person.prototype
tiene una propiedad .species
, por lo que la búsqueda tendrá éxito en Person.prototype
. Entonces se registrará ''human''
.
Aquí, Person.prototype.constructor
apuntará correctamente a Person
.
Así que ahora la parte interesante, la llamada ''subclasificación''. Si queremos crear una clase de Student
, es decir, una subclase de la clase de Person
con algunos cambios adicionales, debemos asegurarnos de que Student.prototype.constructor
apunta a Student para la precisión.
No hace esto por sí mismo. Cuando haces una subclase, el código se ve así:
var Student = function(name, age, school) {
// Calls the ''super'' class, as every student is an instance of a Person
Person.call(this, name, age);
// This is what makes the Student instances different
this.school = school
}
var eve = new Student(''Eve'', 20, ''UCSF'');
console.log(Student.prototype); // this will be an empty object: {}
Llamar a new Student()
aquí devolverá un objeto con todas las propiedades que deseamos. Aquí, si verificamos eve instanceof Person
, devolvería false
. Si intentamos acceder a eve.species
, devolverá undefined
.
En otras palabras, debemos conectar la delegación para que eve instanceof Person
devuelva true y que las instancias de Student
deleguen correctamente en Student.prototype
, y luego Person.prototype
.
PERO, ya que lo estamos llamando con la new
palabra clave, ¿recuerda lo que agrega esa invocación? Se llamaría Object.create(Student.prototype)
, que es como configuramos esa relación delegacional entre Student
y Student.prototype
. Tenga en cuenta que en este momento, Student.prototype
está vacío. Por lo tanto, al buscar .species
una instancia de Student
fallaría, ya que delega solo Student.prototype
, y la propiedad .species
no existe en Student.prototype
.
Cuando asignamos Student.prototype
a Object.create(Person.prototype)
, Student.prototype
sí mismo se delega a Person.prototype
, y la búsqueda de eve.species
se volverá human
como esperamos. Es de suponer que nos gustaría que se heredara de Student.prototype AND Person.prototype. Así que tenemos que arreglar todo eso.
/* This sets up the prototypal delegation correctly
*so that if a lookup fails on Student.prototype, it would delegate to Person''s .prototype
*This also allows us to add more things to Student.prototype
*that Person.prototype may not have
*So now a failed lookup on an instance of Student
*will first look at Student.prototype,
*and failing that, go to Person.prototype (and failing /that/, where do we think it''ll go?)
*/
Student.prototype = Object.create(Person.prototype);
Ahora la delegación funciona, pero estamos sobrescribiendo Student.prototype
con un of Person.prototype
. Entonces, si llamamos a Student.prototype.constructor
, apuntaría a Person
lugar de Student
. Es por eso que tenemos que arreglarlo.
// Now we fix what the .constructor property is pointing to
Student.prototype.constructor = Student
// If we check instanceof here
console.log(eve instanceof Person) // true
En ES5, nuestra propiedad de constructor
es una referencia que se refiere a una función que hemos escrito con la intención de ser un ''constructor''. Aparte de lo que nos da la new
palabra clave, el constructor es, de lo contrario, una función "simple".
En ES6, el constructor
ahora está integrado en la forma en que escribimos las clases; como en, se proporciona como un método cuando declaramos una clase. Esto es simplemente azúcar sintáctico, pero nos otorga algunas comodidades como el acceso a un super
cuando extendemos una clase existente. Así que escribiríamos el código anterior de esta manera:
class Person {
// constructor function here
constructor(name, age) {
this.name = name;
this.age = age;
}
// static getter instead of a static property
static get species() {
return ''human'';
}
}
class Student extends Person {
constructor(name, age, school) {
// calling the superclass constructor
super(name, age);
this.school = school;
}
}