diff --git a/1-js/9-prototypes/4-classes/7.png b/1-js/9-prototypes/4-classes/7.png deleted file mode 100755 index 9a43f55e..00000000 Binary files a/1-js/9-prototypes/4-classes/7.png and /dev/null differ diff --git a/1-js/9-prototypes/4-classes/7@2x.png b/1-js/9-prototypes/4-classes/7@2x.png deleted file mode 100755 index add9a340..00000000 Binary files a/1-js/9-prototypes/4-classes/7@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/10.png b/1-js/9-prototypes/5-class-inheritance/10.png deleted file mode 100755 index f5fa8c74..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/10.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/10@2x.png b/1-js/9-prototypes/5-class-inheritance/10@2x.png deleted file mode 100755 index 53c247af..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/10@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/11.png b/1-js/9-prototypes/5-class-inheritance/11.png deleted file mode 100755 index 826d0239..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/11.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/11@2x.png b/1-js/9-prototypes/5-class-inheritance/11@2x.png deleted file mode 100755 index 995a70eb..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/11@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/array-object-prototype.png b/1-js/9-prototypes/5-class-inheritance/array-object-prototype.png deleted file mode 100755 index 1c732c4b..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/array-object-prototype.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/array-object-prototype@2x.png b/1-js/9-prototypes/5-class-inheritance/array-object-prototype@2x.png deleted file mode 100755 index 4905d1e2..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/array-object-prototype@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/article.md b/1-js/9-prototypes/5-class-inheritance/article.md index 32995e28..c4d13e09 100644 --- a/1-js/9-prototypes/5-class-inheritance/article.md +++ b/1-js/9-prototypes/5-class-inheritance/article.md @@ -1,25 +1,29 @@ # Наследование классов в JavaScript -*Наследование* -- это когда мы на основе одного объекта создаём другой, который его расширяет: добавляет свои свойства, методы и так далее. - Наследование на уровне объектов в JavaScript, как мы видели, реализуется через ссылку `__proto__`. Теперь поговорим о наследовании на уровне классов, то есть когда объекты, создаваемые, к примеру, через `new Admin`, должны иметь все методы, которые есть у объектов, создаваемых через `new User`, и ещё какие-то свои. + [cut] ## Наследование Array от Object -Примеры организации наследования мы встречаем среди встроенных типов JavaScript. Например, массивы, которые создаются при помощи `new Array` (или квадратных скобок `[...]`), используют методы из своего прототипа `Array.prototype`, а если их там нет -- то методы из родителя `Object.prototype`. +Для реализации наследования в наших классах мы будем использовать тот же подход, который принят внутри JavaScript. -Как JavaScript это организует? +Взглянем на него ещё раз на примере `Array`, который наследует от `Object`: -Очень просто: встроенный `Array.prototype` имеет ссылку `__proto__` на `Object.prototype`: + - + -То есть, как и раньше, методы хранятся в прототипах, а для наследования прототипы организованы в `__proto__`-цепочку. +Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`. -Самый наглядный способ это увидеть -- запустить в консоли команду `console.dir([1,2,3])`. +Если же нужен метод объекта, например, `hasOwnProperty`, то его в `Array.prototype` нет, и он берётся из `Object.prototype`. + +Отличный способ "потрогать это руками" -- запустить в консоли команду `console.dir([1,2,3])`. Вывод в Chrome будет примерно таким: @@ -33,7 +37,7 @@ ## Наследование в наших классах -Применим тот же подход для наших классах. Объявим класс `Rabbit`, который будет наследовать от `Animal`. +Применим тот же подход для наших классов: объявим класс `Rabbit`, который будет наследовать от `Animal`. Вначале создадим два этих класса по отдельности, они пока что будут совершенно независимы. @@ -42,10 +46,9 @@ ```js function Animal(name) { this.name = name; + this.speed = 0; } -Animal.prototype.speed = 0; - Animal.prototype.run = function(speed) { this.speed += speed; alert(this.name + ' бежит, скорость ' + this.speed); @@ -62,6 +65,7 @@ Animal.prototype.stop = function() { ```js function Rabbit(name) { this.name = name; + this.speed = 0; } Rabbit.prototype.jump = function() { @@ -76,15 +80,21 @@ var rabbit = new Rabbit('Кроль'); Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов. -Для этого можно поставить ссылку `__proto__` явно: `Rabbit.prototype.__proto__ = Animal.prototype`. Но это не будет работать в IE10-. +Для этого можно поставить ссылку `__proto__` с `Rabbit.prototype` на `Animal.prototype`. -Поэтому лучше используем функцию `Object.create`, благо она либо встроена либо легко эмулируется во всех браузерах. +Можно сделать это так: +```js +Rabbit.prototype.__proto__ = Animal.prototype; +``` -Класс `Animal` остаётся без изменений, а для `Rabbit` добавим установку `prototype`: +Однако, прямой доступ к `__proto__` не поддерживается в IE10-, поэтому для поддержки этих браузеров мы используем функцию `Object.create`. Она либо встроена либо легко эмулируется во всех браузерах. + +Класс `Animal` остаётся без изменений, а `Rabbit.prototype` мы будем создавать с нужным прототипом, используя `Object.create`: ```js function Rabbit(name) { this.name = name; + this.speed = 0; } *!* @@ -98,7 +108,16 @@ Rabbit.prototype.jump = function() { ... }; Теперь выглядеть иерархия будет так: - + + +В `prototype` по умолчанию всегда находится свойство `constructor`, указывающее на функцию-конструктор. В частности, `Rabbit.prototype.constructor == Rabbit`. Если мы рассчитываем использовать это свойство, то при замене `prototype` через `Object.create` нужно его явно сохранить: + +```js +Rabbit.prototype = Object.create(Animal.prototype); +Rabbit.prototype.constructor = Rabbit; +``` + +## Полный код наследования Для наглядности -- вот итоговый код с двумя классами `Animal` и `Rabbit`: @@ -106,10 +125,10 @@ Rabbit.prototype.jump = function() { ... }; // 1. Конструктор Animal function Animal(name) { this.name = name; + this.speed = 0; } -// 1.1. Методы и свойства по умолчанию -- в прототип -Animal.prototype.speed = 0; +// 1.1. Методы -- в прототип Animal.prototype.stop = function() { this.speed = 0; @@ -125,10 +144,12 @@ Animal.prototype.run = function(speed) { // 2. Конструктор Rabbit function Rabbit(name) { this.name = name; + this.speed = 0; } // 2.1. Наследование Rabbit.prototype = Object.create(Animal.prototype); +Rabbit.prototype.constructor = Rabbit; // 2.2. Методы Rabbit Rabbit.prototype.jump = function() { @@ -141,26 +162,21 @@ Rabbit.prototype.jump = function() { Обратим внимание: `Rabbit.prototype = Object.create(proto)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы. -[warn header="Альтернативный вариант: `Rabbit.prototype = new Animal`"] +[warn header="Неправильный вариант: `Rabbit.prototype = new Animal`"] -Можно унаследовать от `Animal` и по-другому: +В некоторых устаревших руководствах предлагают вместо `Object.create(Animal.prototype)` записывать в прототип `new Animal`, вот так: ```js // вместо Rabbit.prototype = Object.create(Animal.prototype) Rabbit.prototype = new Animal(); ``` -В этом случае мы получаем в прототипе не пустой объект с прототипом `Animal.prototype`, а полноценный `Animal`. -Можно даже сконфигурировать его: +Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь `new Animal` -- это объект с прототипом `Animal.prototype`, как и `Object.create(Animal.prototype)`. Они в этом плане идентичны. -```js -Rabbit.prototype = new Animal("Зверь номер два"); -``` +Но у этого подхода важный недостаток. Как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы! -Теперь новые `Rabbit` будут создаваться на основе конкретного экземпляра `Animal`. Это интересная возможность, но как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы! - -Более того, на практике создание объекта, скажем, меню `new Menu`, может иметь побочные эффекты, показывать что-то посетителю, и так далее, и этого мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`. +Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`. [/warn] ## Вызов конструктора родителя @@ -170,10 +186,12 @@ Rabbit.prototype = new Animal("Зверь номер два"); ```js function Animal(name) { this.name = name; + this.speed = 0; } function Rabbit(name) { this.name = name; + this.speed = 0; } ``` @@ -206,20 +224,10 @@ Rabbit.prototype.run = function(speed) { Вызов `rabbit.run()` теперь будет брать `run` из своего прототипа: - + -[smart] -Кстати, можно назначить метод и на уровне конкретного объекта: -```js -rabbit.run = function() { - alert('Особый метод подпрыгивания для этого кролика'); -}; -``` - -[/smart] - -### Вызов метода родителя после переопределения +### Вызов метода родителя внутри своего Более частая ситуация -- когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает. @@ -230,13 +238,17 @@ rabbit.run = function() { Rabbit.prototype.run = function() { *!* + // вызвать метод родителя, передав ему текущие аргументы Animal.prototype.run.apply(this, arguments); - this.jump(); */!* + this.jump(); } ``` -Обратите внимание на `apply` и явное указание контекста. Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект. +Обратите внимание на вызов через `apply` и явное указание контекста. + +Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект. + ## Итого @@ -277,44 +289,46 @@ Rabbit.prototype.run = function() { ```js //+ run -// --------- *!*Класс-Родитель*/!* ------------ -// Конструктор родителя +*!* +// --------- Класс-Родитель ------------ +*/!* +// Конструктор родителя пишет свойства конкретного объекта function Animal(name) { this.name = name; + this.speed = 0; } -// Методы родителя -Animal.prototype.run= function() { +// Методы хранятся в прототипе +Animal.prototype.run = function() { alert(this + " бежит!") } -Animal.prototype.toString = function() { - return this.name; -} - -// --------- *!*Класс-потомок*/!* ----------- +*!* +// --------- Класс-потомок ----------- +*/!* // Конструктор потомка function Rabbit(name) { Animal.apply(this, arguments); } -// *!*Унаследовать*/!* +// Унаследовать *!* Rabbit.prototype = Object.create(Animal.prototype); */!* +// Желательно и constructor сохранить +Rabbit.prototype.constructor = Rabbit; + // Методы потомка Rabbit.prototype.run = function() { - Animal.prototype.run.apply(this); // метод родителя вызвать + // Вызов метода родителя внутри своего + Animal.prototype.run.apply(this); alert(this + " подпрыгивает!"); }; +// Готово, можно создавать объекты var rabbit = new Rabbit('Кроль'); -rabbit.run(); +rabbit.run(); ``` - diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.svg b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.svg new file mode 100644 index 00000000..96e1a15d --- /dev/null +++ b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.svg @@ -0,0 +1,50 @@ + + + + class-inheritance-array-object + Created with Sketch. + + + + + + + slice: function + ... + + + Array.prototype + + + arr + + + + + + hasOwnProperty: function + ... + + + Object.prototype + + + + + + + + + __proto__ + + + + + __proto__ + + + [1, 2, 3] + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.svg new file mode 100644 index 00000000..15b609ee --- /dev/null +++ b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.svg @@ -0,0 +1,50 @@ + + + + class-inheritance-rabbit-animal + Created with Sketch. + + + + + + + jump: function + + + Rabbit.prototype + + + rabbit + + + + + + run: function + stop: function + + + Animal.prototype + + + + + + + + + __proto__ + + + + + __proto__ + + + name: "Кроль" + speed: 0 + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.svg b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.svg new file mode 100644 index 00000000..6baf41fd --- /dev/null +++ b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.svg @@ -0,0 +1,51 @@ + + + + class-inheritance-rabbit-run-animal + Created with Sketch. + + + + + + + jump: function + run: function + + + Rabbit.prototype + + + rabbit + + + + + + run: function + stop: function + + + Animal.prototype + + + + + + + + + __proto__ + + + + + __proto__ + + + name: "Кроль" + speed: 0 + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/6-instanceof/article.md b/1-js/9-prototypes/6-instanceof/article.md index 827ad460..dff06ccd 100644 --- a/1-js/9-prototypes/6-instanceof/article.md +++ b/1-js/9-prototypes/6-instanceof/article.md @@ -1,18 +1,14 @@ # Проверка класса: "instanceof" Оператор `instanceof` позволяет проверить, какому классу принадлежит объект, с учетом прототипного наследования. -[cut] +[cut] ## Алгоритм работы instanceof [#ref-instanceof] Вызов `obj instanceof Constructor` возвращает `true`, если объект принадлежит классу `Constructor` или его родителям. -Как это часто бывает в JavaScript, здесь есть ряд тонкостей. В некоторых ситуациях, проверка может даже ошибаться! - -Вначале -- простейший пример. - -Вот так, по замыслу, должен работать `instanceof`: +Пример использования: ```js //+ run @@ -21,15 +17,15 @@ function Rabbit() { } *!* // создаём объект */!* -var rabbit = new Rabbit; +var rabbit = new Rabbit(); -*!* // проверяем -- этот объект создан Rabbit? +*!* alert(rabbit instanceof Rabbit); // true, верно */!* ``` -Для встроенных объектов: +Массив `arr` принадлежит классу `Array`, но также и является объектом `Object`. Это верно, так как массивы наследуют от объектов: ```js //+ run @@ -38,9 +34,7 @@ alert(arr instanceof Array); // true alert(arr instanceof Object); // true ``` -Массив `arr` принадлежит классу `Array`, но также и является объектом `Object`. Это верно, так как массивы наследуют от объектов. - -По этой же логике вызов `rabbit instanceof Object` -- тоже будет `true`, так как `rabbit` является объектом. +Как это часто бывает в JavaScript, здесь есть ряд тонкостей. В некоторых ситуациях, проверка может даже ошибаться! **Алгоритм проверки `obj instanceof Constructor`:** @@ -50,42 +44,11 @@ alert(arr instanceof Object); // true
  • Если не совпадает, тогда заменить `obj` на `obj.__proto__` и повторить проверку на шаге 2 до тех пор, пока либо не найдется совпадение (результат `true`), либо цепочка прототипов не закончится (результат `false`).
  • -В проверки `rabbit instanceof Rabbit`, совпадение находится тут же, на первом же шаге, так как: `rabbit.__proto__ == Rabbit.prototype`. +В проверке `rabbit instanceof Rabbit`, совпадение на первом же шаге этго алгоритма, так как: `rabbit.__proto__ == Rabbit.prototype`. -А если рассмотреть `rabbit instanceof Rabbit`, то совпадение будет найдено на следующем шаге, т.к. `rabbit.__proto__.__proto__ == Object.prototype`. +А если рассмотреть `arr instanceof Object`, то совпадение будет найдено на следующем шаге, так как `arr.__proto__.__proto__ == Object.prototype`. -[smart header="Примитивы -- не объекты, доказательство"] -Проверим, является ли число объектом `Number`: - -```js -//+ run -alert(123 instanceof Number); // false, нет! -``` - -...С другой стороны, если создать встроенный объект `Number` (не делайте так): - -```js -//+ run -alert( new Number(123) instanceof Number ); // true -``` - -[/smart] - -[warn header="Не друзья: `instanceof` и фреймы"] - -**Оператор `instanceof` не срабатывает, когда значение приходит из другого окна или фрейма.** - -Например, массив, который создан в ифрейме и передан родительскому окну -- будет массивом *в том ифрейме*, но не в родительском окне. Проверка `instanceof Array` в родительском окне вернёт `false`. - -Вообще, у каждого окна и фрейма -- своя иерархия объектов и свой `window` . - -Как правило, эта проблема возникает со встроенными объектами, в этом случае используется проверка внутреннего свойства `[[Class]]`. Более подробно это описано в главе [](/class-property). -[/warn] - -[smart header="Вызов `instanceof Rabbit` не использует саму функцию `Rabbit`"] -Забавно, что сама функция-констуктор не участвует в процессе проверки! - -Используется только цепочка прототипов для проверяемого объекта и `Rabbit.prototype`. +Забавно, что сама функция-констуктор не участвует в процессе проверки! Важна только цепочка прототипов для проверяемого объекта. Это может приводить к забавному результату и даже ошибкам в проверке при изменении `prototype`, например: @@ -104,8 +67,19 @@ alert( rabbit instanceof Rabbit ); // false */!* ``` -Стоит ли говорить, что это один из доводов для того, чтобы никогда не менять `prototype`? Так сказать, во избежание... -[/smart] +Стоит ли говорить, что это один из доводов для того, чтобы никогда не менять `prototype`? Так сказать, во избежание. + +[warn header="Не друзья: `instanceof` и фреймы"] + +Оператор `instanceof` не срабатывает, когда значение приходит из другого окна или фрейма. + +Например, массив, который создан в ифрейме и передан родительскому окну -- будет массивом *в том ифрейме*, но не в родительском окне. Проверка `instanceof Array` в родительском окне вернёт `false`. + +Вообще, у каждого окна и фрейма -- своя иерархия объектов и свой `window` . + +Как правило, эта проблема возникает со встроенными объектами, в этом случае используется проверка внутреннего свойства `[[Class]]`. Более подробно это описано в главе [](/class-property). +[/warn] + ## instanceof + наследование + try..catch = ♡ @@ -232,9 +206,7 @@ try { ## Итого - -