work
|
@ -667,7 +667,11 @@ If you want to learn more about normalization rules and variants -- they are des
|
||||||
- To look for a substring: use `indexOf`, or `includes/startsWith/endsWith` for simple checks.
|
- To look for a substring: use `indexOf`, or `includes/startsWith/endsWith` for simple checks.
|
||||||
- To compare strings according to the language, use `localeCompare`, otherwise they are compared by character codes.
|
- To compare strings according to the language, use `localeCompare`, otherwise they are compared by character codes.
|
||||||
|
|
||||||
There are several other helpful methods in strings, like `str.trim()` that removes ("trims") spaces from the beginning and end of the string, see the [manual](mdn:js/String) for them.
|
There are several other helpful methods in strings:
|
||||||
|
|
||||||
|
- [str.trim()]` -- removes ("trims") spaces from the beginning and end of the string.
|
||||||
|
- [str.repeat(n)]` -- repeats the string `n` times.
|
||||||
|
- ...and others, see the [manual](mdn:js/String) for details.
|
||||||
|
|
||||||
Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.
|
Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.
|
||||||
|
|
||||||
|
|
|
@ -362,7 +362,7 @@ There are two changes:
|
||||||
- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function.
|
- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function.
|
||||||
|
|
||||||
|
|
||||||
## Borrowing a method
|
## Borrowing a method [#method-borrowing]
|
||||||
|
|
||||||
Now let's make one more minor improvement in the hashing function:
|
Now let's make one more minor improvement in the hashing function:
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,22 @@ Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` protot
|
||||||
|
|
||||||
So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited".
|
So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited".
|
||||||
|
|
||||||
Here's an example with an inherited method:
|
Loops `for..in` also include inherited properties:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
jumps: true,
|
||||||
|
__proto__: animal
|
||||||
|
};
|
||||||
|
|
||||||
|
for(let prop in rabbit) alert(prop); // jumps, eats
|
||||||
|
```
|
||||||
|
|
||||||
|
If we have a method in `animal`, it can be called on `rabbit`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let animal = {
|
let animal = {
|
||||||
|
@ -138,7 +153,6 @@ Since now, `rabbit.walk()` call finds the method immediately in the object, it d
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The only exception from that rule are property setters. If there's a setter in the prototype, it is called instead of blind writing a value into the object.
|
The only exception from that rule are property setters. If there's a setter in the prototype, it is called instead of blind writing a value into the object.
|
||||||
|
|
||||||
For instance, here `admin.fullName` is an accessor property, with the setter in the prototype:
|
For instance, here `admin.fullName` is an accessor property, with the setter in the prototype:
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
Результат: `true`, из прототипа
|
|
||||||
|
|
||||||
Результат: `true`. Свойство `prototype` всего лишь задаёт `__proto__` у новых объектов. Так что его изменение не повлияет на `rabbit.__proto__`. Свойство `eats` будет получено из прототипа.
|
|
||||||
|
|
||||||
Результат: `false`. Свойство `Rabbit.prototype` и `rabbit.__proto__` указывают на один и тот же объект. В данном случае изменения вносятся в сам объект.
|
|
||||||
|
|
||||||
Результат: `true`, так как `delete rabbit.eats` попытается удалить `eats` из `rabbit`, где его и так нет. А чтение в `alert` произойдёт из прототипа.
|
|
||||||
|
|
||||||
Результат: `undefined`. Удаление осуществляется из самого прототипа, поэтому свойство `rabbit.eats` больше взять неоткуда.
|
|
|
@ -1,91 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Прототип после создания
|
|
||||||
|
|
||||||
В примерах ниже создаётся объект `new Rabbit`, а затем проводятся различные действия с `prototype`.
|
|
||||||
|
|
||||||
Каковы будут результаты выполнения? Почему?
|
|
||||||
|
|
||||||
Начнём с этого кода. Что он выведет?
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
Добавили строку (выделена), что будет теперь?
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype = {};
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
А если код будет такой? (заменена одна строка):
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype.eats = false;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
А такой? (заменена одна строка)
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
delete rabbit.eats; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
И последний вариант:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
delete Rabbit.prototype.eats; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
Можно прототипно унаследовать от `options` и добавлять/менять опции в наследнике:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Menu(options) {
|
|
||||||
options = Object.create(options);
|
|
||||||
options.width = options.width || 300;
|
|
||||||
|
|
||||||
alert( options.width ); // возьмёт width из наследника
|
|
||||||
alert( options.height ); // возьмёт height из исходного объекта
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Все изменения будут происходить не в самом `options`, а в его наследнике, при этом исходный объект останется незатронутым.
|
|
|
@ -1,29 +0,0 @@
|
||||||
importance: 4
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Аргументы по умолчанию
|
|
||||||
|
|
||||||
Есть функция `Menu`, которая получает аргументы в виде объекта `options`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/* options содержит настройки меню: width, height и т.п. */
|
|
||||||
function Menu(options) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ряд опций должны иметь значение по умолчанию. Мы могли бы проставить их напрямую в объекте `options`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Menu(options) {
|
|
||||||
options.width = options.width || 300; // по умолчанию ширина 300
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
...Но такие изменения могут привести к непредвиденным результатам, т.к. объект `options` может быть повторно использован во внешнем коде. Он передается в `Menu` для того, чтобы параметры из него читали, а не писали.
|
|
||||||
|
|
||||||
Один из способов безопасно назначить значения по умолчанию -- скопировать все свойства `options` в локальные переменные и затем уже менять. Другой способ -- клонировать `options` путём копирования всех свойств из него в новый объект, который уже изменяется.
|
|
||||||
|
|
||||||
При помощи наследования и `Object.create` предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных.
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Разница между вызовами
|
|
||||||
|
|
||||||
Первый вызов ставит `this == rabbit`, остальные ставят `this` равным `Rabbit.prototype`, следуя правилу "`this` -- объект перед точкой".
|
|
||||||
|
|
||||||
Так что только первый вызов выведет `Rabbit`, в остальных он будет `undefined`.
|
|
||||||
|
|
||||||
Код для проверки:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
Rabbit.prototype.sayHi = function() {
|
|
||||||
alert( this.name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Rabbit");
|
|
||||||
|
|
||||||
rabbit.sayHi();
|
|
||||||
Rabbit.prototype.sayHi();
|
|
||||||
Object.getPrototypeOf(rabbit).sayHi();
|
|
||||||
rabbit.__proto__.sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
# Совместимость
|
|
||||||
|
|
||||||
1. Первый вызов работает везде.
|
|
||||||
2. Второй вызов работает везде.
|
|
||||||
3. Третий вызов не будет работать в IE8-, там нет метода `getPrototypeOf`
|
|
||||||
4. Четвёртый вызов -- самый "несовместимый", он не будет работать в IE10-, ввиду отсутствия свойства `__proto__`.
|
|
|
@ -1,29 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Есть ли разница между вызовами?
|
|
||||||
|
|
||||||
Создадим новый объект, вот такой:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
Rabbit.prototype.sayHi = function() {
|
|
||||||
alert( this.name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Rabbit");
|
|
||||||
```
|
|
||||||
|
|
||||||
Одинаково ли сработают эти вызовы?
|
|
||||||
|
|
||||||
```js
|
|
||||||
rabbit.sayHi();
|
|
||||||
Rabbit.prototype.sayHi();
|
|
||||||
Object.getPrototypeOf(rabbit).sayHi();
|
|
||||||
rabbit.__proto__.sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
Все ли они являются кросс-браузерными? Если нет -- в каких браузерах сработает каждый?
|
|
|
@ -1,41 +0,0 @@
|
||||||
Да, можем, но только если уверены, что кто-то позаботился о том, чтобы значение `constructor` было верным.
|
|
||||||
|
|
||||||
В частности, без вмешательства в прототип код точно работает, например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function User(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = new User('Вася');
|
|
||||||
var obj2 = new obj.constructor('Петя');
|
|
||||||
|
|
||||||
alert( obj2.name ); // Петя (сработало)
|
|
||||||
```
|
|
||||||
|
|
||||||
Сработало, так как `User.prototype.constructor == User`.
|
|
||||||
|
|
||||||
Но если кто-то, к примеру, перезапишет `User.prototype` и забудет указать `constructor`, то такой фокус не пройдёт, например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function User(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
*!*
|
|
||||||
User.prototype = {}; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
var obj = new User('Вася');
|
|
||||||
var obj2 = new obj.constructor('Петя');
|
|
||||||
|
|
||||||
alert( obj2.name ); // undefined
|
|
||||||
```
|
|
||||||
|
|
||||||
Почему obj2.name равен undefined? Вот как это работает:
|
|
||||||
|
|
||||||
1. При вызове new `obj.constructor('Петя')`, `obj` ищет у себя свойство `constructor` -- не находит.
|
|
||||||
2. Обращается к своему свойству `__proto__`, которое ведёт к прототипу.
|
|
||||||
3. Прототипом будет (*), пустой объект.
|
|
||||||
4. Далее здесь также ищется свойство constructor -- его нет.
|
|
||||||
5. Где ищем дальше? Правильно -- у следующего прототипа выше, а им будет `Object.prototype`.
|
|
||||||
6. Свойство `Object.prototype.constructor` существует, это встроенный конструктор объектов, который, вообще говоря, не предназначен для вызова с аргументом-строкой, поэтому создаст совсем не то, что ожидается, но то же самое, что вызов `new Object('Петя')`, и у такого объекта не будет `name`.
|
|
|
@ -1,15 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать объект тем же конструктором
|
|
||||||
|
|
||||||
Пусть у нас есть произвольный объект `obj`, созданный каким-то конструктором, каким -- мы не знаем, но хотели бы создать новый объект с его помощью.
|
|
||||||
|
|
||||||
Сможем ли мы сделать так?
|
|
||||||
|
|
||||||
```js
|
|
||||||
var obj2 = new obj.constructor();
|
|
||||||
```
|
|
||||||
|
|
||||||
Приведите пример конструкторов для `obj`, при которых такой код будет работать верно -- и неверно.
|
|
|
@ -1,520 +0,0 @@
|
||||||
# Managing prototype: the history
|
|
||||||
|
|
||||||
In modern Javascript there are many ways to manipulate object prototype. But it wasn't like that all the time.
|
|
||||||
|
|
||||||
Let's swim a little bit with the flow of history to understand these ways and internals of Javascript.
|
|
||||||
|
|
||||||
## Old times: F.prototype
|
|
||||||
|
|
||||||
JavaScript has prototypal inheritance from the beginning. It was one of the core features of the language.
|
|
||||||
|
|
||||||
But in the old times, the `[[Prototype]]` property was internal. There was no way to read or modify it.
|
|
||||||
|
|
||||||
There was only way to set it: `prototype` property on a constructor function. And it still works.
|
|
||||||
|
|
||||||
**When `new F` is called, `[[Prototype]]` of the new object is set to `F.prototype`.**
|
|
||||||
|
|
||||||
Here `F.prototype` means a regular property named `"prototype"`, we didn't use it before.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let animal = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype = animal;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
|
|
||||||
|
|
||||||
alert( rabbit.eats ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting `Rabbit.prototype = animal` literally means the following: "When a `new Rabbit` is created, assign its prototype to `animal`".
|
|
||||||
|
|
||||||
Here's the picture:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
To make it clear: `prototype` is not `[[Prototype]]`. They look similar, but they are totally different. The value of `Rabbit.prototype` is used to set `[[Prototype]]`. That's all. Afterwards, the inhertiance mechanism uses `[[Prototype]]` only.
|
|
||||||
|
|
||||||
```smart header="`prototype` is only \"magical\" for constructors"
|
|
||||||
We can create a property named `"prototype"` in any object. But the special behavior only happens if it's assigned to a constructor function.
|
|
||||||
|
|
||||||
And even if we assign it to a constructor function, it does nothing until we call it with `new`. And only then it will be used to set the prototype for a new object.
|
|
||||||
```
|
|
||||||
|
|
||||||
```warn header="The value of `prototype` should be an object or null"
|
|
||||||
Technically, we can assign anything to `F.prototype`. That's a regular property.
|
|
||||||
|
|
||||||
But the `new` operator will only use it to set `[[Prototype]]` if it's an object or null. Any other value like a string or a number will be ignored.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Native prototypes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Встроенные "классы" в JavaScript
|
|
||||||
|
|
||||||
В JavaScript есть встроенные объекты: `Date`, `Array`, `Object` и другие. Они используют прототипы и демонстрируют организацию "псевдоклассов" на JavaScript, которую мы вполне можем применить и для себя.
|
|
||||||
|
|
||||||
[cut]
|
|
||||||
|
|
||||||
## Откуда методы у {} ?
|
|
||||||
|
|
||||||
Начнём мы с того, что создадим пустой объект и выведем его.
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var obj = {};
|
|
||||||
alert( obj ); // "[object Object]" ?
|
|
||||||
```
|
|
||||||
|
|
||||||
Где код, который генерирует строковое представление для `alert(obj)`? Объект-то ведь пустой.
|
|
||||||
|
|
||||||
## Object.prototype
|
|
||||||
|
|
||||||
...Конечно же, это сделал метод `toString`, который находится... Конечно, не в самом объекте (он пуст), а в его прототипе `obj.__proto__`, можно его даже вывести:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
alert( {}.__proto__.toString ); // function toString
|
|
||||||
```
|
|
||||||
|
|
||||||
Откуда новый объект `obj` получает такой `__proto__`?
|
|
||||||
|
|
||||||
1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов.
|
|
||||||
2. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`.
|
|
||||||
3. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Это можно легко проверить:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var obj = {};
|
|
||||||
|
|
||||||
// метод берётся из прототипа?
|
|
||||||
alert( obj.toString == Object.prototype.toString ); // true, да
|
|
||||||
|
|
||||||
// проверим, правда ли что __proto__ это Object.prototype?
|
|
||||||
alert( obj.__proto__ == Object.prototype ); // true
|
|
||||||
|
|
||||||
// А есть ли __proto__ у Object.prototype?
|
|
||||||
alert( obj.__proto__.__proto__ ); // null, нет
|
|
||||||
```
|
|
||||||
|
|
||||||
## Встроенные "классы" в JavaScript
|
|
||||||
|
|
||||||
Точно такой же подход используется в массивах `Array`, функциях `Function` и других объектах. Встроенные методы для них находятся в `Array.prototype`, `Function.prototype` и т.п.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Например, когда мы создаём массив, `[1, 2, 3]`, то это альтернативный вариант синтаксиса `new Array`, так что у массивов есть стандартный прототип `Array.prototype`.
|
|
||||||
|
|
||||||
Но в нём есть методы лишь для массивов, а для общих методов всех объектов есть ссылка `Array.prototype.__proto__`, равная `Object.prototype`.
|
|
||||||
|
|
||||||
Аналогично, для функций.
|
|
||||||
|
|
||||||
Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее.
|
|
||||||
|
|
||||||
Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`.
|
|
||||||
|
|
||||||
**Поэтому говорят, что "все объекты наследуют от `Object`", а если более точно, то от `Object.prototype`.**
|
|
||||||
|
|
||||||
"Псевдоклассом" или, более коротко, "классом", называют функцию-конструктор вместе с её `prototype`. Такой способ объявления классов называют "прототипным стилем ООП".
|
|
||||||
|
|
||||||
При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который выводит элементы массива через запятую:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var arr = [1, 2, 3]
|
|
||||||
alert( arr ); // 1,2,3 <-- результат Array.prototype.toString
|
|
||||||
```
|
|
||||||
|
|
||||||
Как мы видели раньше, у `Object.prototype` есть свой `toString`, но так как в `Array.prototype` он ищется первым, то берётся именно вариант для массивов:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
````smart header="Вызов методов через `call` и `apply` из прототипа"
|
|
||||||
Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для `arguments`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function showList() {
|
|
||||||
*!*
|
|
||||||
alert( [].join.call(arguments, " - ") );
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша
|
|
||||||
```
|
|
||||||
|
|
||||||
Так как метод `join` находится в `Array.prototype`, то можно вызвать его оттуда напрямую, вот так:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function showList() {
|
|
||||||
*!*
|
|
||||||
alert( Array.prototype.join.call(arguments, " - ") );
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша
|
|
||||||
```
|
|
||||||
|
|
||||||
Это эффективнее, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- больше букв писать.
|
|
||||||
````
|
|
||||||
|
|
||||||
## Примитивы
|
|
||||||
|
|
||||||
Примитивы не являются объектами, но методы берут из соответствующих прототипов: `Number.prototype`, `Boolean.prototype`, `String.prototype`.
|
|
||||||
|
|
||||||
По стандарту, если обратиться к свойству числа, строки или логического значения, то будет создан объект соответствующего типа, например `new String` для строки, `new Number` для чисел, `new Boolean` -- для логических выражений.
|
|
||||||
|
|
||||||
Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен.
|
|
||||||
|
|
||||||
Именно так работает код ниже:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var user = "Вася"; // создали строку (примитив)
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert( user.toUpperCase() ); // ВАСЯ
|
|
||||||
// был создан временный объект new String
|
|
||||||
// вызван метод
|
|
||||||
// new String уничтожен, результат возвращён
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно даже попробовать записать в этот временный объект свойство:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
// попытаемся записать свойство в строку:
|
|
||||||
var user = "Вася";
|
|
||||||
user.age = 30;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert( user.age ); // undefined
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Свойство `age` было записано во временный объект, который был тут же уничтожен, так что смысла в такой записи немного.
|
|
||||||
|
|
||||||
````warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования"
|
|
||||||
Технически, можно создавать объекты для примитивов и вручную, например `new Number`. Но в ряде случаев получится откровенно бредовое поведение. Например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
alert( typeof 1 ); // "number"
|
|
||||||
|
|
||||||
alert( typeof new Number(1) ); // "object" ?!?
|
|
||||||
```
|
|
||||||
|
|
||||||
Или, ещё страннее:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var zero = new Number(0);
|
|
||||||
|
|
||||||
if (zero) { // объект - true, так что alert выполнится
|
|
||||||
alert( "число ноль -- true?!?" );
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Поэтому в явном виде `new String`, `new Number` и `new Boolean` никогда не вызываются.
|
|
||||||
````
|
|
||||||
|
|
||||||
```warn header="Значения `null` и `undefined` не имеют свойств"
|
|
||||||
Значения `null` и `undefined` стоят особняком. Вышесказанное к ним не относится.
|
|
||||||
|
|
||||||
Для них нет соответствующих классов, в них нельзя записать свойство (будет ошибка), в общем, на конкурсе "самое примитивное значение" они точно разделили бы первое место.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Изменение встроенных прототипов [#native-prototype-change]
|
|
||||||
|
|
||||||
Встроенные прототипы можно изменять. В том числе -- добавлять свои методы.
|
|
||||||
|
|
||||||
Мы можем написать метод для многократного повторения строки, и он тут же станет доступным для всех строк:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
String.prototype.repeat = function(times) {
|
|
||||||
return new Array(times + 1).join(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( "ля".repeat(3) ); // ляляля
|
|
||||||
```
|
|
||||||
|
|
||||||
Аналогично мы могли бы создать метод `Object.prototype.each(func)`, который будет применять `func` к каждому свойству:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
for (var prop in this) {
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value); // вызовет f(prop, value), this=value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Попробуем! (внимание, пока что это работает неверно!)
|
|
||||||
var user = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
user.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age -> (!) each
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Обратите внимание -- пример выше работает не совсем корректно. Вместе со свойствами объекта `user` он выводит и наше свойство `each`. Технически, это правильно, так как цикл `for..in` перебирает свойства и в прототипе тоже, но не очень удобно.
|
|
||||||
|
|
||||||
Конечно, это легко поправить добавлением проверки `hasOwnProperty`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
|
|
||||||
for (var prop in this) {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// пропускать свойства из прототипа
|
|
||||||
if (!this.hasOwnProperty(prop)) continue;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Теперь все будет в порядке
|
|
||||||
var obj = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь это сработало, теперь код работает верно. Но мы же не хотим добавлять `hasOwnProperty` в цикл по любому объекту! Поэтому либо не добавляйте свойства в `Object.prototype`, либо можно использовать [дескриптор свойства](/descriptors-getters-setters) и флаг `enumerable`.
|
|
||||||
|
|
||||||
Это, конечно, не будет работать в IE8-:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
|
|
||||||
for (var prop in this) {
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// поправить объявление свойства, установив флаг enumerable: false
|
|
||||||
Object.defineProperty(Object.prototype, 'each', {
|
|
||||||
enumerable: false
|
|
||||||
});
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// Теперь все будет в порядке
|
|
||||||
var obj = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Есть несколько "за" и "против" модификации встроенных прототипов:
|
|
||||||
|
|
||||||
```compare
|
|
||||||
+ Методы в прототипе автоматически доступны везде, их вызов прост и красив.
|
|
||||||
- Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен.
|
|
||||||
- Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения.
|
|
||||||
```
|
|
||||||
|
|
||||||
Как правило, минусы весомее, но есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются.
|
|
||||||
|
|
||||||
**Допустимо изменение прототипа встроенных объектов, которое добавляет поддержку метода из современных стандартов в те браузеры, где её пока нет.**
|
|
||||||
|
|
||||||
Например, добавим `Object.create(proto)` в старые браузеры:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (!Object.create) {
|
|
||||||
|
|
||||||
Object.create = function(proto) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = proto;
|
|
||||||
return new F;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Именно так работает библиотека [es5-shim](https://github.com/kriskowal/es5-shim), которая предоставляет многие функции современного JavaScript для старых браузеров. Они добавляются во встроенные объекты и их прототипы.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
- Методы встроенных объектов хранятся в их прототипах.
|
|
||||||
- Встроенные прототипы можно расширить или поменять.
|
|
||||||
- Добавление методов в `Object.prototype`, если оно не сопровождается `Object.defineProperty` с установкой `enumerable` (IE9+), "сломает" циклы `for..in`, поэтому стараются в этот прототип методы не добавлять.
|
|
||||||
|
|
||||||
Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов.
|
|
||||||
|
|
||||||
Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create">Object.create</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys">Object.keys</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function.prototype.bind</a> и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Свойство constructor
|
|
||||||
|
|
||||||
У каждой функции по умолчанию уже есть свойство `prototype`.
|
|
||||||
|
|
||||||
Оно содержит объект такого вида:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
|
|
||||||
Rabbit.prototype = {
|
|
||||||
constructor: Rabbit
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
В коде выше я создал `Rabbit.prototype` вручную, но ровно такой же -- генерируется автоматически.
|
|
||||||
|
|
||||||
Проверим:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit() {}
|
|
||||||
|
|
||||||
// в Rabbit.prototype есть одно свойство: constructor
|
|
||||||
alert( Object.getOwnPropertyNames(Rabbit.prototype) ); // constructor
|
|
||||||
|
|
||||||
// оно равно Rabbit
|
|
||||||
alert( Rabbit.prototype.constructor == Rabbit ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно его использовать для создания объекта с тем же конструктором, что и данный:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
alert( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Кроль");
|
|
||||||
|
|
||||||
var rabbit2 = new rabbit.constructor("Крольчиха");
|
|
||||||
```
|
|
||||||
|
|
||||||
Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же.
|
|
||||||
|
|
||||||
````warn header="Свойство `constructor` легко потерять"
|
|
||||||
JavaScript никак не использует свойство `constructor`. То есть, оно создаётся автоматически, а что с ним происходит дальше -- это уже наша забота. В стандарте прописано только его создание.
|
|
||||||
|
|
||||||
В частности, при перезаписи `Rabbit.prototype = { jumps: true }` свойства `constructor` больше не будет.
|
|
||||||
|
|
||||||
Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не "сломается". Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие `constructor` вручную:
|
|
||||||
```js
|
|
||||||
Rabbit.prototype = {
|
|
||||||
jumps: true,
|
|
||||||
*!*
|
|
||||||
constructor: Rabbit
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Либо можно поступить аккуратно и добавить свойства к встроенному `prototype` без его замены:
|
|
||||||
```js
|
|
||||||
// сохранится встроенный constructor
|
|
||||||
Rabbit.prototype.jumps = true
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
## Эмуляция Object.create для IE8- [#inherit]
|
|
||||||
|
|
||||||
Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи `F.prototype`.
|
|
||||||
|
|
||||||
Теперь небольшое "лирическое отступление" в область совместимости.
|
|
||||||
|
|
||||||
Прямые методы работы с прототипом отсутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах.
|
|
||||||
|
|
||||||
Кросс-браузерный аналог -- назовём его `inherit`, состоит буквально из нескольких строк:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function inherit(proto) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = proto;
|
|
||||||
var object = new F;
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Результат вызова `inherit(animal)` идентичен `Object.create(animal)`. Она создаёт новый пустой объект с прототипом `animal`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var animal = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = inherit(animal);
|
|
||||||
|
|
||||||
alert( rabbit.eats ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Посмотрите внимательно на функцию `inherit` и вы, наверняка, сами поймёте, как она работает...
|
|
||||||
|
|
||||||
Если где-то неясности, то её построчное описание:
|
|
||||||
|
|
||||||
```js no-beautify
|
|
||||||
function inherit(proto) {
|
|
||||||
function F() {} // (1)
|
|
||||||
F.prototype = proto // (2)
|
|
||||||
var object = new F; // (3)
|
|
||||||
return object; // (4)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Создана новая функция `F`. Она ничего не делает с `this`, так что если вызвать `new F`, то получим пустой объект.
|
|
||||||
2. Свойство `F.prototype` устанавливается в будущий прототип `proto`
|
|
||||||
3. Результатом вызова `new F` будет пустой объект с `__proto__` равным значению `F.prototype`.
|
|
||||||
4. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
|
|
||||||
|
|
||||||
Для унификации можно запустить такой код, и метод `Object.create` станет кросс-браузерным:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (!Object.create) Object.create = inherit; /* определение inherit - выше */
|
|
||||||
```
|
|
||||||
|
|
||||||
В частности, аналогичным образом работает библиотека [es5-shim](https://github.com/es-shims/es5-shim), при подключении которой `Object.create` станет доступен для всех браузеров.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
Для произвольной функции -- назовём её `Person`, верно следующее:
|
|
||||||
|
|
||||||
- Прототип `__proto__` новых объектов, создаваемых через `new Person`, можно задавать при помощи свойства `Person.prototype`.
|
|
||||||
- Значением `Person.prototype` по умолчанию является объект с единственным свойством `constructor`, содержащим ссылку на `Person`. Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить.
|
|
||||||
- Современный метод `Object.create(proto)` можно эмулировать при помощи `prototype`, если хочется, чтобы он работал в IE8-.
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
Answers:
|
||||||
|
|
||||||
|
1. `true`.
|
||||||
|
|
||||||
|
The assignment to `Rabbit.prototype` sets up `[[Prototype]]` for new objects, but it does not affect the existing ones.
|
||||||
|
|
||||||
|
2. `false`.
|
||||||
|
|
||||||
|
Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
|
||||||
|
|
||||||
|
So when we change its content through one reference, it is visible through the other one.
|
||||||
|
|
||||||
|
3. `true`.
|
||||||
|
|
||||||
|
All `delete` operations are applied directly to the object. Here `delete rabbit.eats` tries to remove `eats` property from `rabbit`, but it doesn't have it. So the operation won't have any effect.
|
||||||
|
|
||||||
|
4. `undefined`.
|
||||||
|
|
||||||
|
The property `eats` is deleted from the prototype, it doesn't exist any more.
|
|
@ -0,0 +1,92 @@
|
||||||
|
importance: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Changing "prototype"
|
||||||
|
|
||||||
|
In the code below we create `new Rabbit`, and then try to modify its prototype.
|
||||||
|
|
||||||
|
What will be the results?
|
||||||
|
|
||||||
|
In the start, we have this code:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function Rabbit() {}
|
||||||
|
Rabbit.prototype = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
1. We added one more string (emphased), what `alert` shows now?
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
Rabbit.prototype = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
Rabbit.prototype = {};
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // ?
|
||||||
|
```
|
||||||
|
|
||||||
|
2. ...And if the code is like this (replaced one line)?
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
Rabbit.prototype = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
Rabbit.prototype.eats = false;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // ?
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Like this (replaced one line)?
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
Rabbit.prototype = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
delete rabbit.eats;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // ?
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The last variant:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
Rabbit.prototype = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
*!*
|
||||||
|
delete Rabbit.prototype.eats;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // ?
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
The method can take all enumerable keys using `Object.keys` and output their list.
|
||||||
|
|
||||||
|
To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows to provide an object with property descriptors as the second argument.
|
||||||
|
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
let dictionary = Object.create(null, {
|
||||||
|
toString: { // define toString property
|
||||||
|
value() { // with function value
|
||||||
|
return Object.keys(this).join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
dictionary.apple = "Apple";
|
||||||
|
dictionary.__proto__ = "test";
|
||||||
|
|
||||||
|
// apple and __proto__ is in the loop
|
||||||
|
for(let key in dictionary) {
|
||||||
|
alert(key); // "apple", then "__proto"
|
||||||
|
}
|
||||||
|
|
||||||
|
// comma-separated list of properties by toString
|
||||||
|
alert(dictionary); // "apple,__proto__"
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
importance: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Add toString to dictionary
|
||||||
|
|
||||||
|
There's an object `dictionary`, suited to store any `key/value` pairs.
|
||||||
|
|
||||||
|
Add `toString` for it, that would show a list of comma-delimited keys. The method itself should not show up in `for..in` over the object.
|
||||||
|
|
||||||
|
Here's how it should work:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let dictionary = Object.create(null);
|
||||||
|
|
||||||
|
// your code to add toString
|
||||||
|
|
||||||
|
dictionary.apple = "Apple";
|
||||||
|
dictionary.__proto__ = "test";
|
||||||
|
|
||||||
|
// apple and __proto__ is in the loop
|
||||||
|
for(let key in dictionary) {
|
||||||
|
alert(key); // "apple", then "__proto"
|
||||||
|
}
|
||||||
|
|
||||||
|
// comma-separated list of properties by toString
|
||||||
|
alert(dictionary); // "apple,__proto__"
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
The first call has `this == rabbit`, the other ones have `this` equal to `Rabbit.prototype`, because it's actually the object before the dot.
|
||||||
|
|
||||||
|
So only the first call shows `Rabbit`, other ones show `undefined`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function Rabbit(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
Rabbit.prototype.sayHi = function() {
|
||||||
|
alert( this.name );
|
||||||
|
}
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("Rabbit");
|
||||||
|
|
||||||
|
rabbit.sayHi(); // Rabbit
|
||||||
|
Rabbit.prototype.sayHi(); // undefined
|
||||||
|
Object.getPrototypeOf(rabbit).sayHi(); // undefined
|
||||||
|
rabbit.__proto__.sayHi(); // undefined
|
||||||
|
```
|
|
@ -0,0 +1,27 @@
|
||||||
|
importance: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# The difference beteeen calls
|
||||||
|
|
||||||
|
Let's create a new `rabbit` object:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
Rabbit.prototype.sayHi = function() {
|
||||||
|
alert(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("Rabbit");
|
||||||
|
```
|
||||||
|
|
||||||
|
These calls do the same thing or not?
|
||||||
|
|
||||||
|
```js
|
||||||
|
rabbit.sayHi();
|
||||||
|
Rabbit.prototype.sayHi();
|
||||||
|
Object.getPrototypeOf(rabbit).sayHi();
|
||||||
|
rabbit.__proto__.sayHi();
|
||||||
|
```
|
|
@ -0,0 +1,44 @@
|
||||||
|
We can use such approach if we are sure that `"constructor"` property has the correct value.
|
||||||
|
|
||||||
|
For instance, if we don't touch the default `"prototype"`, then this code works for sure:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new User('John');
|
||||||
|
let user2 = new user.constructor('Pete');
|
||||||
|
|
||||||
|
alert( user2.name ); // Pete (worked!)
|
||||||
|
```
|
||||||
|
|
||||||
|
It worked, because `User.prototype.constructor == User`.
|
||||||
|
|
||||||
|
..But if someone, so to say, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
*!*
|
||||||
|
User.prototype = {}; // (*)
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
let user = new User('John');
|
||||||
|
let user2 = new user.constructor('Pete');
|
||||||
|
|
||||||
|
alert( user2.name ); // undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
Why `user2.name` is `undefined`?
|
||||||
|
|
||||||
|
Here's how `new user.constructor('Pete')` works:
|
||||||
|
|
||||||
|
1. First, it looks for `constructor` in `user`. Nothing.
|
||||||
|
2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing.
|
||||||
|
3. The value of `User.prototype` is a plain object `{}`, it's prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used.
|
||||||
|
|
||||||
|
At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all.
|
|
@ -0,0 +1,15 @@
|
||||||
|
importance: 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create an object with the same constructor
|
||||||
|
|
||||||
|
Imagine, we have an arbitrary object `obj`, created by a constructor function -- we don't know which one, but we'd like to create a new object using it.
|
||||||
|
|
||||||
|
Can we do it like that?
|
||||||
|
|
||||||
|
```js
|
||||||
|
let obj2 = new obj.constructor();
|
||||||
|
```
|
||||||
|
|
||||||
|
Give an example of a constructor function for `obj` which lets such code work right. And an example that makes it work wrong.
|
359
1-js/9-object-inheritance/02-managing-prototype/article.md
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
# Managing prototypes: the history
|
||||||
|
|
||||||
|
In modern Javascript there are many ways to manipulate object prototype. But it wasn't like that all the time.
|
||||||
|
|
||||||
|
Let's swim a little bit with the flow of history to understand these ways and also get deeper into internals of Javascript.
|
||||||
|
|
||||||
|
## Old times: F.prototype
|
||||||
|
|
||||||
|
JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language.
|
||||||
|
|
||||||
|
But in the old times, the `[[Prototype]]` property was internal. There was no way to read or modify it.
|
||||||
|
|
||||||
|
There was only way to set it: to use a `"prototype"` property of the constructor function.
|
||||||
|
|
||||||
|
As we know already, `new F` creates a new object. But what we didn't use yet is a special `"prototype"` property on it.
|
||||||
|
|
||||||
|
**When `new F` is called, `[[Prototype]]` of the new object is set to `F.prototype`.**
|
||||||
|
|
||||||
|
Here `F.prototype` means a regular property named `"prototype"`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.
|
||||||
|
|
||||||
|
Like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
function Rabbit(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
Rabbit.prototype = animal;
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
|
||||||
|
|
||||||
|
alert( rabbit.eats ); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `Rabbit.prototype = animal` literally states the following: "When a `new Rabbit` is created, assign its `[[Prototype]]` to `animal`".
|
||||||
|
|
||||||
|
Here's the picture:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
On the picture, `"prototype"` is a horizontal arrow, meaning that it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
|
||||||
|
|
||||||
|
To make it finally clear: `"prototype"` is not `[[Prototype]]`. They look similar, but they are totally different. The value of `Rabbit.prototype` is used to set `[[Prototype]]`. That's all about it. Afterwards, the property search mechanism uses `[[Prototype]]` only.
|
||||||
|
|
||||||
|
To evade any misunderstandings, in this text the quoted `"prototype"` means the property, while the unquoted prototype means the object prototype.
|
||||||
|
|
||||||
|
```smart header="The `\"prototype\"` property is only \"magical\" for constructors"
|
||||||
|
We can create a property named `"prototype"` in any object. But the special behavior only happens if it's assigned to a constructor function.
|
||||||
|
|
||||||
|
And even if we assign it to a constructor function, it does nothing until we call it with `new`. And only then it will be used to set the `[[Prototype]]` for a new object.
|
||||||
|
```
|
||||||
|
|
||||||
|
```warn header="The value of `\"prototype\"` should be either an object or null"
|
||||||
|
Technically, we can assign anything to `F.prototype`. That's a regular property by nature.
|
||||||
|
|
||||||
|
But the `new` operator will only use it for `[[Prototype]]` if it's an object or `null`. Any other value like a string or a number will be ignored.
|
||||||
|
```
|
||||||
|
|
||||||
|
## The "constructor" property
|
||||||
|
|
||||||
|
Every function by default already has the `"prototype"` property.
|
||||||
|
|
||||||
|
It's an object of this form:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
|
||||||
|
Rabbit.prototype = {
|
||||||
|
constructor: Rabbit
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `Rabbit.prototype` is assigned manually, but the same object is its value by default.
|
||||||
|
|
||||||
|
We can check it:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
// Rabbit.prototype = { constructor: Rabbit }
|
||||||
|
|
||||||
|
alert( Rabbit.prototype.constructor == Rabbit ); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the picture:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Naturally, the `"constructor"` property becomes available to all rabbits through the `[[Prototype]]`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function Rabbit() {}
|
||||||
|
|
||||||
|
let rabbit = new Rabbit();
|
||||||
|
|
||||||
|
alert(rabbit.constructor == Rabbit); // true
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
We can use it to create a new object using the same constructor as the existing one:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function Rabbit(name) {
|
||||||
|
this.name = name;
|
||||||
|
alert(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("White Rabbit");
|
||||||
|
|
||||||
|
let rabbit2 = new rabbit.constructor("Black Rabbit");
|
||||||
|
```
|
||||||
|
|
||||||
|
That may come in handy when we have an object, but don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create the same.
|
||||||
|
|
||||||
|
Probably the most important thing about `"constructor"` is that...
|
||||||
|
|
||||||
|
**JavaScript itself does not use the `"constructor"` property at all.**
|
||||||
|
|
||||||
|
Yes, it exists in the default `"prototype"` for functions, but that's literally all about it. No language function relies on it and nothing controls its validity.
|
||||||
|
|
||||||
|
It is created automatically, but what happens with it later -- is totally on us.
|
||||||
|
|
||||||
|
In particular, if we assign our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it any more.
|
||||||
|
|
||||||
|
Such assignment won't break native methods or syntax, because nothing in the language uses the `"constructor"` property. But we may want to keep `"constructor"` for convenience or just in case, by adding properties to the default `"prototype"` instead of overwriting it as a whole:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function Rabbit() {}
|
||||||
|
|
||||||
|
// Not overwrite Rabbit.prototype totally
|
||||||
|
// just add to it
|
||||||
|
Rabbit.prototype.jumps = true
|
||||||
|
// the default Rabbit.prototype.constructor is preserved
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, alternatively, recreate it manually:
|
||||||
|
|
||||||
|
```js
|
||||||
|
Rabbit.prototype = {
|
||||||
|
jumps: true,
|
||||||
|
*!*
|
||||||
|
constructor: Rabbit
|
||||||
|
*/!*
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Object.prototype
|
||||||
|
|
||||||
|
The `"prototype"` property is deep in the core of Javascript. All built-in objects use it to keep their methods.
|
||||||
|
|
||||||
|
We'll see how it works for plain objects first, and then for more complex ones.
|
||||||
|
|
||||||
|
Let's say we output an empty object:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let obj = {};
|
||||||
|
alert( obj ); // "[object Object]" ?
|
||||||
|
```
|
||||||
|
|
||||||
|
Where's the code that generates `"[object Object]"`? That's a built-in `toString` method, but where is it? The object is empty.
|
||||||
|
|
||||||
|
Of course, it's in the prototype. Let's check:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
alert( {}.__proto__.toString ); // function toString()
|
||||||
|
```
|
||||||
|
|
||||||
|
The short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is the built-in object constructor function. That value of its `"prototype"` property is a built-in object with `toString` and other functions:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
During the construction of `new Object()`, the `[[Prototype]]` of it is set to `Object.prototype`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For future calls of `obj.toString()` -- the function will be taken from `Object.prototype`.
|
||||||
|
|
||||||
|
There are several other methods in `Object.prototype`, for instance:
|
||||||
|
|
||||||
|
- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty) -- returns `true` if `obj` has its own (not inherited) property named `key`.
|
||||||
|
- [objA.isPrototypeOf(objB)](mdn:js/Object/isPrototypeOf) -- returns `true` if `objA` is a prototype of `objB`, taking into account the full prototype chain. In other words, it checks if `objB.__proto__.__proto__...__proto__ == objA`.
|
||||||
|
|
||||||
|
They are available for all objects by default.
|
||||||
|
|
||||||
|
For instance, we can use `for..in` loop to walk over all properties and `hasOwnProperty` -- to detect (and filter if needed) properties that are inherited:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
jumps: true,
|
||||||
|
__proto__: animal
|
||||||
|
};
|
||||||
|
|
||||||
|
for(let key in rabbit) {
|
||||||
|
*!*
|
||||||
|
let isOwn = rabbit.hasOwnProperty(key);
|
||||||
|
*/!*
|
||||||
|
alert(`${key} is own: ${isOwn}`); // "jumps is own: true", then: "eats is own: false"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we have a chain of prototypes: `rabbit`, then `animal`, then `Object.prototype`, the implicit prototype of `animal`, and only `null` above it:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Quite surprising, most object-related methods are not in `Object.prototype`, but on the `Object` constructor itself.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
|
||||||
|
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
|
||||||
|
- [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of enumerable own string property names.
|
||||||
|
- ...and others.
|
||||||
|
|
||||||
|
Why language creators made this way? What's the point of making `Object.keys(obj)` instead of better-looking `obj.keys()`?
|
||||||
|
|
||||||
|
That's because it's possible to create objects not inheriting from `Object.prototype`. Such objects do not inherit prototype methods, but `Object.keys` will work for them just nice. We'll see examples very soon.
|
||||||
|
|
||||||
|
Here we were talking about plain objects, but `Array`, `Date` and other native objects also keep their methods in `Array.prototype`, `Date.prototype` and so on respectively. We'll look more deeply into the overall structure in the next chapter.
|
||||||
|
|
||||||
|
Meanwhile, let's continue with the history flow.
|
||||||
|
|
||||||
|
## ES5: Object.create
|
||||||
|
|
||||||
|
People wanted a more straightforward way to use prototypal inheritance, without constructor functions as well.
|
||||||
|
|
||||||
|
Some browsers implemented non-standard `__proto__` property, and specification also advanced a bit.
|
||||||
|
|
||||||
|
With the arrival of 5th edition of EcmaScript, a new method has appeared:
|
||||||
|
|
||||||
|
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
|
||||||
|
|
||||||
|
Now we could use a single-argument call of `Object.create` to create the inheriting object, and then fill it with properties:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
// Historical code
|
||||||
|
let animal = {
|
||||||
|
eats: true
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let rabbit = Object.create(animal);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert(rabbit.eats); // true
|
||||||
|
|
||||||
|
// add other properties
|
||||||
|
rabbit.jumps = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
Great, prototypal inheritance became possible without any constructor functions!
|
||||||
|
|
||||||
|
````smart header="`Object.create` to make a clone"
|
||||||
|
Nowadays, `Object.create` is sometimes used to perform a full object cloning:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// fully identical clone of obj
|
||||||
|
let clone = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
|
||||||
|
```
|
||||||
|
|
||||||
|
This call makes a truly exact copy, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`.
|
||||||
|
````
|
||||||
|
## Full access
|
||||||
|
|
||||||
|
In 2015, the new version of EcmaScript specification included two methods to get/set prototype:
|
||||||
|
|
||||||
|
- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the prototype of `obj`.
|
||||||
|
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the prototype of `obj` to `proto`.
|
||||||
|
|
||||||
|
Using `setPrototypeOf` was not recommended, because Javascript engines try their best to remember prototype chains and optimize access. For most practical tasks, prototype chains are stable: `rabbit` inherits from `animal`, and that's it, so this method is indeed rarely used. But it exists.
|
||||||
|
|
||||||
|
At the same time there was a quest to either ditch or standartize de-facto implemented by engines `__proto__` property.
|
||||||
|
|
||||||
|
Usually things that are adopted by everyone make their way into the standard. But with `__proto__` there was a problem.
|
||||||
|
|
||||||
|
As we know, objects can be used as associative arrays, that is: to store key/value pairs. And if the key is a user-provided string, then he can type in `"__proto__"` as well.
|
||||||
|
|
||||||
|
Like in this example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let obj = {};
|
||||||
|
|
||||||
|
let key = prompt("What's the key?", "__proto__");
|
||||||
|
obj[key] = "some value";
|
||||||
|
|
||||||
|
alert(obj[key]); // [object Object], not "some value"!
|
||||||
|
```
|
||||||
|
|
||||||
|
Here the assignment is ignored, because `__proto__` must be either an object or `null`, a string can not become a prototype. That's already not good, because we expect it to happen. But in more complex cases the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways.
|
||||||
|
|
||||||
|
What's worst -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and may turn them into vulnerabilities.
|
||||||
|
|
||||||
|
Such thing happens only with `__proto__`. All other properties are safe.
|
||||||
|
|
||||||
|
So, what to do?
|
||||||
|
|
||||||
|
It was decided that `__proto__` should indeed be standartized, but as a 2nd class citizen, in Annex B of the standard, that is optional for non-browser environments. And in a way that allows to work around that problem.
|
||||||
|
|
||||||
|
The `__proto__`, by the latest specification, is not a property of an object, but an accessor property of `Object.prototype`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
So, if `obj.__proto__` is read or assigned, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`.
|
||||||
|
|
||||||
|
As it was said in the beginning: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself.
|
||||||
|
|
||||||
|
Now, if we want to use an object as an assotiative array, we can do it with a little trick:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
let obj = Object.create(null);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
let key = prompt("What's the key?", "__proto__");
|
||||||
|
obj[key] = "some value";
|
||||||
|
|
||||||
|
alert(obj[key]); // "some value"
|
||||||
|
```
|
||||||
|
|
||||||
|
`Object.create(null)` creates an empty object without a prototype:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
So, there are no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right.
|
||||||
|
|
||||||
|
Of course, such object lacks other built-in object methods, e.g. `toString`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
let obj = Object.create(null);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert(obj); // Error (no toString)
|
||||||
|
```
|
||||||
|
|
||||||
|
...But that's usually ok for associative arrays. If needed, we can add a `toString` of our own.
|
||||||
|
|
||||||
|
Please note that most object methods are `Object.something`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects.
|
||||||
|
|
||||||
|
|
||||||
|
## Summary [todo]
|
||||||
|
|
||||||
|
Here in the tutorial I use `__proto__` for shorter and more readable examples. Also all modern engines support it. But in the long run, `Object.getPrototypeOf/setPrototypeOf` is safer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 4 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 34 KiB |
161
1-js/9-object-inheritance/03-native-prototypes/article.md
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
|
||||||
|
# Native prototypes
|
||||||
|
|
||||||
|
Native object, such as `Array`, `Date`, `Function` and others also keep methods in prototypes.
|
||||||
|
|
||||||
|
For instance, when we create an array, `[1, 2, 3]`, the default `Array` constructor is used. The data is written into the object, while `Array.prototype` becomes its prototype and provides methods. That's very memory-effecient.
|
||||||
|
|
||||||
|
And, by specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects".
|
||||||
|
|
||||||
|
[cut]
|
||||||
|
|
||||||
|
Here's the overall picture (for 3 natives to fit):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Let's check the prototype chain:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2, 3];
|
||||||
|
|
||||||
|
// it inherits from Array.prototype
|
||||||
|
alert( arr.__proto__ === Array.prototype ); // true
|
||||||
|
|
||||||
|
// then from Object.prototype
|
||||||
|
alert( arr.__proto__.__proto__ === Object.prototype ); // true
|
||||||
|
|
||||||
|
// and null on the top
|
||||||
|
alert( arr.__proto__.__proto__.__proto__ ); // null
|
||||||
|
```
|
||||||
|
|
||||||
|
Some methods in prototypes may overlap, for instance, `Array.prototype` has its own `toString` that lists comma-delimited elements:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2, 3]
|
||||||
|
alert( arr ); // 1,2,3 <-- the result of Array.prototype.toString
|
||||||
|
```
|
||||||
|
|
||||||
|
As we've seen before, `Object.prototype` has `toString` as well, but `Array.prototype` is earlier in the chain, so the array variant is used.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Functions also work the same way. They are objects of a built-in `Function` constructor, and their methods: `call/apply` and others are taken from `Function.prototype`. Functions have their own `toString` too.
|
||||||
|
|
||||||
|
The most intricate thing is with strings, numbers and booleans. As we remember, they are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors `String`, `Number`, `Boolean`, they provide the methods and disappear. These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`.
|
||||||
|
|
||||||
|
````smart header="Using methods directly from the prototype"
|
||||||
|
In [one of previous chapters](info:call-apply-decorators#method-borrowing) we talked about method borrowing:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function showArgs() {
|
||||||
|
*!*
|
||||||
|
// borrow join from array and call in the context of arguments
|
||||||
|
alert( [].join.call(arguments, " - ") );
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
showList("John", "Pete", "Alice"); // John - Pete - Alice
|
||||||
|
```
|
||||||
|
|
||||||
|
Because `join` resides in `Array.prototype`, we can call it from there directly and rewrite it as:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function showArgs() {
|
||||||
|
*!*
|
||||||
|
alert( Array.prototype.join.call(arguments, " - ") );
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's more efficient, because evades creation of an extra array object `[]`. From the other side -- more letters to write it.
|
||||||
|
````
|
||||||
|
|
||||||
|
````warn header="Constructors `String/Number/Boolean` are for internal use only"
|
||||||
|
Technically, we can create "wrapper objects" for primitives manually using `new Number(1)`. But things will go crazy in many places.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
alert( typeof 1 ); // "number"
|
||||||
|
|
||||||
|
alert( typeof new Number(1) ); // "object"!
|
||||||
|
```
|
||||||
|
|
||||||
|
And, because it's an object:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let zero = new Number(0);
|
||||||
|
|
||||||
|
if (zero) { // zero is true, because it's an object
|
||||||
|
alert( "zero is true in the boolean context?!?" );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The same functions `String/Number/Boolean` without `new` have a totally different behavior: they convert a value to the corresponding type: to a string, a number, or a boolean.
|
||||||
|
|
||||||
|
This is totally valid:
|
||||||
|
```js
|
||||||
|
let num = Number("123"); // convert a string to number
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```warn header="Values `null` and `undefined` have no object wrappers"
|
||||||
|
Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changing native prototypes [#native-prototype-change]
|
||||||
|
|
||||||
|
Native prototypes can be changed. For instance, we can add a method to `String.prototype`, and it will become available for all strings:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
String.prototype.show = function() {
|
||||||
|
alert(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
"BOOM!".show(); // BOOM!
|
||||||
|
```
|
||||||
|
|
||||||
|
Throughout the process of development we often have ideas which built-in methods we'd like to have. And there may be slight temptation to add them to prototypes. But that practice is generally frowned upon.
|
||||||
|
|
||||||
|
Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them overwrites the other one.
|
||||||
|
|
||||||
|
In modern scripts, there is only one case when modifying native prototypes is approved. That's polyfills.
|
||||||
|
|
||||||
|
If there's a method in Javascript specification that is not yet supported by all browser (or another environment that we use), then may decide to implement it manually and populate the built-in prototype with it.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
if (!String.prototype.repeat) { // if there's no such method
|
||||||
|
// add it to the prototype
|
||||||
|
String.prototype.repeat = function(times) {
|
||||||
|
// actually, the code should be more complex than that,
|
||||||
|
// throw errors for negative values of "times"
|
||||||
|
// the full algorithm is in the specification
|
||||||
|
return new Array(times + 1).join(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
alert( "La".repeat(3) ); // LaLaLa
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary [todo]
|
||||||
|
|
||||||
|
- Методы встроенных объектов хранятся в их прототипах.
|
||||||
|
- Встроенные прототипы можно расширить или поменять.
|
||||||
|
- Добавление методов в `Object.prototype`, если оно не сопровождается `Object.defineProperty` с установкой `enumerable` (IE9+), "сломает" циклы `for..in`, поэтому стараются в этот прототип методы не добавлять.
|
||||||
|
|
||||||
|
Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов.
|
||||||
|
|
||||||
|
Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create">Object.create</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys">Object.keys</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function.prototype.bind</a> и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
160
1-js/9-object-inheritance/04-class-patterns/article.md
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
|
||||||
|
# Class patterns
|
||||||
|
|
||||||
|
There's a special syntax construct and a keyword `class` in JavaScript. But before turning to it, we should consider that the term "class" comes the theory of OOP. And it actually has a broader meaning.
|
||||||
|
|
||||||
|
In JavaScript there are several well-known programming patterns to make classes even without using the `class` construct. And here we'll talk about them first.
|
||||||
|
|
||||||
|
The `class` construct will come naturally in the next chapter.
|
||||||
|
|
||||||
|
When talking about classes, it's important to start from the roots, to evade any ambiguity. So here's the definition of the term.
|
||||||
|
|
||||||
|
[cut]
|
||||||
|
|
||||||
|
```quote author="Wikipedia"
|
||||||
|
In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functional class pattern
|
||||||
|
|
||||||
|
The constructor function below can be considered a class according to the definition:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name) {
|
||||||
|
this.sayHi = function() {
|
||||||
|
alert(name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new User("John");
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
It follows all parts of the definition:
|
||||||
|
|
||||||
|
1. It is a program-code-template for creating objects (callable with `new`).
|
||||||
|
2. It provides initial values for state (`name` from parameters).
|
||||||
|
3. It provides methods (`sayHi`).
|
||||||
|
|
||||||
|
This is called *functional class pattern*. It is rarely used, because prototypes are generally better.
|
||||||
|
|
||||||
|
Here's the same class rewritten using prototypes:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
User.prototype.sayHi = function() {
|
||||||
|
alert(this.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = new User("John");
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the method `sayHi` is shared between all users through prototype. That's more memory-efficient as putting a copy of it into every object like the functional pattern does. Prototype-based classes are also more convenient for inheritance. As we've seen, that's what the language itself uses, and we'll be using them further on.
|
||||||
|
|
||||||
|
### Internal properties and methods
|
||||||
|
|
||||||
|
In the functional class pattern, variables and functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code.
|
||||||
|
|
||||||
|
Here's a bigger example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name, birthday) {
|
||||||
|
|
||||||
|
function calcAge() {
|
||||||
|
new Date().getFullYear() - birthday.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sayHi = function() {
|
||||||
|
alert(name + ', age:' + calcAge());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new User("John", new Date(2000,0,1));
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it. The external code that creates the `user` only can see a *public* method `sayHi`.
|
||||||
|
|
||||||
|
In short, functional classes provide a shared outer lexical environment for private variables and methods.
|
||||||
|
|
||||||
|
Prototype-bases classes do not have it. As we can see, methods are created outside of the constructor, in the prototype. And per-object data like `name` is stored in object properties. So, technically they are all available for external code.
|
||||||
|
|
||||||
|
But there is a widely known agreement that internal properties are prepended with an underscore `"_"`.
|
||||||
|
|
||||||
|
Like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function User(name, birthday) {
|
||||||
|
*!*
|
||||||
|
this._name = name;
|
||||||
|
this._birthday = birthday;
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
User.prototype._calcAge = function() {
|
||||||
|
*/!*
|
||||||
|
return new Date().getFullYear() - this._birthday.getFullYear();
|
||||||
|
};
|
||||||
|
|
||||||
|
User.prototype.sayHi = function() {
|
||||||
|
alert(this._name + ', age:' + this._calcAge());
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = new User("John", new Date(2000,0,1));
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
Technically, that changes nothing. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in external code.
|
||||||
|
|
||||||
|
## Prototype-based classes
|
||||||
|
|
||||||
|
Prototype-based classes are structured like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The code example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function Animal(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Animal.prototype.eat = function() {
|
||||||
|
alert(this.name + ' eats.');
|
||||||
|
};
|
||||||
|
|
||||||
|
function Rabbit(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// inherit methods
|
||||||
|
Object.setPrototypeOf(Rabbit.prototype, Animal.prototype); // (*)
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
Rabbit.prototype.jump = function() {
|
||||||
|
alert(this.name + ' jumps!');
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("White Rabbit")
|
||||||
|
rabbit.eat();
|
||||||
|
rabbit.jump();
|
||||||
|
```
|
||||||
|
|
||||||
|
Here the line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it, that's not painted for brevity.
|
||||||
|
|
||||||
|
The structure of exactly that code piece is:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
call parent method (overrides)
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 46 KiB |
586
1-js/9-object-inheritance/05-class/article.md
Normal file
|
@ -0,0 +1,586 @@
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
|
||||||
|
The "class" construct allows to define prototype-based classes with a much more comfortable syntax than before.
|
||||||
|
|
||||||
|
But more than that -- there are also other inheritance-related features baked in.
|
||||||
|
|
||||||
|
[cut]
|
||||||
|
|
||||||
|
## The "class" syntax
|
||||||
|
|
||||||
|
<!--The class syntax is versatile, so we'll first see the overall picture and then explore it by examples.-->
|
||||||
|
|
||||||
|
The class syntax is versatile, so we'll start from a simple class, and then build on top of it.
|
||||||
|
|
||||||
|
A prototype-based class `User`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function User(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
User.prototype.sayHi = function() {
|
||||||
|
alert(this.name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Can be rewritten as:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class User {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
sayHi() {
|
||||||
|
alert(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"class"` construct is tricky from the beginning. We may think that it defines a new lanuage-level entity, but no!
|
||||||
|
|
||||||
|
The resulting variable `User` is actually a function labelled as a `"constructor"`. The value of `User.prototype` is an object with methods listed in the definition. Here it includes `sayHi` and, well, the reference to `constructor` also.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Here, let's check it:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class User {
|
||||||
|
constructor(name) { this.name = name; }
|
||||||
|
sayHi() { alert(this.name); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// proof: User is the same as constructor
|
||||||
|
alert(User == User.prototype.constructor); // true
|
||||||
|
|
||||||
|
// And there are two methods in its "prototype"
|
||||||
|
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
||||||
|
|
||||||
|
// The usage is same:
|
||||||
|
let user = new User("John");
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|
||||||
|
|
||||||
|
The class constructor function has two special features:
|
||||||
|
|
||||||
|
- It can't be called without `new`.
|
||||||
|
- If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`. Please don't be confused: the string representation may vary, but that doesn't affect anything.
|
||||||
|
|
||||||
|
Please note that no code statements and `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters.
|
||||||
|
|
||||||
|
Here we use a getter/setter pair for `name` to make sure that it is valid:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class User {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
// invokes the setter
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
get name() {
|
||||||
|
*/!*
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
set name(value) {
|
||||||
|
*/!*
|
||||||
|
if (value.length < 4) {
|
||||||
|
throw new Error("Name too short.");
|
||||||
|
}
|
||||||
|
this._name = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new User("John");
|
||||||
|
alert(user.name); // John
|
||||||
|
|
||||||
|
user = new User(""); // Error: name too short
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="Class methods are non-enumerable"
|
||||||
|
Class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its methods.
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart header="What if there's no constructor?"
|
||||||
|
If there's no `constructor` in the `class` construct, then an empty function is generated, same as `constructor() {}`. So things still work the same way.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Class Expression
|
||||||
|
|
||||||
|
Just like functions, classes can be defined inside any other expression, passed around, returned from functions etc:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
function getClass() {
|
||||||
|
*!*
|
||||||
|
return class {
|
||||||
|
sayHi() {
|
||||||
|
alert("Hello");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
let User = getClass();
|
||||||
|
|
||||||
|
new User().sayHi(); // Hello
|
||||||
|
```
|
||||||
|
|
||||||
|
That's normal if we recall that `class` is just a special form of constructor-and-prototype definition.
|
||||||
|
|
||||||
|
Such classes also may have a name, that is visible inside that class only:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let User = class *!*MyClass*/!* {
|
||||||
|
sayHi() {
|
||||||
|
alert(MyClass);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new User().sayHi(); // works
|
||||||
|
|
||||||
|
alert(MyClass); // error, MyClass is only visible in methods of the class
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Inheritance, super
|
||||||
|
|
||||||
|
To inherit from another class, we can specify `"extends"` and the parent class before the brackets `{..}`.
|
||||||
|
|
||||||
|
Here `Rabbit` inherits from `Animal`:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.speed = 0;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(speed) {
|
||||||
|
this.speed += speed;
|
||||||
|
alert(`${this.name} runs with speed ${this.speed}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.speed = 0;
|
||||||
|
alert(`${this.name} stopped.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// Inherit from Animal
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
hide() {
|
||||||
|
alert(`${this.name} hides!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("White Rabbit");
|
||||||
|
|
||||||
|
rabbit.run(5); // White Rabbit runs with speed 5.
|
||||||
|
rabbit.hide(); // White Rabbit hides!
|
||||||
|
```
|
||||||
|
|
||||||
|
The `extends` keyword adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before.
|
||||||
|
|
||||||
|
Now let's move forward and override a method. Naturally, if we specify our own `stop` in `Rabbit`, then the inherited one will not be called:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
stop() {
|
||||||
|
// ...this will be used for rabbit.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...But usually we don't want to fully replace a parent method, but rather to build on top of it, tweak or extend its functionality. So we do something in our method, but call the parent method before/after or in the process.
|
||||||
|
|
||||||
|
Classes provide `"super"` keyword for that.
|
||||||
|
|
||||||
|
- `super.method(...)` to call a parent method.
|
||||||
|
- `super(...)` to call a parent constructor (inside our constructor only).
|
||||||
|
|
||||||
|
For instance, let our rabbit autohide when stopped:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.speed = 0;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(speed) {
|
||||||
|
this.speed += speed;
|
||||||
|
alert(`${this.name} runs with speed ${this.speed}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.speed = 0;
|
||||||
|
alert(`${this.name} stopped.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
hide() {
|
||||||
|
alert(`${this.name} hides!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
stop() {
|
||||||
|
super.stop(); // call parent stop
|
||||||
|
hide(); // and then hide
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
let rabbit = new Rabbit("White Rabbit");
|
||||||
|
|
||||||
|
rabbit.run(5); // White Rabbit runs with speed 5.
|
||||||
|
rabbit.stop(); // White Rabbit stopped. White rabbit hides!
|
||||||
|
```
|
||||||
|
|
||||||
|
With constructors, it is a bit more tricky.
|
||||||
|
|
||||||
|
Let's add a constructor to `Rabbit` that specifies the ear length:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.speed = 0;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
|
||||||
|
*!*
|
||||||
|
constructor(name, earLength) {
|
||||||
|
this.speed = 0;
|
||||||
|
this.name = name;
|
||||||
|
this.earLength = earLength;
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// Doesn't work!
|
||||||
|
let rabbit = new Rabbit("White Rabbit", 10); // Error
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Wops! We've got an error, now we can't create rabbits. What went wrong?
|
||||||
|
|
||||||
|
The short answer is: "constructors in inheriting classes must call `super(...)`, and do it before using `this`".
|
||||||
|
|
||||||
|
...But why? What's going on here? Indeed, the requirement seems strange.
|
||||||
|
|
||||||
|
Of course, there's an explanation.
|
||||||
|
|
||||||
|
In JavaScript, there's a distinction between a constructor function of an inheriting class and all others. If there's an `extend`, then the constructor is labelled with an internal property `[[ConstructorKind]]:"derived"`.
|
||||||
|
|
||||||
|
- When a normal constructor runs, it creates an empty object as `this` and continues with it.
|
||||||
|
- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job.
|
||||||
|
|
||||||
|
So we have a choice:
|
||||||
|
|
||||||
|
- Either do not specify a constructor in the inheriting class at all. Then it will be created by default as `constructor(...args) { super(...args); }`, so `super` will be called.
|
||||||
|
- Or if we specify it, then we must call `super`, at least to create `this`. The topmost constructor in the inheritance chain is not derived, so it will make it.
|
||||||
|
|
||||||
|
The working variant:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.speed = 0;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
|
||||||
|
constructor(name, earLength) {
|
||||||
|
*!*
|
||||||
|
super(name);
|
||||||
|
*/!*
|
||||||
|
this.earLength = earLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// now fine
|
||||||
|
let rabbit = new Rabbit("White Rabbit", 10);
|
||||||
|
alert(rabbit.name); // White Rabbit
|
||||||
|
alert(rabbit.earLength); // 10
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Super: internals, [[HomeObject]]
|
||||||
|
|
||||||
|
Let's get a little deeper under the hood of `super` and learn some interesting things by the way.
|
||||||
|
|
||||||
|
First to say, from all components that we've learned till now, it's impossible for `super` to work.
|
||||||
|
|
||||||
|
Indeed, how it can work? When an object method runs, all it knows is `this`. If `super` wants to take parent methods, maybe it can just use its `[[Prototype]]`?
|
||||||
|
|
||||||
|
Let's try to do it. Without classes, using bare objects at first.
|
||||||
|
|
||||||
|
Here, `rabbit.eat()` should call `animal.eat()`.
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
name: "Animal",
|
||||||
|
eat() {
|
||||||
|
alert(this.name + " eats.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
__proto__: animal,
|
||||||
|
name: "Rabbit",
|
||||||
|
eat() {
|
||||||
|
*!*
|
||||||
|
this.__proto__.eat.call(this); // (*)
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rabbit.eat(); // Rabbit eats.
|
||||||
|
```
|
||||||
|
|
||||||
|
At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object.
|
||||||
|
|
||||||
|
And it works.
|
||||||
|
|
||||||
|
|
||||||
|
Now let's add one more object to the chain, and see how things break:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
name: "Animal",
|
||||||
|
eat() {
|
||||||
|
alert(this.name + " eats.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
__proto__: animal,
|
||||||
|
eat() {
|
||||||
|
// bounce around rabbit-style and call parent
|
||||||
|
this.__proto__.eat.call(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let longEar = {
|
||||||
|
__proto__: rabbit,
|
||||||
|
eat() {
|
||||||
|
// do something with long ears and call parent
|
||||||
|
this.__proto__.eat.call(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
longEar.eat(); // Error: Maximum call stack size exceeded
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Doesn't work any more! If we trace `longEar.eat()` call, it becomes obvious, why:
|
||||||
|
|
||||||
|
1. Inside `longEar.eat()`, we pass the call to `rabbit.eat` giving it the same `this=longEar`.
|
||||||
|
2. Inside `rabbit.eat`, we want to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` ends up being the same `rabbit.eat`!
|
||||||
|
3. ...So it calls itself in the endless loop.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
There problem seems unsolvable, because `this` must always be the calling object itself, no matter which parent method is called. So its prototype will always be the immediate parent of the object. We can't ascend any further.
|
||||||
|
|
||||||
|
To provide the solution, JavaScript adds one more special property for functions: `[[HomeObject]]`.
|
||||||
|
|
||||||
|
**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.**
|
||||||
|
|
||||||
|
This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever.
|
||||||
|
|
||||||
|
But `[[HomeObject]]` is used only for calling parent methods, to resolve the prototype. So it doesn't break compatibility.
|
||||||
|
|
||||||
|
Let's see how it works:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
name: "Animal",
|
||||||
|
eat() { // [[HomeObject]] == animal
|
||||||
|
alert(this.name + " eats.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
__proto__: animal,
|
||||||
|
name: "Rabbit",
|
||||||
|
eat() { // [[HomeObject]] == rabbit
|
||||||
|
super.eat();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let longEar = {
|
||||||
|
__proto__: rabbit,
|
||||||
|
name: "Long Ear",
|
||||||
|
eat() { // [[HomeObject]] == longEar
|
||||||
|
super.eat();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
longEar.eat(); // Long Ear eats.
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Okay now, because `super` always resolves the parent relative to the method's `[[HomeObject]]`.
|
||||||
|
|
||||||
|
`[[HomeObject]]` works both in classes and objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`.
|
||||||
|
|
||||||
|
Here non-method syntax is used, so `[[HomeObject]]` property is not set and the inheritance doesn't work:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let animal = {
|
||||||
|
eat: function() { // should be the short syntax: eat() {...}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rabbit = {
|
||||||
|
__proto__: animal,
|
||||||
|
eat: function() {
|
||||||
|
super.eat();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
rabbit.eat(); // Error in super, because there's no [[HomeObject]]
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Static methods
|
||||||
|
|
||||||
|
Static methods are bound to the class function, not to its `"prototype"`.
|
||||||
|
|
||||||
|
An example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class User {
|
||||||
|
*!*
|
||||||
|
static staticMethod() {
|
||||||
|
*/!*
|
||||||
|
alert(this == User);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
User.staticMethod(); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
That actually does the same as assigning it as a function property:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function User() { }
|
||||||
|
|
||||||
|
User.staticMethod = function() {
|
||||||
|
alert(this == User);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
|
||||||
|
|
||||||
|
Usually, static methods are used when the code is related to the class, but not to a particular object of it.
|
||||||
|
|
||||||
|
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Article {
|
||||||
|
constructor(title, date) {
|
||||||
|
this.title = title;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
static compare(articleA, articleB) {
|
||||||
|
return articleA.date - articleB.date;
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage
|
||||||
|
let articles = [
|
||||||
|
new Article("Mind", new Date(2016, 1, 1)),
|
||||||
|
new Article("Body", new Date(2016, 0, 1)),
|
||||||
|
new Article("JavaScript", new Date(2016, 11, 1))
|
||||||
|
];
|
||||||
|
|
||||||
|
*!*
|
||||||
|
articles.sort(Article.compare);
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert( articles[0].title ); // Body
|
||||||
|
```
|
||||||
|
|
||||||
|
Here `Article.compare` stands "over" the articles, as a meants to compare them.
|
||||||
|
|
||||||
|
Another example would be a so-called "factory" method, that creates an object with specific parameters.
|
||||||
|
|
||||||
|
Like `Article.createTodays()` here:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Article {
|
||||||
|
constructor(title, date) {
|
||||||
|
this.title = title;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
*!*
|
||||||
|
static createTodays() {
|
||||||
|
// remember, this = Article
|
||||||
|
return new this("Todays digest", new Date());
|
||||||
|
}
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
let article = article.createTodays();
|
||||||
|
|
||||||
|
alert( articles.title ); // Todays digest
|
||||||
|
```
|
||||||
|
|
||||||
|
Now every time we need to create a todays digest, we can call `Article.createTodays()`.
|
||||||
|
|
||||||
|
Static methods are often used in database-related classes to search/save/remove entries from the database by a query, without having them at hand.
|
||||||
|
|
||||||
|
|
||||||
|
### Static methods and inheritance
|
||||||
|
|
||||||
|
Todo: picture with function -> prototype and vertical link for functions
|
||||||
|
|
||||||
|
|
||||||
|
## Todo absent constructor
|
||||||
|
|
||||||
|
for simple classes parse `constructor( ){ }`
|
||||||
|
for derived `constructor(... args){ super (...args);}`
|
BIN
1-js/9-object-inheritance/05-class/class-user.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
1-js/9-object-inheritance/05-class/class-user@2x.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
1-js/9-object-inheritance/05-class/this-super-loop.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
1-js/9-object-inheritance/05-class/this-super-loop@2x.png
Normal file
After Width: | Height: | Size: 50 KiB |
|
@ -1,9 +0,0 @@
|
||||||
Результат: `true`, из прототипа
|
|
||||||
|
|
||||||
Результат: `true`. Свойство `prototype` всего лишь задаёт `__proto__` у новых объектов. Так что его изменение не повлияет на `rabbit.__proto__`. Свойство `eats` будет получено из прототипа.
|
|
||||||
|
|
||||||
Результат: `false`. Свойство `Rabbit.prototype` и `rabbit.__proto__` указывают на один и тот же объект. В данном случае изменения вносятся в сам объект.
|
|
||||||
|
|
||||||
Результат: `true`, так как `delete rabbit.eats` попытается удалить `eats` из `rabbit`, где его и так нет. А чтение в `alert` произойдёт из прототипа.
|
|
||||||
|
|
||||||
Результат: `undefined`. Удаление осуществляется из самого прототипа, поэтому свойство `rabbit.eats` больше взять неоткуда.
|
|
|
@ -1,91 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Прототип после создания
|
|
||||||
|
|
||||||
В примерах ниже создаётся объект `new Rabbit`, а затем проводятся различные действия с `prototype`.
|
|
||||||
|
|
||||||
Каковы будут результаты выполнения? Почему?
|
|
||||||
|
|
||||||
Начнём с этого кода. Что он выведет?
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
Добавили строку (выделена), что будет теперь?
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype = {};
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
А если код будет такой? (заменена одна строка):
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype.eats = false;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
А такой? (заменена одна строка)
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
delete rabbit.eats; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
||||||
И последний вариант:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {}
|
|
||||||
Rabbit.prototype = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = new Rabbit();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
delete Rabbit.prototype.eats; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( rabbit.eats );
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
Можно прототипно унаследовать от `options` и добавлять/менять опции в наследнике:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Menu(options) {
|
|
||||||
options = Object.create(options);
|
|
||||||
options.width = options.width || 300;
|
|
||||||
|
|
||||||
alert( options.width ); // возьмёт width из наследника
|
|
||||||
alert( options.height ); // возьмёт height из исходного объекта
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Все изменения будут происходить не в самом `options`, а в его наследнике, при этом исходный объект останется незатронутым.
|
|
|
@ -1,29 +0,0 @@
|
||||||
importance: 4
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Аргументы по умолчанию
|
|
||||||
|
|
||||||
Есть функция `Menu`, которая получает аргументы в виде объекта `options`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/* options содержит настройки меню: width, height и т.п. */
|
|
||||||
function Menu(options) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ряд опций должны иметь значение по умолчанию. Мы могли бы проставить их напрямую в объекте `options`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Menu(options) {
|
|
||||||
options.width = options.width || 300; // по умолчанию ширина 300
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
...Но такие изменения могут привести к непредвиденным результатам, т.к. объект `options` может быть повторно использован во внешнем коде. Он передается в `Menu` для того, чтобы параметры из него читали, а не писали.
|
|
||||||
|
|
||||||
Один из способов безопасно назначить значения по умолчанию -- скопировать все свойства `options` в локальные переменные и затем уже менять. Другой способ -- клонировать `options` путём копирования всех свойств из него в новый объект, который уже изменяется.
|
|
||||||
|
|
||||||
При помощи наследования и `Object.create` предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных.
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Разница между вызовами
|
|
||||||
|
|
||||||
Первый вызов ставит `this == rabbit`, остальные ставят `this` равным `Rabbit.prototype`, следуя правилу "`this` -- объект перед точкой".
|
|
||||||
|
|
||||||
Так что только первый вызов выведет `Rabbit`, в остальных он будет `undefined`.
|
|
||||||
|
|
||||||
Код для проверки:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
Rabbit.prototype.sayHi = function() {
|
|
||||||
alert( this.name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Rabbit");
|
|
||||||
|
|
||||||
rabbit.sayHi();
|
|
||||||
Rabbit.prototype.sayHi();
|
|
||||||
Object.getPrototypeOf(rabbit).sayHi();
|
|
||||||
rabbit.__proto__.sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
# Совместимость
|
|
||||||
|
|
||||||
1. Первый вызов работает везде.
|
|
||||||
2. Второй вызов работает везде.
|
|
||||||
3. Третий вызов не будет работать в IE8-, там нет метода `getPrototypeOf`
|
|
||||||
4. Четвёртый вызов -- самый "несовместимый", он не будет работать в IE10-, ввиду отсутствия свойства `__proto__`.
|
|
|
@ -1,29 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Есть ли разница между вызовами?
|
|
||||||
|
|
||||||
Создадим новый объект, вот такой:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
Rabbit.prototype.sayHi = function() {
|
|
||||||
alert( this.name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Rabbit");
|
|
||||||
```
|
|
||||||
|
|
||||||
Одинаково ли сработают эти вызовы?
|
|
||||||
|
|
||||||
```js
|
|
||||||
rabbit.sayHi();
|
|
||||||
Rabbit.prototype.sayHi();
|
|
||||||
Object.getPrototypeOf(rabbit).sayHi();
|
|
||||||
rabbit.__proto__.sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
Все ли они являются кросс-браузерными? Если нет -- в каких браузерах сработает каждый?
|
|
|
@ -1,41 +0,0 @@
|
||||||
Да, можем, но только если уверены, что кто-то позаботился о том, чтобы значение `constructor` было верным.
|
|
||||||
|
|
||||||
В частности, без вмешательства в прототип код точно работает, например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function User(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = new User('Вася');
|
|
||||||
var obj2 = new obj.constructor('Петя');
|
|
||||||
|
|
||||||
alert( obj2.name ); // Петя (сработало)
|
|
||||||
```
|
|
||||||
|
|
||||||
Сработало, так как `User.prototype.constructor == User`.
|
|
||||||
|
|
||||||
Но если кто-то, к примеру, перезапишет `User.prototype` и забудет указать `constructor`, то такой фокус не пройдёт, например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function User(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
*!*
|
|
||||||
User.prototype = {}; // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
var obj = new User('Вася');
|
|
||||||
var obj2 = new obj.constructor('Петя');
|
|
||||||
|
|
||||||
alert( obj2.name ); // undefined
|
|
||||||
```
|
|
||||||
|
|
||||||
Почему obj2.name равен undefined? Вот как это работает:
|
|
||||||
|
|
||||||
1. При вызове new `obj.constructor('Петя')`, `obj` ищет у себя свойство `constructor` -- не находит.
|
|
||||||
2. Обращается к своему свойству `__proto__`, которое ведёт к прототипу.
|
|
||||||
3. Прототипом будет (*), пустой объект.
|
|
||||||
4. Далее здесь также ищется свойство constructor -- его нет.
|
|
||||||
5. Где ищем дальше? Правильно -- у следующего прототипа выше, а им будет `Object.prototype`.
|
|
||||||
6. Свойство `Object.prototype.constructor` существует, это встроенный конструктор объектов, который, вообще говоря, не предназначен для вызова с аргументом-строкой, поэтому создаст совсем не то, что ожидается, но то же самое, что вызов `new Object('Петя')`, и у такого объекта не будет `name`.
|
|
|
@ -1,15 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Создать объект тем же конструктором
|
|
||||||
|
|
||||||
Пусть у нас есть произвольный объект `obj`, созданный каким-то конструктором, каким -- мы не знаем, но хотели бы создать новый объект с его помощью.
|
|
||||||
|
|
||||||
Сможем ли мы сделать так?
|
|
||||||
|
|
||||||
```js
|
|
||||||
var obj2 = new obj.constructor();
|
|
||||||
```
|
|
||||||
|
|
||||||
Приведите пример конструкторов для `obj`, при которых такой код будет работать верно -- и неверно.
|
|
|
@ -1,205 +0,0 @@
|
||||||
# F.prototype and new
|
|
||||||
|
|
||||||
До этого момента мы говорили о наследовании объектов, объявленных через `{...}`.
|
|
||||||
|
|
||||||
Но в реальных проектах объекты обычно создаются функцией-конструктором через `new`. Посмотрим, как указать прототип в этом случае.
|
|
||||||
|
|
||||||
[cut]
|
|
||||||
|
|
||||||
## Свойство F.prototype
|
|
||||||
|
|
||||||
Самым очевидным решением является назначение `__proto__` в конструкторе.
|
|
||||||
|
|
||||||
Например, если я хочу, чтобы у всех объектов, которые создаются `new Rabbit`, был прототип `animal`, я могу сделать так:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var animal = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
*!*
|
|
||||||
this.__proto__ = animal;
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Кроль");
|
|
||||||
|
|
||||||
alert( rabbit.eats ); // true, из прототипа
|
|
||||||
```
|
|
||||||
|
|
||||||
Недостаток этого подхода -- он не работает в IE10-.
|
|
||||||
|
|
||||||
К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ.
|
|
||||||
|
|
||||||
**Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство `prototype`.**
|
|
||||||
|
|
||||||
**При создании объекта через `new`, в его прототип `__proto__` записывается ссылка из `prototype` функции-конструктора.**
|
|
||||||
|
|
||||||
Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var animal = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Rabbit.prototype = animal;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Кроль"); // rabbit.__proto__ == animal
|
|
||||||
|
|
||||||
alert( rabbit.eats ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: *"При создании объекта через `new Rabbit` запиши ему `__proto__ = animal`".*
|
|
||||||
|
|
||||||
```smart header="Свойство `prototype` имеет смысл только у конструктора"
|
|
||||||
Свойство с именем `prototype` можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору.
|
|
||||||
|
|
||||||
Само по себе, без вызова оператора `new`, оно вообще ничего не делает, его единственное назначение -- указывать `__proto__` для новых объектов.
|
|
||||||
```
|
|
||||||
|
|
||||||
```warn header="Значением `prototype` может быть только объект"
|
|
||||||
Технически, в это свойство можно записать что угодно.
|
|
||||||
|
|
||||||
Однако, при работе `new`, свойство `prototype` будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Свойство constructor
|
|
||||||
|
|
||||||
У каждой функции по умолчанию уже есть свойство `prototype`.
|
|
||||||
|
|
||||||
Оно содержит объект такого вида:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function Rabbit() {}
|
|
||||||
|
|
||||||
Rabbit.prototype = {
|
|
||||||
constructor: Rabbit
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
В коде выше я создал `Rabbit.prototype` вручную, но ровно такой же -- генерируется автоматически.
|
|
||||||
|
|
||||||
Проверим:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit() {}
|
|
||||||
|
|
||||||
// в Rabbit.prototype есть одно свойство: constructor
|
|
||||||
alert( Object.getOwnPropertyNames(Rabbit.prototype) ); // constructor
|
|
||||||
|
|
||||||
// оно равно Rabbit
|
|
||||||
alert( Rabbit.prototype.constructor == Rabbit ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно его использовать для создания объекта с тем же конструктором, что и данный:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Rabbit(name) {
|
|
||||||
this.name = name;
|
|
||||||
alert( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var rabbit = new Rabbit("Кроль");
|
|
||||||
|
|
||||||
var rabbit2 = new rabbit.constructor("Крольчиха");
|
|
||||||
```
|
|
||||||
|
|
||||||
Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же.
|
|
||||||
|
|
||||||
````warn header="Свойство `constructor` легко потерять"
|
|
||||||
JavaScript никак не использует свойство `constructor`. То есть, оно создаётся автоматически, а что с ним происходит дальше -- это уже наша забота. В стандарте прописано только его создание.
|
|
||||||
|
|
||||||
В частности, при перезаписи `Rabbit.prototype = { jumps: true }` свойства `constructor` больше не будет.
|
|
||||||
|
|
||||||
Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не "сломается". Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие `constructor` вручную:
|
|
||||||
```js
|
|
||||||
Rabbit.prototype = {
|
|
||||||
jumps: true,
|
|
||||||
*!*
|
|
||||||
constructor: Rabbit
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Либо можно поступить аккуратно и добавить свойства к встроенному `prototype` без его замены:
|
|
||||||
```js
|
|
||||||
// сохранится встроенный constructor
|
|
||||||
Rabbit.prototype.jumps = true
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
## Эмуляция Object.create для IE8- [#inherit]
|
|
||||||
|
|
||||||
Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи `F.prototype`.
|
|
||||||
|
|
||||||
Теперь небольшое "лирическое отступление" в область совместимости.
|
|
||||||
|
|
||||||
Прямые методы работы с прототипом отсутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах.
|
|
||||||
|
|
||||||
Кросс-браузерный аналог -- назовём его `inherit`, состоит буквально из нескольких строк:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function inherit(proto) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = proto;
|
|
||||||
var object = new F;
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Результат вызова `inherit(animal)` идентичен `Object.create(animal)`. Она создаёт новый пустой объект с прототипом `animal`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var animal = {
|
|
||||||
eats: true
|
|
||||||
};
|
|
||||||
|
|
||||||
var rabbit = inherit(animal);
|
|
||||||
|
|
||||||
alert( rabbit.eats ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Посмотрите внимательно на функцию `inherit` и вы, наверняка, сами поймёте, как она работает...
|
|
||||||
|
|
||||||
Если где-то неясности, то её построчное описание:
|
|
||||||
|
|
||||||
```js no-beautify
|
|
||||||
function inherit(proto) {
|
|
||||||
function F() {} // (1)
|
|
||||||
F.prototype = proto // (2)
|
|
||||||
var object = new F; // (3)
|
|
||||||
return object; // (4)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Создана новая функция `F`. Она ничего не делает с `this`, так что если вызвать `new F`, то получим пустой объект.
|
|
||||||
2. Свойство `F.prototype` устанавливается в будущий прототип `proto`
|
|
||||||
3. Результатом вызова `new F` будет пустой объект с `__proto__` равным значению `F.prototype`.
|
|
||||||
4. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
|
|
||||||
|
|
||||||
Для унификации можно запустить такой код, и метод `Object.create` станет кросс-браузерным:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (!Object.create) Object.create = inherit; /* определение inherit - выше */
|
|
||||||
```
|
|
||||||
|
|
||||||
В частности, аналогичным образом работает библиотека [es5-shim](https://github.com/es-shims/es5-shim), при подключении которой `Object.create` станет доступен для всех браузеров.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
Для произвольной функции -- назовём её `Person`, верно следующее:
|
|
||||||
|
|
||||||
- Прототип `__proto__` новых объектов, создаваемых через `new Person`, можно задавать при помощи свойства `Person.prototype`.
|
|
||||||
- Значением `Person.prototype` по умолчанию является объект с единственным свойством `constructor`, содержащим ссылку на `Person`. Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить.
|
|
||||||
- Современный метод `Object.create(proto)` можно эмулировать при помощи `prototype`, если хочется, чтобы он работал в IE8-.
|
|
||||||
|
|
|
@ -1,307 +0,0 @@
|
||||||
# Встроенные "классы" в JavaScript
|
|
||||||
|
|
||||||
В JavaScript есть встроенные объекты: `Date`, `Array`, `Object` и другие. Они используют прототипы и демонстрируют организацию "псевдоклассов" на JavaScript, которую мы вполне можем применить и для себя.
|
|
||||||
|
|
||||||
[cut]
|
|
||||||
|
|
||||||
## Откуда методы у {} ?
|
|
||||||
|
|
||||||
Начнём мы с того, что создадим пустой объект и выведем его.
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var obj = {};
|
|
||||||
alert( obj ); // "[object Object]" ?
|
|
||||||
```
|
|
||||||
|
|
||||||
Где код, который генерирует строковое представление для `alert(obj)`? Объект-то ведь пустой.
|
|
||||||
|
|
||||||
## Object.prototype
|
|
||||||
|
|
||||||
...Конечно же, это сделал метод `toString`, который находится... Конечно, не в самом объекте (он пуст), а в его прототипе `obj.__proto__`, можно его даже вывести:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
alert( {}.__proto__.toString ); // function toString
|
|
||||||
```
|
|
||||||
|
|
||||||
Откуда новый объект `obj` получает такой `__proto__`?
|
|
||||||
|
|
||||||
1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов.
|
|
||||||
2. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`.
|
|
||||||
3. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Это можно легко проверить:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var obj = {};
|
|
||||||
|
|
||||||
// метод берётся из прототипа?
|
|
||||||
alert( obj.toString == Object.prototype.toString ); // true, да
|
|
||||||
|
|
||||||
// проверим, правда ли что __proto__ это Object.prototype?
|
|
||||||
alert( obj.__proto__ == Object.prototype ); // true
|
|
||||||
|
|
||||||
// А есть ли __proto__ у Object.prototype?
|
|
||||||
alert( obj.__proto__.__proto__ ); // null, нет
|
|
||||||
```
|
|
||||||
|
|
||||||
## Встроенные "классы" в JavaScript
|
|
||||||
|
|
||||||
Точно такой же подход используется в массивах `Array`, функциях `Function` и других объектах. Встроенные методы для них находятся в `Array.prototype`, `Function.prototype` и т.п.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Например, когда мы создаём массив, `[1, 2, 3]`, то это альтернативный вариант синтаксиса `new Array`, так что у массивов есть стандартный прототип `Array.prototype`.
|
|
||||||
|
|
||||||
Но в нём есть методы лишь для массивов, а для общих методов всех объектов есть ссылка `Array.prototype.__proto__`, равная `Object.prototype`.
|
|
||||||
|
|
||||||
Аналогично, для функций.
|
|
||||||
|
|
||||||
Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее.
|
|
||||||
|
|
||||||
Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`.
|
|
||||||
|
|
||||||
**Поэтому говорят, что "все объекты наследуют от `Object`", а если более точно, то от `Object.prototype`.**
|
|
||||||
|
|
||||||
"Псевдоклассом" или, более коротко, "классом", называют функцию-конструктор вместе с её `prototype`. Такой способ объявления классов называют "прототипным стилем ООП".
|
|
||||||
|
|
||||||
При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который выводит элементы массива через запятую:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var arr = [1, 2, 3]
|
|
||||||
alert( arr ); // 1,2,3 <-- результат Array.prototype.toString
|
|
||||||
```
|
|
||||||
|
|
||||||
Как мы видели раньше, у `Object.prototype` есть свой `toString`, но так как в `Array.prototype` он ищется первым, то берётся именно вариант для массивов:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
````smart header="Вызов методов через `call` и `apply` из прототипа"
|
|
||||||
Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для `arguments`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function showList() {
|
|
||||||
*!*
|
|
||||||
alert( [].join.call(arguments, " - ") );
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша
|
|
||||||
```
|
|
||||||
|
|
||||||
Так как метод `join` находится в `Array.prototype`, то можно вызвать его оттуда напрямую, вот так:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function showList() {
|
|
||||||
*!*
|
|
||||||
alert( Array.prototype.join.call(arguments, " - ") );
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша
|
|
||||||
```
|
|
||||||
|
|
||||||
Это эффективнее, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- больше букв писать.
|
|
||||||
````
|
|
||||||
|
|
||||||
## Примитивы
|
|
||||||
|
|
||||||
Примитивы не являются объектами, но методы берут из соответствующих прототипов: `Number.prototype`, `Boolean.prototype`, `String.prototype`.
|
|
||||||
|
|
||||||
По стандарту, если обратиться к свойству числа, строки или логического значения, то будет создан объект соответствующего типа, например `new String` для строки, `new Number` для чисел, `new Boolean` -- для логических выражений.
|
|
||||||
|
|
||||||
Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен.
|
|
||||||
|
|
||||||
Именно так работает код ниже:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var user = "Вася"; // создали строку (примитив)
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert( user.toUpperCase() ); // ВАСЯ
|
|
||||||
// был создан временный объект new String
|
|
||||||
// вызван метод
|
|
||||||
// new String уничтожен, результат возвращён
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно даже попробовать записать в этот временный объект свойство:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
// попытаемся записать свойство в строку:
|
|
||||||
var user = "Вася";
|
|
||||||
user.age = 30;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert( user.age ); // undefined
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Свойство `age` было записано во временный объект, который был тут же уничтожен, так что смысла в такой записи немного.
|
|
||||||
|
|
||||||
````warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования"
|
|
||||||
Технически, можно создавать объекты для примитивов и вручную, например `new Number`. Но в ряде случаев получится откровенно бредовое поведение. Например:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
alert( typeof 1 ); // "number"
|
|
||||||
|
|
||||||
alert( typeof new Number(1) ); // "object" ?!?
|
|
||||||
```
|
|
||||||
|
|
||||||
Или, ещё страннее:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
var zero = new Number(0);
|
|
||||||
|
|
||||||
if (zero) { // объект - true, так что alert выполнится
|
|
||||||
alert( "число ноль -- true?!?" );
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Поэтому в явном виде `new String`, `new Number` и `new Boolean` никогда не вызываются.
|
|
||||||
````
|
|
||||||
|
|
||||||
```warn header="Значения `null` и `undefined` не имеют свойств"
|
|
||||||
Значения `null` и `undefined` стоят особняком. Вышесказанное к ним не относится.
|
|
||||||
|
|
||||||
Для них нет соответствующих классов, в них нельзя записать свойство (будет ошибка), в общем, на конкурсе "самое примитивное значение" они точно разделили бы первое место.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Изменение встроенных прототипов [#native-prototype-change]
|
|
||||||
|
|
||||||
Встроенные прототипы можно изменять. В том числе -- добавлять свои методы.
|
|
||||||
|
|
||||||
Мы можем написать метод для многократного повторения строки, и он тут же станет доступным для всех строк:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
String.prototype.repeat = function(times) {
|
|
||||||
return new Array(times + 1).join(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( "ля".repeat(3) ); // ляляля
|
|
||||||
```
|
|
||||||
|
|
||||||
Аналогично мы могли бы создать метод `Object.prototype.each(func)`, который будет применять `func` к каждому свойству:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
for (var prop in this) {
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value); // вызовет f(prop, value), this=value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Попробуем! (внимание, пока что это работает неверно!)
|
|
||||||
var user = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
user.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age -> (!) each
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Обратите внимание -- пример выше работает не совсем корректно. Вместе со свойствами объекта `user` он выводит и наше свойство `each`. Технически, это правильно, так как цикл `for..in` перебирает свойства и в прототипе тоже, но не очень удобно.
|
|
||||||
|
|
||||||
Конечно, это легко поправить добавлением проверки `hasOwnProperty`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
|
|
||||||
for (var prop in this) {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// пропускать свойства из прототипа
|
|
||||||
if (!this.hasOwnProperty(prop)) continue;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Теперь все будет в порядке
|
|
||||||
var obj = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь это сработало, теперь код работает верно. Но мы же не хотим добавлять `hasOwnProperty` в цикл по любому объекту! Поэтому либо не добавляйте свойства в `Object.prototype`, либо можно использовать [дескриптор свойства](/descriptors-getters-setters) и флаг `enumerable`.
|
|
||||||
|
|
||||||
Это, конечно, не будет работать в IE8-:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
Object.prototype.each = function(f) {
|
|
||||||
|
|
||||||
for (var prop in this) {
|
|
||||||
var value = this[prop];
|
|
||||||
f.call(value, prop, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// поправить объявление свойства, установив флаг enumerable: false
|
|
||||||
Object.defineProperty(Object.prototype, 'each', {
|
|
||||||
enumerable: false
|
|
||||||
});
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// Теперь все будет в порядке
|
|
||||||
var obj = {
|
|
||||||
name: 'Вася',
|
|
||||||
age: 25
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.each(function(prop, val) {
|
|
||||||
alert( prop ); // name -> age
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Есть несколько "за" и "против" модификации встроенных прототипов:
|
|
||||||
|
|
||||||
```compare
|
|
||||||
+ Методы в прототипе автоматически доступны везде, их вызов прост и красив.
|
|
||||||
- Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен.
|
|
||||||
- Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения.
|
|
||||||
```
|
|
||||||
|
|
||||||
Как правило, минусы весомее, но есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются.
|
|
||||||
|
|
||||||
**Допустимо изменение прототипа встроенных объектов, которое добавляет поддержку метода из современных стандартов в те браузеры, где её пока нет.**
|
|
||||||
|
|
||||||
Например, добавим `Object.create(proto)` в старые браузеры:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (!Object.create) {
|
|
||||||
|
|
||||||
Object.create = function(proto) {
|
|
||||||
function F() {}
|
|
||||||
F.prototype = proto;
|
|
||||||
return new F;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Именно так работает библиотека [es5-shim](https://github.com/kriskowal/es5-shim), которая предоставляет многие функции современного JavaScript для старых браузеров. Они добавляются во встроенные объекты и их прототипы.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
- Методы встроенных объектов хранятся в их прототипах.
|
|
||||||
- Встроенные прототипы можно расширить или поменять.
|
|
||||||
- Добавление методов в `Object.prototype`, если оно не сопровождается `Object.defineProperty` с установкой `enumerable` (IE9+), "сломает" циклы `for..in`, поэтому стараются в этот прототип методы не добавлять.
|
|
||||||
|
|
||||||
Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов.
|
|
||||||
|
|
||||||
Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create">Object.create</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys">Object.keys</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function.prototype.bind</a> и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim).
|
|
||||||
|
|
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,31 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function CoffeeMachine(power) {
|
|
||||||
// свойства конкретной кофеварки
|
|
||||||
this._power = power;
|
|
||||||
this._waterAmount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// свойства и методы для всех объектов класса
|
|
||||||
CoffeeMachine.prototype.WATER_HEAT_CAPACITY = 4200;
|
|
||||||
|
|
||||||
CoffeeMachine.prototype._getTimeToBoil = function() {
|
|
||||||
return this._waterAmount * this.WATER_HEAT_CAPACITY * 80 / this._power;
|
|
||||||
};
|
|
||||||
|
|
||||||
CoffeeMachine.prototype.run = function() {
|
|
||||||
setTimeout(function() {
|
|
||||||
alert( 'Кофе готов!' );
|
|
||||||
}, this._getTimeToBoil());
|
|
||||||
};
|
|
||||||
|
|
||||||
CoffeeMachine.prototype.setWaterAmount = function(amount) {
|
|
||||||
this._waterAmount = amount;
|
|
||||||
};
|
|
||||||
|
|
||||||
var coffeeMachine = new CoffeeMachine(10000);
|
|
||||||
coffeeMachine.setWaterAmount(50);
|
|
||||||
coffeeMachine.run();
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Перепишите в виде класса
|
|
||||||
|
|
||||||
Есть класс `CoffeeMachine`, заданный в функциональном стиле.
|
|
||||||
|
|
||||||
Задача: переписать `CoffeeMachine` в виде класса с использованием прототипа.
|
|
||||||
|
|
||||||
Исходный код:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function CoffeeMachine(power) {
|
|
||||||
var waterAmount = 0;
|
|
||||||
|
|
||||||
var WATER_HEAT_CAPACITY = 4200;
|
|
||||||
|
|
||||||
function getTimeToBoil() {
|
|
||||||
return waterAmount * WATER_HEAT_CAPACITY * 80 / power;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.run = function() {
|
|
||||||
setTimeout(function() {
|
|
||||||
alert( 'Кофе готов!' );
|
|
||||||
}, getTimeToBoil());
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setWaterAmount = function(amount) {
|
|
||||||
waterAmount = amount;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var coffeeMachine = new CoffeeMachine(10000);
|
|
||||||
coffeeMachine.setWaterAmount(50);
|
|
||||||
coffeeMachine.run();
|
|
||||||
```
|
|
||||||
|
|
||||||
P.S. При описании через прототипы локальные переменные недоступны методам, поэтому нужно будет переделать их в защищённые свойства.
|
|
|
@ -1,46 +0,0 @@
|
||||||
# Почему возникает проблема
|
|
||||||
|
|
||||||
Давайте подробнее разберем происходящее при вызове `speedy.found("яблоко")`:
|
|
||||||
|
|
||||||
1. Интерпретатор ищет свойство `found` в `speedy`. Но `speedy` -- пустой объект, т.к. `new Hamster` ничего не делает с `this`.
|
|
||||||
2. Интерпретатор идёт по ссылке `speedy.__proto__ (==Hamster.prototype)` и находят там метод `found`, запускает его.
|
|
||||||
3. Значение `this` устанавливается в объект перед точкой, т.е. в `speedy`.
|
|
||||||
4. Для выполнения `this.food.push()` нужно найти свойство `this.food`. Оно отсутствует в `speedy`, но есть в `speedy.__proto__`.
|
|
||||||
5. Значение `"яблоко"` добавляется в `speedy.__proto__.food`.
|
|
||||||
|
|
||||||
**У всех хомяков общий живот!** Или, в терминах JavaScript, свойство `food` изменяется в прототипе, который является общим для всех объектов-хомяков.
|
|
||||||
|
|
||||||
Заметим, что этой проблемы не было бы при простом присваивании:
|
|
||||||
|
|
||||||
```js
|
|
||||||
this.food = something;
|
|
||||||
```
|
|
||||||
|
|
||||||
В этом случае значение записалось бы в сам объект, без поиска `found` в прототипе.
|
|
||||||
|
|
||||||
**Проблема возникает только со свойствами-объектами в прототипе.**
|
|
||||||
|
|
||||||
Для исправления проблемы нужно дать каждому хомяку свой живот. Это можно сделать, присвоив его в конструкторе.
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Hamster() {
|
|
||||||
*!*
|
|
||||||
this.food = [];
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
Hamster.prototype.found = function(something) {
|
|
||||||
this.food.push(something);
|
|
||||||
};
|
|
||||||
|
|
||||||
speedy = new Hamster();
|
|
||||||
lazy = new Hamster();
|
|
||||||
|
|
||||||
speedy.found("яблоко");
|
|
||||||
speedy.found("орех");
|
|
||||||
|
|
||||||
alert(speedy.food.length) // 2
|
|
||||||
alert(lazy.food.length) // 0(!)
|
|
||||||
```
|
|
||||||
|
|
||||||
Теперь всё в порядке. У каждого хомяка -- свой живот.
|
|
|
@ -1,34 +0,0 @@
|
||||||
importance: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Хомяки с __proto__
|
|
||||||
|
|
||||||
Вы -- руководитель команды, которая разрабатывает игру, хомяковую ферму. Один из программистов получил задание создать класс "хомяк" (англ - `"Hamster"`).
|
|
||||||
|
|
||||||
Объекты-хомяки должны иметь массив `food` для хранения еды и метод `found` для добавления.
|
|
||||||
|
|
||||||
Ниже -- его решение. При создании двух хомяков, если поел один -- почему-то сытым становится и второй тоже.
|
|
||||||
|
|
||||||
В чём дело? Как поправить?
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Hamster() {}
|
|
||||||
|
|
||||||
Hamster.prototype.food = []; // пустой "живот"
|
|
||||||
|
|
||||||
Hamster.prototype.found = function(something) {
|
|
||||||
this.food.push(something);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Создаём двух хомяков и кормим первого
|
|
||||||
var speedy = new Hamster();
|
|
||||||
var lazy = new Hamster();
|
|
||||||
|
|
||||||
speedy.found("яблоко");
|
|
||||||
speedy.found("орех");
|
|
||||||
|
|
||||||
alert( speedy.food.length ); // 2
|
|
||||||
alert( lazy.food.length ); // 2 (!??)
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
# Свои классы на прототипах
|
|
||||||
|
|
||||||
Используем ту же структуру, что JavaScript использует внутри себя, для объявления своих классов.
|
|
||||||
|
|
||||||
[cut]
|
|
||||||
|
|
||||||
## Обычный конструктор
|
|
||||||
|
|
||||||
Вспомним, как мы объявляли классы ранее.
|
|
||||||
|
|
||||||
Например, этот код задаёт класс `Animal` в функциональном стиле, без всяких прототипов:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Animal(name) {
|
|
||||||
this.speed = 0;
|
|
||||||
this.name = name;
|
|
||||||
|
|
||||||
this.run = function(speed) {
|
|
||||||
this.speed += speed;
|
|
||||||
alert( this.name + ' бежит, скорость ' + this.speed );
|
|
||||||
};
|
|
||||||
|
|
||||||
this.stop = function() {
|
|
||||||
this.speed = 0;
|
|
||||||
alert( this.name + ' стоит' );
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var animal = new Animal('Зверь');
|
|
||||||
|
|
||||||
alert( animal.speed ); // 0, начальная скорость
|
|
||||||
animal.run(3); // Зверь бежит, скорость 3
|
|
||||||
animal.run(10); // Зверь бежит, скорость 13
|
|
||||||
animal.stop(); // Зверь стоит
|
|
||||||
```
|
|
||||||
|
|
||||||
## Класс через прототип
|
|
||||||
|
|
||||||
А теперь создадим аналогичный класс, используя прототипы, наподобие того, как сделаны классы `Object`, `Date` и остальные.
|
|
||||||
|
|
||||||
Чтобы объявить свой класс, нужно:
|
|
||||||
|
|
||||||
1. Объявить функцию-конструктор.
|
|
||||||
2. Записать методы и свойства, нужные всем объектам класса, в `prototype`.
|
|
||||||
|
|
||||||
Опишем класс `Animal`:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
// конструктор
|
|
||||||
function Animal(name) {
|
|
||||||
this.name = name;
|
|
||||||
this.speed = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// методы в прототипе
|
|
||||||
Animal.prototype.run = function(speed) {
|
|
||||||
this.speed += speed;
|
|
||||||
alert( this.name + ' бежит, скорость ' + this.speed );
|
|
||||||
};
|
|
||||||
|
|
||||||
Animal.prototype.stop = function() {
|
|
||||||
this.speed = 0;
|
|
||||||
alert( this.name + ' стоит' );
|
|
||||||
};
|
|
||||||
|
|
||||||
var animal = new Animal('Зверь');
|
|
||||||
|
|
||||||
alert( animal.speed ); // 0, свойство взято из прототипа
|
|
||||||
animal.run(5); // Зверь бежит, скорость 5
|
|
||||||
animal.run(5); // Зверь бежит, скорость 10
|
|
||||||
animal.stop(); // Зверь стоит
|
|
||||||
```
|
|
||||||
|
|
||||||
В объекте `animal` будут храниться свойства конкретного экземпляра: `name` и `speed`, а общие методы -- в прототипе.
|
|
||||||
|
|
||||||
Совершенно такой же подход, как и для встроенных классов в JavaScript.
|
|
||||||
|
|
||||||
## Сравнение
|
|
||||||
|
|
||||||
Чем такое задание класса лучше и хуже функционального стиля?
|
|
||||||
|
|
||||||
```compare
|
|
||||||
+ Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный -- только свойства. Поэтому прототипный стиль -- быстрее и экономнее по памяти.
|
|
||||||
- При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором.
|
|
||||||
```
|
|
||||||
|
|
||||||
Таким образом, прототипный стиль -- быстрее и экономнее, но немного менее удобен.
|
|
||||||
|
|
||||||
К примеру, есть у нас приватное свойство `name` и метод `sayHi` в функциональном стиле ООП:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Animal(name) {
|
|
||||||
this.sayHi = function() {
|
|
||||||
*!*
|
|
||||||
alert( name );
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var animal = new Animal("Зверь");
|
|
||||||
animal.sayHi(); // Зверь
|
|
||||||
```
|
|
||||||
|
|
||||||
При задании методов в прототипе мы не сможем её так оставить, ведь методы находятся *вне* конструктора, у них нет общей области видимости, поэтому приходится записывать `name` в сам объект, обозначив его как защищённое:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
function Animal(name) {
|
|
||||||
*!*
|
|
||||||
this._name = name;
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
Animal.prototype.sayHi = function() {
|
|
||||||
*!*
|
|
||||||
alert( this._name );
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
var animal = new Animal("Зверь");
|
|
||||||
animal.sayHi(); // Зверь
|
|
||||||
```
|
|
||||||
|
|
||||||
Впрочем, недостаток этот -- довольно условный. Ведь при наследовании в функциональном стиле также пришлось бы писать `this._name`, чтобы потомок получил доступ к этому значению.
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 44 KiB |
16
archive/proto/user.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
```js run
|
||||||
|
function User(name, birthday) {
|
||||||
|
let age = calcAge();
|
||||||
|
|
||||||
|
function calcAge() {
|
||||||
|
new Date().getFullYear() - birthday.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sayHi = function() {
|
||||||
|
alert(name + ', age:' + age);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = new User("John", new Date(2000,0,1));
|
||||||
|
user.sayHi(); // John
|
||||||
|
```
|