Protótipos JS e fatos pouco conhecidos

Introdução lírica 



Tendo recebido mais uma vez um monte de perguntas sobre protótipos na próxima entrevista, percebi que havia esquecido um pouco as complexidades da prototipagem e decidi atualizar meus conhecimentos. Eu me deparei com um monte de artigos que foram escritos ou com base na inspiração do autor, como ele "sente" os protótipos, ou o artigo era sobre uma parte separada do tópico e não dava uma imagem completa do que estava acontecendo. 



Acontece que há muitas coisas não óbvias dos velhos tempos do ES5 e até do ES6 das quais eu não tinha ouvido falar. Também descobriu que a saída do console do navegador pode não corresponder à realidade.



O que é um protótipo



O objeto em JS tem propriedades próprias e herdadas, por exemplo, neste código:



var foo = { bar: 1 };
foo.bar === 1 // true
typeof foo.toString === "function" // true


o objeto footem sua própria propriedade barcom um valor 1, mas também possui outras propriedades, como toString. Para entender como um objeto fooobtém uma nova propriedade toString, vamos dar uma olhada no que o objeto consiste:





A questão é que um objeto tem uma referência a outro objeto protótipo. Ao acessar um campo foo.toString, uma busca por tal propriedade é realizada primeiro a partir do próprio objeto, e então de seu protótipo, o protótipo de seu protótipo, e assim por diante até que a cadeia de protótipos termine. É como uma lista de objetos unidos individualmente, onde o objeto e seus objetos de protótipo são verificados por sua vez. É assim que a herança de propriedades é implementada, por exemplo, (quase, mas mais sobre isso depois) qualquer objeto tem métodos valueOfe 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.



Seria ingênuo esperar que um artigo respondesse a todas as perguntas. Se você tiver perguntas interessantes, excursões na história, declarações fundamentadas ou infundadas de que fiz tudo errado, ou correções de erros, escreva nos comentários. 




All Articles