- Объяснять, как поиск свойства идёт по цепочке прототипов до
Object.prototypeиnull - Создавать объекты через
Object.createи функции-конструкторы, вызванные сnew - Показывать, что
classпостроен на прототипах, и отличать собственные свойства от унаследованных
У каждого массива есть метод map, у каждой строки — toUpperCase, хотя ты никогда не записывал эти методы в свои массивы. Откуда они берутся? В JavaScript у каждого объекта есть скрытая ссылка на другой объект — его прототип. Если свойства нет в самом объекте, JavaScript ищет его в прототипе. Встроенные методы, наследование и классы построены именно на этом простом механизме.
Цепочка прототипов
Прототип объекта читают через Object.getPrototypeOf(obj) (старая запись __proto__ тоже работает, но не рекомендуется). При чтении obj.x JavaScript сначала проверяет собственные свойства объекта, затем прототип, затем прототип прототипа и так далее — пока не дойдёт до null. Если свойства нигде нет, результат — undefined. Запись работает иначе: obj.x = ... всегда создаёт или меняет собственное свойство и не трогает прототип.
const animal = {
eats: true,
describe() {
return `${this.name} eats: ${this.eats}`;
},
};
const cat = Object.create(animal);
cat.name = 'Tom';
console.log(cat.describe());
console.log(Object.getPrototypeOf(cat) === animal);
console.log(Object.hasOwn(cat, 'name'), Object.hasOwn(cat, 'eats'));
cat.eats = false;
console.log(cat.eats, animal.eats);
console.log(Object.getPrototypeOf(animal) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));▸ Ожидаемый результат
Tom eats: true true true false false true true null
cat.eats = false создало собственное свойство, которое «затеняет» унаследованное, — animal не изменился. Цепочка: cat → animal → Object.prototype → null.Встроенные объекты устроены так же. Цепочка массива: массив → Array.prototype → Object.prototype → null. map находится не в самом массиве, а в Array.prototype. Оператор in проверяет всю цепочку, а Object.hasOwn — только собственные свойства. У Array.prototype есть свой toString, который затеняет Object.prototype.toString.
const numbers = [1, 2, 3];
console.log(Object.getPrototypeOf(numbers) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
console.log(Object.hasOwn(numbers, 'map'), 'map' in numbers);
console.log(numbers.toString === Object.prototype.toString);
console.log(numbers.toString());▸ Ожидаемый результат
true true false true false 1,2,3
Функции-конструкторы и new
До появления class объекты одинаковой структуры создавали с помощью функций-конструкторов. Когда обычную функцию вызывают с new, JavaScript делает четыре шага:
- создаёт пустой объект;
- делает его прототипом
Fn.prototype; - выполняет функцию, где
this— новый объект; - возвращает новый объект, если функция не вернула другой объект.
Методы записывают в Fn.prototype: тогда все экземпляры делят одну копию метода, а не носят каждый свою. В программе с тысячей объектов это заметная экономия памяти.
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
Student.prototype.introduce = function () {
return `${this.name}, grade ${this.grade}`;
};
const leyla = new Student('Leyla', 9);
const elvin = new Student('Elvin', 10);
console.log(leyla.introduce(), '|', elvin.introduce());
console.log(leyla.introduce === elvin.introduce);
console.log(Object.getPrototypeOf(leyla) === Student.prototype);
console.log(leyla);▸ Ожидаемый результат
Leyla, grade 9 | Elvin, grade 10
true
true
Student { name: 'Leyla', grade: 9 }Классы — синтаксический сахар
class (ES2015) позволяет записать ту же структуру понятнее: класс на самом деле — функция, его методы попадают в ClassName.prototype, а extends связывает прототипы между собой. Есть и отличия: код класса всегда в строгом режиме, класс нельзя вызвать без new, методы не видны в for...in, а #приватные поля существуют только в классах.
class Shape {
constructor(name) { this.name = name; }
describe() { return `I am a ${this.name}`; }
}
class Circle extends Shape {
constructor(radius) {
super('circle');
this.radius = radius;
}
area() { return +(Math.PI * this.radius ** 2).toFixed(2); }
}
const c = new Circle(2);
console.log(typeof Shape, c.describe(), c.area());
console.log(Object.getPrototypeOf(Circle.prototype) === Shape.prototype);
console.log(Object.getOwnPropertyNames(Circle.prototype));
console.log(Object.hasOwn(c, 'area'), 'area' in c);
try {
Shape('square');
} catch (error) {
console.log(error.name);
}▸ Ожидаемый результат
function I am a circle 12.57 true [ 'constructor', 'area' ] false true TypeError
| Запись с классом | Что происходит «под капотом» |
|---|---|
class A { m() {} } | function A() {} + A.prototype.m = function () {} |
class B extends A {} | Object.setPrototypeOf(B.prototype, A.prototype) |
super.m() | A.prototype.m.call(this) |
static s() {} | A.s = function () {} |
extends ещё и связывает B с A, чтобы наследовались статические методы.С помощью Object.create(vehicle) создай два объекта: car (brand: 'Toyota') и bike (brand: 'BMX' и собственное свойство wheels: 2). Метод describe() у обоих должен браться из прототипа. Строки ниже выведут результат.
const vehicle = {
wheels: 4,
describe() {
return `${this.brand} has ${this.wheels} wheels`;
},
};
// create car and bike here
console.log(car.describe());
console.log(bike.describe());
console.log(Object.hasOwn(car, 'wheels'), Object.hasOwn(bike, 'wheels'));▸ Ожидаемый результат
Toyota has 4 wheels BMX has 2 wheels false true
Добавь в Counter.prototype метод increment(): он увеличивает value на 1 и возвращает this, чтобы вызовы можно было соединять в цепочку. Метод должен быть общим для всех экземпляров, а не собственным свойством каждого объекта.
function Counter(start) {
this.value = start;
}
// add increment() to Counter.prototype
const a = new Counter(5);
const b = new Counter(0);
a.increment().increment();
console.log(a.value, b.value);
console.log(a.increment === b.increment);
console.log(Object.hasOwn(a, 'increment'));▸ Ожидаемый результат
7 0 true false
Главное
- У каждого объекта есть скрытая ссылка на прототип; отсутствующее свойство ищется по цепочке до
Object.prototype, а затемnull. - Запись свойства всегда создаёт собственное свойство, которое затеняет унаследованное; прототип не меняется.
Object.create(proto)создаёт объект с заданным прототипом;new Fn()связывает новый объект сFn.prototype.- Методы в прототипе общие для всех экземпляров;
Object.hasOwnпроверяет собственные свойства,in— всю цепочку. classпостроен на прототипах и добавляет удобства: строгий режим, обязательныйnew, приватные поля.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
const a = { x: 1 }; const b = Object.create(a); b.x = 2; чему равно a.x?