Prototipos JS y hechos poco conocidos

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 footiene su propia propiedad barcon un valor 1, pero también tiene otras propiedades como toString. Para entender cómo un objeto fooobtiene 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 valueOfy 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 :



  1. self

  2. prototype self

  3. self this

  4. 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.



  1. Student Person

  2. `Student.prototype` `Person`

  3. `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. 




All Articles