diff --git a/1-js/9-prototypes/1-prototype/1.png b/1-js/9-prototypes/1-prototype/1.png deleted file mode 100755 index 85778ffc..00000000 Binary files a/1-js/9-prototypes/1-prototype/1.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/1@2x.png b/1-js/9-prototypes/1-prototype/1@2x.png deleted file mode 100755 index 3439b25e..00000000 Binary files a/1-js/9-prototypes/1-prototype/1@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/article.md b/1-js/9-prototypes/1-prototype/article.md index f0e1be00..a1871959 100644 --- a/1-js/9-prototypes/1-prototype/article.md +++ b/1-js/9-prototypes/1-prototype/article.md @@ -13,7 +13,6 @@ Пример кода (кроме IE10-): -
```js //+ run var animal = { eats: true }; @@ -27,8 +26,6 @@ rabbit.__proto__ = animal; alert(rabbit.jumps); // true alert(rabbit.eats); // true ``` -
-
  1. Первый `alert` здесь работает очевидным образом -- он выводит свойство `jumps` объекта `rabbit`.
  2. @@ -37,18 +34,7 @@ alert(rabbit.eats); // true Иллюстрация происходящего при чтении `rabbit.eats` (поиск идет снизу вверх): -```js -var animal = { - eats: true -}; - -var rabbit = { - jumps: true -}; -``` - - - + **Объект, на который указывает ссылка `__proto__`, называется *"прототипом"*. В данном случае получилось, что `animal` является прототипом для `rabbit`.** @@ -72,38 +58,16 @@ alert(rabbit.eats); // false, свойство взято из rabbit **Другими словами, прототип -- это "резервное хранилище свойств и методов" объекта, автоматически используемое при поиске.** +У объекта, который является `__proto__`, может быть свой `__proto__`, у того -- свой, и так далее. При этом свойства будут искаться по цепочке. + [smart header="Ссылка __proto__ в спецификации"] Если вы будете читать спецификацию EcmaScript -- свойство `__proto__` обозначено в ней как `[[Prototype]]`. Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется `prototype`, и которое мы рассмотрим позже. [/smart] - -## Цепочка прототипов - -У объекта, который является `__proto__`, может быть свой `__proto__`, у того -- свой, и так далее. - -Например, цепочка наследования из трех объектов `donkey -> winnie -> owl`: - -```js -//+ run -var donkey = { /* ... */ }; -var winnie = { /* ... */ }; -var owl = { knowsAll: true }; - -donkey.__proto__ = winnie; -winnie.__proto__ = owl; - -*!* -alert( donkey.knowsAll ); // true -*/!* -``` - -Картина происходящего: - - -## Перебор свойств без прототипа +## Метод hasOwnProperty Обычный цикл `for..in` не делает различия между свойствами объекта и его прототипа. @@ -172,28 +136,28 @@ for (var key in rabbit) { } ``` + ## Методы для работы с __proto__ В современных браузерах есть два дополнительных метода для работы с `__proto__`. Зачем они нужны, если есть `__proto__`? В общем-то, не очень нужны, но по историческим причинам тоже существуют.
    -
    [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)
    +
    Чтение: [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)
    Возвращает `obj.__proto__` (кроме IE8-)
    -
    [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)
    +
    Запись: [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)
    Устанавливает `obj.__proto__ = proto` (кроме IE10-).
    Кроме того, есть ещё один вспомогательный метод: -
    [Object.create(proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create)
    -
    Создаёт пустой объект с `__proto__`, равным первому аргументу (кроме IE8-).
    +
    Создание объекта с прототипом: [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create)
    +
    Создаёт пустой объект с `__proto__`, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать [дескрипторы свойств](/descriptors-getters-setters).
    -Метод `Object.create` -- несколько более мощный, чем здесь описано, у него есть необязательный второй аргумент, который позволяет также задать другие свойства объекта, но используется он редко и пока что нам не нужен. Мы рассмотрим его позже, в главе [](/descriptors-getters-setters). - ## Итого + @@ -205,12 +169,12 @@ for (var key in rabbit) { -Возможно, вас смущает недостаточная поддержка `__proto__` в старых IE. Но это временно. В последующих главах мы рассмотрим дополнительные методы работы с `__proto__`, включая те, которые работают везде. +Возможно, вас смущает недостаточная поддержка `__proto__` в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с `__proto__`, включая те, которые работают везде. -Также мы рассмотрим, как свойство `__proto__` используется внутри самого языка JavaScript. +Также мы рассмотрим, как свойство `__proto__` используется внутри самого языка JavaScript и как организовать классы с его помощью. diff --git a/1-js/9-prototypes/1-prototype/donkey_winnie_owl.png b/1-js/9-prototypes/1-prototype/donkey_winnie_owl.png deleted file mode 100755 index 82352906..00000000 Binary files a/1-js/9-prototypes/1-prototype/donkey_winnie_owl.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/donkey_winnie_owl@2x.png b/1-js/9-prototypes/1-prototype/donkey_winnie_owl@2x.png deleted file mode 100755 index 3a3d1ca3..00000000 Binary files a/1-js/9-prototypes/1-prototype/donkey_winnie_owl@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.svg b/1-js/9-prototypes/1-prototype/proto-animal-rabbit.svg index 719fbf26..a3bfc80b 100644 --- a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.svg +++ b/1-js/9-prototypes/1-prototype/proto-animal-rabbit.svg @@ -1,22 +1,34 @@ - + proto-animal-rabbit Created with Sketch. - - - - - - + + + + + eats: true + + + animal + - - - - + + + + jumps: true + + + rabbit + + + + + __proto__ + \ No newline at end of file diff --git a/1-js/9-prototypes/6-constructor/1-new-object-same-constructor/solution.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md similarity index 72% rename from 1-js/9-prototypes/6-constructor/1-new-object-same-constructor/solution.md rename to 1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md index 9f345069..ffaea872 100644 --- a/1-js/9-prototypes/6-constructor/1-new-object-same-constructor/solution.md +++ b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md @@ -16,4 +16,19 @@ alert(obj2.name); // Петя (сработало) Сработало, так как `User.prototype.constructor == User`. -Но если кто-то, к примеру, перезапишет `User.prototype` и забудет указать `constructor`, то такой фокус не пройдёт. \ No newline at end of file +Но если кто-то, к примеру, перезапишет `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 +``` \ No newline at end of file diff --git a/1-js/9-prototypes/6-constructor/1-new-object-same-constructor/task.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md similarity index 71% rename from 1-js/9-prototypes/6-constructor/1-new-object-same-constructor/task.md rename to 1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md index 12e9beb0..99cb4e90 100644 --- a/1-js/9-prototypes/6-constructor/1-new-object-same-constructor/task.md +++ b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md @@ -10,4 +10,4 @@ var obj2 = new obj.constructor(); ``` -В каком случае такой код будет работать, а в каком -- нет? +Приведите пример конструкторов для `obj`, при которых такой код будет работать верно -- и неверно. diff --git a/1-js/9-prototypes/2-new-prototype/article.md b/1-js/9-prototypes/2-new-prototype/article.md index f9e80be1..95f41b1d 100644 --- a/1-js/9-prototypes/2-new-prototype/article.md +++ b/1-js/9-prototypes/2-new-prototype/article.md @@ -2,7 +2,7 @@ До этого момента мы говорили о наследовании объектов, объявленных через `{...}`. -Но что, если объекты создаются функцией-конструктором через `new`? Как указать прототип в этом случае? +Но в реальных проектах объекты обычно создаются функцией-конструктором через `new`. Посмотрим, как указать прототип в этом случае. [cut] ## Свойство F.prototype @@ -13,7 +13,7 @@ ```js //+ run -var animal = { eats: true } +var animal = { eats: true }; function Rabbit(name) { this.name = name; @@ -57,9 +57,9 @@ alert( rabbit.eats ); // true Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: *"При создании объекта через `new Rabbit` запиши ему `__proto__ = animal`".* [smart header="Свойство `prototype` имеет смысл только у конструктора"] -Свойство `prototype` можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору. +Свойство с именем `prototype` можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору. -Само по себе оно вообще ничего не делает, его единственное назначение -- ставить `__proto__` новым объектам. +Само по себе, без вызова оператора `new`, оно вообще ничего не делает, его единственное назначение -- указывать `__proto__` для новых объектов. [/smart] @@ -70,15 +70,81 @@ alert( rabbit.eats ); // true Однако, при работе `new`, свойство `prototype` будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано. [/warn] +## Свойство 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 +``` +[/warn] + + ## Эмуляция Object.create для IE8- [#inherit] Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи `F.prototype`. -Теперь вернёмся к созданию объектов без конструктора. +Теперь небольшое "лирическое отступление" в область совместимости. -Мы знаем, что в этом случае можно указывать прототип при помощи `__proto__`, но это не работает в IE10-. Также мы знаем, что есть метод `Object.create(proto)`, который создаёт пустой объект с данным прототипом, но он не работает в IE8-. - -**Используя `prototype`, вызов `Object.create` можно легко эмулировать, так что он будет работать во всех браузерах, включая даже очень-очень старые.** +Прямые методы работы с прототипом осутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах. Кросс-браузерный аналог -- назовём его `inherit`, состоит буквально из нескольких строк: @@ -91,7 +157,7 @@ function inherit(proto) { } ``` -Результат вызова `inherit(animal)` идентичен `Object.create(animal)`. Это будет новый пустой объект с прототипом `animal`. +Результат вызова `inherit(animal)` идентичен `Object.create(animal)`. Она создаёт новый пустой объект с прототипом `animal`. Например: @@ -106,7 +172,7 @@ alert(rabbit.eats); // true Посмотрите внимательно на функцию `inherit` и вы, наверняка, сами поймёте, как она работает... -Давайте, на всякий случай, пройдём её по шагам: +Если где-то неясности, то её построчное описание: ```js function inherit(proto) { @@ -124,10 +190,7 @@ function inherit(proto) {
  3. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
- -Эта функция широко используется в библиотеках и фреймворках. - -Здесь и далее мы будем использовать `Object.create`, предполагая что для IE8- выполнен код: +Для унификации можно запустить такой код, и метод `Object.create` станет кросс-браузерным: ```js if (!Object.create) Object.create = inherit; /* определение inherit - выше */ @@ -135,12 +198,15 @@ if (!Object.create) Object.create = inherit; /* определение inherit - В частности, аналогичным образом работает библиотека [es5-shim](https://github.com/es-shims/es5-shim), при подключении которой `Object.create` станет доступен для всех браузеров. + ## Итого +Для произвольной функции -- назовём её `Constructor`, верно следующее: + diff --git a/1-js/9-prototypes/3-native-prototypes/5.png b/1-js/9-prototypes/3-native-prototypes/5.png deleted file mode 100755 index f4202de0..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/5.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/5@2x.png b/1-js/9-prototypes/3-native-prototypes/5@2x.png deleted file mode 100755 index 9adfa463..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/5@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/6.png b/1-js/9-prototypes/3-native-prototypes/6.png deleted file mode 100755 index a4ab88fd..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/6.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/6@2x.png b/1-js/9-prototypes/3-native-prototypes/6@2x.png deleted file mode 100755 index 6ac1ba26..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/6@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/article.md b/1-js/9-prototypes/3-native-prototypes/article.md index 590286ec..086c4219 100644 --- a/1-js/9-prototypes/3-native-prototypes/article.md +++ b/1-js/9-prototypes/3-native-prototypes/article.md @@ -14,19 +14,26 @@ var obj = { }; alert( obj ); // "[object Object]" ? ``` -В объекте, очевидно, ничего нет... Но кто же тогда генерирует строковое представление для `alert(obj)`? +Где код, который генерирует строковое представление для `alert(obj)`? Объект-то ведь пустой. ## Object.prototype -...Конечно же, это сделал метод `toString`, который находится во встроенном прототипе `Object.prototype`. Этот прототип ставится всем объектам `Object` при создании и содержит все встроенные методы и свойства для объектов. - -В деталях, работает это так: +...Конечно же, это сделал метод `toString`, который находится... Конечно, не в самом объекте (он пуст), а в его прототипе `obj.__proto__`, можно его даже вывести: + +```js +//+ run +alert( {}.__proto__.toString ); // function toString +``` + +Откуда новый объект `obj` получает такой `__proto__`? +
  1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов.
  2. -
  3. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, то есть в данном случае `Object.prototype`.
  4. +
  5. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`.
  6. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`.
- + + Это можно легко проверить: @@ -39,26 +46,32 @@ 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` и т.п. - + -Как видно из картинки, `Array.prototype` в свою очередь имеет прототипом `Object.prototype`, поэтому если метода нет у массива, то он ищется в объекте. +Например, когда мы создаём массив, `[1, 2, 3]`, то это альтернативный вариант синтаксиса `new Array`, так что у массивов есть стандартный прототип `Array.prototype`. -Например, при вызове `arr.hasOwnProperty(...)` для массива `arr` метод `hasOwnProperty` берётся из `Object.prototype`. +Но в нём есть методы лишь для массивов, а для общих методов всех объектов есть ссылка `Array.prototype.__proto__`, равная `Object.prototype`. -Получается иерархия наследования, которая всегда заканчивается на `Object.prototype`. Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`. +Аналогично, для функций. + +Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее. + +Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`. **Поэтому говорят, что "все объекты наследуют от `Object`", а если более точно, то от `Object.prototype`.** -**"Псевдоклассом" или, более коротко, "классом", называют функцию-конструктор вместе с её `prototype`.** +"Псевдоклассом" или, более коротко, "классом", называют функцию-конструктор вместе с её `prototype`. Такой способ объявления классов называют "прототипным стилем ООП". -[smart header="Переопределение методов в наследниках"] -**При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который находится в `Array.prototype.toString`:** +При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который выводит элементы массива через запятую: ```js //+ run @@ -66,12 +79,14 @@ var arr = [1, 2, 3] alert( arr ); // 1,2,3 <-- результат Array.prototype.toString ``` -Для вывода объекта JavaScript ищет `toString` сначала в самом объекте `arr`, затем в `arr.__proto__`, который равен `Array.prototype`. +Как мы видели раньше, у `Object.prototype` есть свой `toString`, но так как в `Array.prototype` он ищется первым, то берётся именно вариант для массивов: -Конечно, если бы его там не было -- поиск пошёл бы выше в `Array.prototype.__proto__`, который по стандарту (см. диаграмму выше) равен `Object.prototype`, и тогда использовался бы стандартный метод для объектов. -[/smart] + -Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для произвольных объектов, имеющих нумерованные свойства и `length`: + +[smart header="Вызов методов через `apply` из прототипа"] + +Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для `arguments`: ```js //+ run @@ -97,14 +112,16 @@ function showList() { showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша ``` -Это лучше, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- так больше писать. - +Это эффективнее, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- больше букв писать. +[/smart] ## Примитивы Примитивы не являются объектами, но методы берут из соответствующих прототипов: `Number.prototype`, `Boolean.prototype`, `String.prototype`. -По стандарту, если обратиться к свойству примитива, то будет создан объект соответствующего типа, например `new String`, произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен. +По стандарту, если обратиться к свойству числа, строки или логического значения, то будет создан объект соответствующего типа, например `new String` для строки, `new Number` для чисел, `new Boolean` -- для логических выражений. + +Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен. Именно так работает код ниже: @@ -114,17 +131,17 @@ var user = "Вася"; // создали строку (примитив) *!* alert( user.toUpperCase() ); // ВАСЯ -// создан временный объект new String +// был создан временный объект new String // вызван метод // new String уничтожен, результат возвращён */!* ``` -**Принципиальное отличие от объектов -- в примитив нельзя записать свойство.** +Можно даже попробовать записать в этот временный объект свойство: ```js //+ run -// а теперь попытаемся записать свойство в строку: +// попытаемся записать свойство в строку: var user = "Вася"; user.age = 30; @@ -133,7 +150,7 @@ alert(user.age); // undefined */!* ``` -Свойство `age` было записано во временный объект, который был тут же уничтожен. +Свойство `age` было записано во временный объект, который был тут же уничтожен, так что смысла в такой записи немного. [warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования"] Технически, можно создавать объекты для примитивов и вручную, например `new Number`. Но в ряде случаев получится откровенно бредовое поведение. Например: @@ -163,7 +180,6 @@ if (zero) { // объект - true, так что alert выполнится Значения `null` и `undefined` стоят особняком. Вышесказанное к ним не относится. Для них нет соответствующих классов, в них нельзя записать свойство (будет ошибка), в общем, на конкурсе "самое примитивное значение" они точно разделили бы первое место. - [/warn] @@ -194,16 +210,16 @@ Object.prototype.each = function(f) { } // Попробуем! (внимание, пока что это работает неверно!) -var obj = { name: 'Вася', age: 25 }; +var user = { name: 'Вася', age: 25 }; -obj.each(function(prop, val) { +user.each(function(prop, val) { alert(prop); // name -> age -> (!) each }); ``` -Обратите внимание -- пример выше работает неправильно. Он выводит лишнее свойство `each`, т.к. цикл `for..in` перебирает свойства в прототипе. Встроенные методы при этом пропускаются, а наш метод -- вылез. +Обратите внимание -- пример выше работает не совсем корректно. Вместе со свойствами объекта `user` он выводит и наше свойство `each`. Технически, это правильно, так как цикл `for..in` перебирает свойства и в прототипе тоже, но не очень удобно. -В данном случае это легко поправить добавлением проверки `hasOwnProperty`: +Конечно, это легко поправить добавлением проверки `hasOwnProperty`: ```js //+ run @@ -212,6 +228,7 @@ Object.prototype.each = function(f) { for (var prop in this) { *!* + // пропускать свойства из прототипа if (!this.hasOwnProperty(prop)) continue; */!* @@ -230,28 +247,35 @@ obj.each(function(prop, val) { }); ``` -Здесь это сработало, теперь код работает верно. Но мы же не хотим добавлять `hasOwnProperty` в цикл по любому объекту! Поэтому... +Здесь это сработало, теперь код работает верно. Но мы же не хотим добавлять `hasOwnProperty` в цикл по любому объекту! Поэтому либо не добавляйте свойства в `Object.prototype`, либо можно использовать [дескриптор свойства](/descriptors-getters-setters) и флаг `enumerable`. +Это, конечно, не будет работать в IE8-: +```js +//+ run +Object.prototype.each = function(f) { -[warn header="Не добавляйте свойства в `Object.prototype`"] + for (var prop in this) { + var value = this[prop]; + f.call(value, prop, value); + } -Свойства, добавленные в `Object.prototype`, появятся во всех `for..in` циклах. Они в них будут лишними. +}; -[/warn] +*!* +// поправить объявление свойства, установив флаг enumerable: false +Object.defineProperty(Object.prototype, 'each', { enumerable: false }); +*/!* +// Теперь все будет в порядке +var obj = { name: 'Вася', age: 25 }; +obj.each(function(prop, val) { + alert(prop); // name -> age +}); +``` -[smart header="Современный стандарт и `for..in`"] - -Встроенные свойства и методы не перебираются в `for..in`, так как у них есть специальный внутренний флаг `[[Enumerable]]`, установленный в `false`. - -Современные браузеры (включая IE с версии 9) позволяют устанавливать этот флаг для любых свойств, используя специальные вызовы, описанные в главе [](/descriptors-getters-setters). При таком добавлении предупреждение станет неактуальным, так как они тоже не будут видны в `for..in`. -[/smart] - -**Многие объекты не участвуют в циклах `for..in`, например строки, функции... С ними уж точно нет такой проблемы, и в их прототипы, пожалуй, можно добавлять свои методы.** - -Но здесь есть свои "за" и "против": +Есть несколько "за" и "против" модификации встроенных прототипов: [compare] +Методы в прототипе автоматически доступны везде, их вызов прост и красив. @@ -259,7 +283,7 @@ obj.each(function(prop, val) { -Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения. [/compare] -С другой стороны, есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются. +Как правило, минусы весомее, но есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются. **Допустимо изменение прототипа встроенных объектов, которое добавляет поддержку метода из современных стандартов в те браузеры, где её пока нет.** diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototype-object.svg b/1-js/9-prototypes/3-native-prototypes/native-prototype-object.svg new file mode 100644 index 00000000..83e59c12 --- /dev/null +++ b/1-js/9-prototypes/3-native-prototypes/native-prototype-object.svg @@ -0,0 +1,40 @@ + + + + native-prototype-object + Created with Sketch. + + + + + + + toString: function + другие методы объектов + + + Object.prototype + + + + + + obj + + + + + + __proto__ + + + + + __proto__ + + + null + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.svg b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.svg new file mode 100644 index 00000000..091dcd45 --- /dev/null +++ b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.svg @@ -0,0 +1,48 @@ + + + + native-prototypes-array-tostring + Created with Sketch. + + + + + + + toString: function + ... + + + Array.prototype + + + + + + toString: function + ... + + + Object.prototype + + + + + + + + + __proto__ + + + + + __proto__ + + + + [1, 2, 3] + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.svg b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.svg new file mode 100644 index 00000000..24cd6d42 --- /dev/null +++ b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.svg @@ -0,0 +1,103 @@ + + + + native-prototypes-classes + Created with Sketch. + + + + + + + toString: function + другие методы объектов + + + Object.prototype + + + + + + __proto__ + + + + + null + + + + + slice: function + другие методы массивов + + + __proto__ + + + Array.prototype + + + + + + __proto__ + + + apply: function + другие методы функций + + + Function.prototype + + + + + + toFixed: function + другие методы чисел + + + Number.prototype + + + __proto__ + + + + + + + + + [1, 2, 3] + + + + function f(args) { + ... + } + + + + 5 + + + + + __proto__ + + + + + __proto__ + + + + + __proto__ + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.svg b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.svg new file mode 100644 index 00000000..88c49a86 --- /dev/null +++ b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.svg @@ -0,0 +1,40 @@ + + + + native-prototypes-object + Created with Sketch. + + + + + + + toString: function + другие методы объектов + + + Object.prototype + + + + + + obj + + + + + + __proto__ + + + + + __proto__ + + + null + + + + \ No newline at end of file diff --git a/1-js/9-prototypes/4-classes/article.md b/1-js/9-prototypes/4-classes/article.md index cc6da884..03186252 100644 --- a/1-js/9-prototypes/4-classes/article.md +++ b/1-js/9-prototypes/4-classes/article.md @@ -5,9 +5,9 @@ [cut] ## Обычный конструктор -Вспомним, как мы объявляли конструкторы ранее. +Вспомним, как мы объявляли классы ранее. -Например, этот код задаёт объект `Animal` без всяких прототипов: +Например, этот код задаёт класс `Animal` в функциональном стиле, без всяких прототипов: ```js //+ run @@ -38,7 +38,8 @@ animal.stop(); // Зверь стоит А теперь создадим аналогичный класс, используя прототипы, наподобие того, как сделаны классы `Object`, `Date` и остальные. -**Чтобы объявить свой класс, нужно:** +Чтобы объявить свой класс, нужно: +
  1. Объявить функцию-конструктор.
  2. Записать методы и свойства, нужные всем объектам класса, в `prototype`.
  3. @@ -51,6 +52,7 @@ animal.stop(); // Зверь стоит // конструктор function Animal(name) { this.name = name; + this.speed = 0; } // методы в прототипе @@ -64,9 +66,6 @@ Animal.prototype.stop = function() { alert(this.name + ' стоит'); }; -// свойство speed со значением "по умолчанию" -Animal.prototype.speed = 0; - var animal = new Animal('Зверь'); alert(animal.speed); // 0, свойство взято из прототипа @@ -75,23 +74,21 @@ animal.run(5); // Зверь бежит, скорость 10 animal.stop(); // Зверь стоит ``` -Здесь объекту `animal` принадлежит лишь свойство `name`, а остальное находится в прототипе. +В объекте `animal` будут хранится свойства конкретного экземпляра: `name` и `speed`, а общие методы -- в прототипе. -Обратим внимание, значение `speed` по умолчанию тоже перенесено в прототип, ведь оно во всех объектах (в начале) одинаково, но вызовы `animal.run()`, `animal.stop()` в примере используют вызов `this.speed += speed`, а любая запись в `this.свойство` работает с самим объектом. - -То есть, начальное значение `speed` берётся из прототипа, а новое -- пишется уже в сам объект. И в дальнейшем используется. - - +Совершенно такой же подход, как и для встроенных классов в JavaScript. ## Сравнение -Чем такое задание класса лучше и хуже предыдущего? +Чем такое задание класса лучше и хуже функционального стиля? [compare] -+Прототип позволяет хранить общие свойства и методы в единственном экземпляре, таким образом экономится память и создание объекта происходит быстрее, чем если записывать их в каждый `this`. --При объявлении класса через прототип, мы теряем возможность использовать локальные переменные и функции. ++Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный -- только свойства. Поэтому прототипный стиль -- быстрее и экономнее по памяти. +-При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором. [/compare] +Таким образом, прототипный стиль -- быстрее и экономнее, но немного менее удобен. + К примеру, есть у нас приватное свойство `name` и метод `sayHi` в функциональном стиле ООП: ```js @@ -128,11 +125,4 @@ var animal = new Animal("Зверь"); animal.sayHi(); // Зверь ``` -Ранее в каждый объект `Animal` записывалась своя функция `this.sayHi`, а теперь есть одна функция такого рода в прототипе. В сам объект мы пишем только то, что свойственно именно этому объекту. - -## Задачи - -Обычно свойства по умолчанию хранятся в прототипе. Но если свойство по умолчанию -- объект, то его в прототипе хранить нельзя. - -Почему? Смотрите задачу ниже на эту тему. - +Впрочем, недостаток этот -- довольно условный. Ведь при наследовании в функциональном стиле также пришлось бы писать `this._name`, чтобы потомок получил доступ к этому значению. diff --git a/1-js/9-prototypes/6-constructor/2-constructor-inherited/solution.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md similarity index 100% rename from 1-js/9-prototypes/6-constructor/2-constructor-inherited/solution.md rename to 1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md diff --git a/1-js/9-prototypes/6-constructor/2-constructor-inherited/task.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md similarity index 100% rename from 1-js/9-prototypes/6-constructor/2-constructor-inherited/task.md rename to 1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md diff --git a/1-js/9-prototypes/6-constructor/8.png b/1-js/9-prototypes/6-constructor/8.png deleted file mode 100755 index b875db60..00000000 Binary files a/1-js/9-prototypes/6-constructor/8.png and /dev/null differ diff --git a/1-js/9-prototypes/6-constructor/8@2x.png b/1-js/9-prototypes/6-constructor/8@2x.png deleted file mode 100755 index fc77fd69..00000000 Binary files a/1-js/9-prototypes/6-constructor/8@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/6-constructor/9.png b/1-js/9-prototypes/6-constructor/9.png deleted file mode 100755 index 666fd19c..00000000 Binary files a/1-js/9-prototypes/6-constructor/9.png and /dev/null differ diff --git a/1-js/9-prototypes/6-constructor/9@2x.png b/1-js/9-prototypes/6-constructor/9@2x.png deleted file mode 100755 index 3c193118..00000000 Binary files a/1-js/9-prototypes/6-constructor/9@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/6-constructor/article.md b/1-js/9-prototypes/6-constructor/article.md deleted file mode 100644 index 3cc088ac..00000000 --- a/1-js/9-prototypes/6-constructor/article.md +++ /dev/null @@ -1,141 +0,0 @@ -# F.prototype по умолчанию, "constructor" - -В стандарте JavaScript есть свойство `constructor`, которое, по идее, должно быть в каждом объекте и указывать, какой функцией он создан. - -Однако на практике оно ведет себя не всегда адекватным образом. Мы рассмотрим, как оно работает и почему именно так, а также -- как сделать, чтобы оно работало правильно. -[cut] - -## Свойство constructor [#constructor] - -По замыслу, свойство `constructor` объекта должно содержать ссылку на функцию, создавшую объект. И в простейших случаях так оно и есть, вот например: - -```js -//+ run -function Rabbit() { } - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.constructor == Rabbit ); // true -*/!* -``` - -Как видим, всё работает. Мы получили из объекта функцию, которая его создала. - -Но всё не так просто. Расширим наш пример установкой `Rabbit.prototype`: - -```js -//+ run -function Rabbit() { } - -Rabbit.prototype = { jumps: true } ; - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.constructor == Rabbit ); // false (упс, потеряли конструктор!) -*/!* -``` - -...Сломалось! Чтобы детальнее понять происходящее -- посмотрим, откуда берется свойство `constructor` и что с ним произошло. - -## Значение prototype по умолчанию - -До этого мы записывали и меняли свойство `prototype`, но оказывается, оно существует и без нашего вмешательства. - -**Свойство `prototype` есть у каждой функции, даже если его не ставить.** - -**Оно создается автоматически вместе с самой функцией, и по умолчанию является пустым объектом с единственным свойством `constructor`, которое ссылается обратно на функцию.** - -Вот такой вид имеет прототип по умолчанию: - -```js -Rabbit.prototype = { - constructor: Rabbit -} -``` - - - -Так что объект `rabbit` не хранит в себе свойство `constructor`, а берёт его из прототипа. - -Сделаем прямую проверку этого: - -```js -//+ run -function Rabbit() { } - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.hasOwnProperty('constructor') ); // false, в объекте нет! - -alert( Rabbit.prototype.hasOwnProperty('constructor') ); // true, да, оно в прототипе -*/!* -``` - -## Потеря constructor - -JavaScript не прилагает никаких усилий для проверки корректности или поддержания правильного значения `constructor`. - -**При замене `Rabbit.prototype` на свой объект, свойство `constructor` из него обычно пропадает.** - -Например: - -```js -//+ run -function Rabbit() { } - -Rabbit.prototype = {}; // (*) - -var rabbit = new Rabbit(); - -alert( rabbit.constructor == Rabbit ); // false -alert( rabbit.constructor == Object ); // true -``` - -Ого! Теперь конструктором `rabbit` является не `Rabbit`, а `Object`. Но почему? - -Вот иллюстрация, которая наглядно демонстрирует причину: - - - -То есть, свойство `constructor` бралось из `Rabbit.prototype`, но мы заменили его на пустой объект `Rabbit.prototype = {}`. В нём свойства `constructor` там уже нет, значит оно ищется дальше по цепочке прототипов, в `{}.__proto__`, то есть берётся из встроенного `Object.prototype`. А там оно равно `Object`. - -Что делать? - -**Если мы хотим, чтобы свойство `constructor` объекта всегда хранило функцию, которая его создала -- об этом нужно позаботиться самостоятельно.** - -То есть, можно не заменять встроенный `Rabbit.prototype`, а расширить его. Или же, если обязательно нужно заменить, скажем при наследовании, то указать `constructor` явно: - -```js -//+ run -function Animal() { } -Animal.prototype.run = function() { } - -function Rabbit() { } -*!* -Rabbit.prototype = Object.create(Animal); -Rabbit.prototype.constructor = Rabbit; -*/!* - -Rabbit.prototype.run = function() { } - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.constructor == Rabbit ); // true -*/!* -``` - -## Итого - -При создании функции, её `prototype` -- это объект с единственным свойством `constructor`, которое указывает обратно на функцию. - -**Забавен тот факт, что больше нигде в спецификации JavaScript свойство `constructor` не упоминается. Интерпретатор не использует его никак и нигде.** - -В результате, в частности, `constructor` теряется при замене `prototype` на новый объект. И ничего страшного в этом нет. - -Но если мы хотим использовать это свойство сами, чтобы при необходимости получать функцию-конструктор объекта, но при смене прототипа нужно самим смотреть, чтобы ненароком не перезаписать его. - - diff --git a/1-js/9-prototypes/7-instanceof/1-strange-instanceof/solution.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md similarity index 100% rename from 1-js/9-prototypes/7-instanceof/1-strange-instanceof/solution.md rename to 1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md diff --git a/1-js/9-prototypes/7-instanceof/1-strange-instanceof/task.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md similarity index 100% rename from 1-js/9-prototypes/7-instanceof/1-strange-instanceof/task.md rename to 1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md diff --git a/1-js/9-prototypes/7-instanceof/2-instanceof-result/solution.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md similarity index 100% rename from 1-js/9-prototypes/7-instanceof/2-instanceof-result/solution.md rename to 1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md diff --git a/1-js/9-prototypes/7-instanceof/2-instanceof-result/task.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md similarity index 100% rename from 1-js/9-prototypes/7-instanceof/2-instanceof-result/task.md rename to 1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md diff --git a/1-js/9-prototypes/7-instanceof/article.md b/1-js/9-prototypes/6-instanceof/article.md similarity index 100% rename from 1-js/9-prototypes/7-instanceof/article.md rename to 1-js/9-prototypes/6-instanceof/article.md diff --git a/1-js/9-prototypes/8-class-extend/article.md b/1-js/9-prototypes/7-class-extend/article.md similarity index 100% rename from 1-js/9-prototypes/8-class-extend/article.md rename to 1-js/9-prototypes/7-class-extend/article.md diff --git a/1-js/9-prototypes/9-why-prototypes-better/article.md b/1-js/9-prototypes/8-why-prototypes-better/article.md similarity index 100% rename from 1-js/9-prototypes/9-why-prototypes-better/article.md rename to 1-js/9-prototypes/8-why-prototypes-better/article.md