Introducción lírica
Habiendo recibido una vez más un montón de preguntas sobre prototipos en la siguiente entrevista, me di cuenta de que había olvidado un poco las complejidades de la creación de prototipos y decidí actualizar mis conocimientos. Me encontré con un montón de artículos que fueron escritos sobre la inspiración del autor, cómo "siente" los prototipos, o el artículo trataba sobre una parte separada del tema y no daba una imagen completa de lo que estaba sucediendo.
Resulta que hay muchas cosas no obvias de los viejos tiempos de ES5 e incluso de ES6 de las que no había oído hablar. También resultó que la salida de la consola del navegador puede no corresponder a la realidad.
Que es un prototipo
El objeto en JS tiene propiedades propias y heredadas, por ejemplo, en este código:
var foo = { bar: 1 };
foo.bar === 1 // true
typeof foo.toString === "function" // true
el objeto foo
tiene su propia propiedad bar
con un valor 1
, pero también tiene otras propiedades como toString
. Para entender cómo un objeto foo
obtiene una nueva propiedad toString
, echemos un vistazo a en qué consiste el objeto:
La cuestión es que un objeto tiene una referencia a otro objeto prototipo. Al acceder a un campo foo.toString
, primero se realiza una búsqueda de dicha propiedad desde el propio objeto, y luego desde su prototipo, el prototipo de su prototipo, y así sucesivamente hasta que finaliza la cadena de prototipos. Es como una lista de objetos enlazados individualmente, donde el objeto y sus objetos prototipo se comprueban sucesivamente. Así es como se implementa la herencia de propiedades, por ejemplo, (casi, pero más sobre eso más adelante) cualquier objeto tiene métodos valueOf
y toString
.
, constructor
__proto__
. constructor
-, , __proto__
( null, ). .
, .
constructor
constructor
– , :
const a = {};
a.constructor === Object // true
, , :
object.constructor(object.arg)
, , , . constructor
, writable , , , .
, , JS . , , [[SlotName]]
. [[Prototype]]
- ( null
, ).
- , [[Prototype]]
JS , . , __proto__
, , JS .
,
__proto__
[[Prototype]]
Object.prototype
:
- __proto__
. __proto__
, . __proto__
:
const foo = {};
foo.toString(); // toString() Object.prototype '[object Object]',
foo.__proto__ = null; // null
foo.toString(); // TypeError: foo.toString is not a function
foo.__proto__ = Object.prototype; //
foo.toString(); // , TypeError: foo.toString is not a function
? , __proto__
– Object.prototype
, foo
. - Object.prototype
, __proto__
.
. :
var baz = { test: "test" };
var foo = { bar: 1 };
foo.__proto__ = baz;
Chrome foo :
baz
Object.prototype
:
baz.__proto__ = null;
Chrome :
Object.prototype
baz
__proto__
undefined
foo
, Chrome __proto__
. [[Prototype]]
, __proto__
, , .
: .
: __proto__
Object.setPrototypeOf
.
var myProto = { name: "Jake" };
var foo = {};
Object.setPrototypeOf(foo, myProto);
foo.__proto__ = myProto;
, , .
[[Extensible]]
, . , false : Object.freeze
, Object.seal
, Object.preventExtensions
. :
const obj = {};
Object.preventExtensions(obj);
Object.setPrototypeOf(obj, Function.prototype); // TypeError: #<Object> is not extensible
. .
:
const foo = Object.create(myPrototype);
Object.create
, __proto__
:
const foo = { __proto__: myPrototype };
:
const f = function () {}
f.prototype = myPrototype;
const foo = new f();
new
, . , new
prototype
, .. [[Prototype]]
, .
.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const user = new Person('John', 'Doe');
Person , :
Person.prototype
? , prototype
(note 3), prototype
, . , :
Person.prototype.fullName = function () {
return this.firstName + ' ' + this.lastName;
}
user.fullName()
"John Doe".
new
new . new :
- self
- prototype self
- self this
- self ,
, new :
function custom_new(constructor, args) {
// https://stackoverflow.com/questions/31538010/test-if-a-variable-is-a-primitive-rather-than-an-object
function isPrimitive(val) {
return val !== Object(val);
}
const self = Object.create({});
const constructorValue = constructor.apply(self, args) || self;
return isPrimitive(constructorValue) ? self : constructorValue;
}
custom_new(Person, ['John', 'Doe'])
ES6 new new.target, , new, :
function Foo() {
console.log(new.target === Foo);
}
Foo(); // false
new Foo(); // true
new.target
undefined , new
;
, Student
Person
.
- Student Person
- `Student.prototype` `Person`
- `Student.prototype`
function Student(firstName, lastName, grade) {
Person.call(this, firstName, lastName);
this.grade = grade;
}
// 1
Student.prototype = Object.create(Person.prototype, {
constructor: {
value:Student,
enumerable: false,
writable: true
}
});
// 2
Object.setPrototypeOf(Student.prototype, Person.prototype);
Student.prototype.isGraduated = function() {
return this.grade === 0;
}
const student = new Student('Judy', 'Doe', 7);
( , .. this ), ( )
1 , .. Object.setPrototypeOf
.
, , Person Student:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
class Student extends Person {
constructor(firstName, lastName, grade) {
super(firstName, lastName);
this.grade = grade;
}
isGraduated() {
return this.grade === 0;
}
}
, :
- , new
-
prototype .
P.S.
Sería ingenuo esperar que un artículo responda a todas las preguntas. Si tiene preguntas interesantes, excursiones en la historia, declaraciones razonadas o infundadas de que hice todo mal, o correcciones por errores, escriba en los comentarios.