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 foo
has its own property bar
with a value 1
, but it also has other properties such as toString
. To understand how an object foo
gets 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 valueOf
and 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.
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.