JS prototypes and little-known facts

Lyrical introduction 



Having once again received a bunch of questions about prototypes at the next interview, I realized that I had slightly forgotten the intricacies of prototyping, and decided to refresh my knowledge. I came across a bunch of articles that were written either on the inspiration of the author, how he "feels" the prototypes, or the article was about a separate part of the topic and did not give a complete picture of what was happening. 



It turns out that there are a lot of non-obvious things from the old days of ES5 and even ES6 that I hadn't heard of. It also turned out that the output of the browser console may not correspond to reality.



What is a prototype



Object in JS has its own and inherited properties, for example, in this code:



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


the object foohas its own property barwith a value 1, but it also has other properties such as toString. To understand how an object foogets a new property toString, let's take a look at what the object consists of:





The point is that an object has a reference to another prototype object. When accessing a field foo.toString, a search for such a property is first performed from the object itself, and then from its prototype, the prototype of its prototype, and so on until the prototype chain ends. It is like a singly linked list of objects, where the object and its prototype objects are checked in turn. This is how the inheritance of properties is implemented, for example, (almost, but more on that later) any object has methods valueOfand 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.



It would be naive to expect one article to answer all the questions. If you have interesting questions, excursions into history, reasoned or groundless statements that I did everything wrong, or corrections on errors, write in the comments. 




All Articles