renovations

This commit is contained in:
Ilya Kantor 2015-01-14 10:23:45 +03:00
parent c7d4c7e3ff
commit e1948130f6
170 changed files with 1496 additions and 1161 deletions

View file

@ -96,9 +96,9 @@ user = null;
admin.sayHi(); // упс! внутри sayHi обращение по старому имени, ошибка!
```
**Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана!**
Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана.
Через `this` метод может обратиться к любому свойству объекта, а, при желании, и передать объект куда-либо:
Через `this` метод может не только обратиться к любому свойству объекта, но и передать куда-то ссылку на сам объект целиком:
```js
//+ run
@ -112,8 +112,8 @@ var user = {
*/!*
};
function showName(obj) {
alert( obj.name );
function showName(namedObj) {
alert( namedObj.name );
}
user.sayHi(); // Василий
@ -157,7 +157,7 @@ admin['g'](); // Админ (не важно, доступ к объекту ч
*/!*
```
**Значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.**
Итак, значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.
## Значение this при вызове без контекста
@ -176,7 +176,9 @@ function func() {
func();
```
В современном стандарте языка это поведение изменено, вместо глобального объекта `this` будет `undefined`.
Таково поведение в старом стандарте.
А в режиме `use strict` вместо глобального объекта `this` будет `undefined`:
```js
//+ run
@ -188,12 +190,13 @@ function func() {
func();
```
Это стоит иметь в виду для общего развития, но обычно если в функции используется `this`, то она, всё же, проектируется для вызова в контексте объекта.
Обычно если в функции используется `this`, то она, всё же, служит для вызова в контексте объекта, так что такая ситуация -- скорее исключение.
[warn header="`this` теряется при операциях с методом"]
Ещё раз обратим внимание: контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта.
## Ссылочный тип
Чтобы `this` передался правильно, нужно вызвать функцию именно через точку (или квадратные скобки). Любой более хитрый вызов приведёт к потере контекста, например:
Контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта. Чтобы `this` передался, нужно вызвать функцию именно через точку (или квадратные скобки).
Любой более хитрый вызов приведёт к потере контекста, например:
```js
//+ run
@ -204,25 +207,38 @@ var user = {
};
user.hi(); // Вася (простой вызов работает)
*!*
// а теперь вызовем user.hi или user.bye в зависимости от имени
(user.name == "Вася" ? user.hi : user.bye)(); // undefined
*/!*
```
В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. При этом `this` теряется.
В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. Но `this` при этом теряется.
Иначе говоря, такой вызов эквивалентен двум строкам:
Если хочется понять, почему, то причина кроется в деталях работы вызова `obj.method()`.
```js
var method = (user.name == "Вася" ? user.hi : user.bye);
method(); // без this
```
Он ведь, на самом деле, состоит из двух независимых операций: точка `.` -- получение свойства и скобки `()` -- его вызов (предполагается, что это функция).
[/warn]
## Задачи
Функция, как мы говорили раньше, сама по себе не запоминает контекст. Чтобы "донести его" до скобок, JavaScript применяет "финт ушами" -- точка возвращает не функцию, а значение специального "ссылочного" типа [Reference Type](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-reference-specification-type).
Этот тип представляет собой связку "base-name-strict", где:
<ul>
<li>*base* -- как раз объект,</li>
<li>*name* -- имя свойства,</li>
<li>*strict* -- вспомогательный флаг для передачи `use strict`.</li>
</ul>
То есть, ссылочный тип (Reference Type) -- это своеобразное "три-в-одном". Он существует исключительно для целей спецификации, мы его не видим, поскольку любой оператор тут же от него избавляется:
<ul>
<li>Скобки `()` получают из `base` значение свойства `name` и вызывают в контексте base.</li>
<li>Другие операторы получают из `base` значение свойства `name` и используют, а остальные компоненты игнорируют.</li>
</ul>
Поэтому любая операция над результатом операции получения свойства, кроме вызова, приводит к потере контекста.
Аналогично работает и получение свойства через квадратные скобки `obj[method]`.

View file

@ -0,0 +1,15 @@
Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект.
В данном случае сравнение означает численное приведение. У массивов нет `valueOf`, поэтому вызывается `toString`, который возвращает список элементов через запятую.
В данном случае, элемент только один - он и возвращается. Так что `['x']` становится `'x'`. Получилось `'x' == 'x'`, верно.
P.S.
По той же причине верны равенства:
```js
//+ run
alert( ['x','y'] == 'x,y' ); // true
alert( [] == '' ); // true
```

View file

@ -0,0 +1,11 @@
# ['x'] == 'x'
[importance 5]
Почему результат `true` ?
```js
//+ run
alert( ['x'] == 'x' );
```

View file

@ -0,0 +1,10 @@
# Первый alert(foo)
Возвращает строковое представление объекта, используя `toString`, т.е. `"foo"`.
# Второй alert(foo + 1)
Оператор `'+'` преобразует объект к примитиву, используя `valueOf`, так что результат: `3`.
# Третий alert(foo + '3')
То же самое, что и предыдущий случай, объект превращается в примитив `2`. Затем происходит сложение `2 + '3'`. Оператор `'+'` при сложении чего-либо со строкой приводит и второй операнд к строке, а затем применяет конкатенацию, так что результат -- строка `"23"`.

View file

@ -0,0 +1,24 @@
# Преобразование
[importance 5]
Объявлен объект с `toString` и `valueOf`.
Какими будут результаты `alert`?
```js
var foo = {
toString: function () {
return 'foo';
},
valueOf: function () {
return 2;
}
};
alert(foo);
alert(foo + 1);
alert(foo + "3");
```
Подумайте, прежде чем ответить.

View file

@ -0,0 +1,34 @@
# Ответ по первому равенству
Два объекта равны только тогда, когда это один и тот же объект.
В первом равенстве создаются два массива, это разные объекты, так что они неравны.
# Ответ по второму равенству
<ol>
<li>Первым делом, обе части сравнения вычисляются. Справа находится `![]`. Логическое НЕ `'!'` преобразует аргумент к логическому типу. Массив является объектом, так что это `true`. Значит, правая часть становится `![] = !true = false`. Так что получили:
```js
alert( [] == false );
```
</li>
<li>Проверка равенства между объектом и примитивом вызывает численное преобразование объекта.
У массива нет `valueOf`, сработает `toString` и преобразует массив в список элементов, то есть - в пустую строку:
```js
alert( '' == false );
```
</li>
<li>Сравнение различных типов вызывает численное преобразование слева и справа:
```js
alert( 0 == 0 );
```
Теперь результат очевиден.
</li>
</ol>

View file

@ -0,0 +1,13 @@
# Почему [] == [] неверно, а [ ] == ![ ] верно?
[importance 5]
Почему первое равенство -- неверно, а второе -- верно?
```js
//+ run
alert( [] == [] ); // false
alert( [] == ![] ); // true
```
Какие преобразования происходят при вычислении?

View file

@ -0,0 +1,33 @@
```js
new Date(0) - 0 = 0 // (1)
new Array(1)[0] + "" = "undefined" // (2)
({})[0] = undefined // (3)
[1] + 1 = "11" // (4)
[1,2] + [3,4] = "1,23,4" // (5)
[] + null + 1 = "null1" // (6)
[[0]][0][0] = 0 // (7)
({} + {}) = "[object Object][object Object]" // (8)
```
<ol>
<li>`new Date(0)` -- дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус `-` преобразует дату обратно в число миллисекунд, то есть в `0`.</li>
<li>`new Array(num)` при вызове с единственным аргументом-числом создаёт массив данной длины, без элементов. Поэтому его нулевой элемент равен `undefined`, при сложении со строкой получается строка `"undefined"`.</li>
<li>Фигурные скобки -- это создание пустого объекта, у него нет свойства `'0'`. Так что значением будет `undefined`.
Обратите внимание на внешние, круглые скобки. Если их убрать и запустить `{}[0]` в отладочной консоли браузера -- будет `0`, т.к. скобки `{}` будут восприняты как пустой блок кода, после которого идёт массив.</li>
<li>Массив преобразуется в строку `"1"`. Оператор `"+"` при сложении со строкой приводит второй аргумент к строке -- значит будет `"1" + "1" = "11"`.</li>
<li>Массивы приводятся к строке и складываются.</li>
<li>Массив преобразуется в пустую строку `"" + null + 1`, оператор `"+"` видит, что слева строка и преобразует `null` к строке, получается `"null" + 1`, и в итоге `"null1"`.</li>
<li>`[[0]]` -- это вложенный массив `[0]` внутри внешнего `[ ]`. Затем мы берём от него нулевой элемент, и потом еще раз.
Если это непонятно, то посмотрите на такой пример:
```js
alert( [1,[0],2][1] );
```
Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента.
</li>
<li>Каждый объект преобразуется к примитиву. У встроенных объектов `Object` нет подходящего `valueOf`, поэтому используется `toString`, так что складываются в итоге строковые представления объектов.</li>
</ol>

View file

@ -0,0 +1,17 @@
# Вопросник по преобразованиям, для объектов
[importance 5]
Подумайте, какой результат будет у выражений ниже. Когда закончите -- сверьтесь с решением.
```js
new Date(0) - 0
new Array(1)[0] + ""
({})[0]
[1] + 1
[1,2] + [3,4]
[] + null + 1
[[0]][0][0]
({} + {})
```

View file

@ -0,0 +1,59 @@
# Подсказка
Чтобы `sum(1)`, а также `sum(1)(2)` можно было вызвать новыми скобками -- результатом `sum` должна быть функция.
Но эта функция также должна уметь превращаться в число. Для этого нужно дать ей соответствующий `valueOf`. А если мы хотим, чтобы и в строковом контексте она вела себя так же -- то `toString`.
# Решение
Функция, которая возвращается `sum`, должна накапливать значение при каждом вызове.
Удобнее всего хранить его в замыкании, в переменной `currentSum`. Каждый вызов прибавляет к ней очередное значение:
```js
//+ run
function sum(a) {
var currentSum = a;
function f(b) {
currentSum += b;
return f;
}
f.toString = function() { return currentSum; };
return f;
}
alert( sum(1)(2) ); // 3
alert( sum(5)(-1)(2) ); // 6
alert( sum(6)(-1)(-2)(-3) ); // 0
alert( sum(0)(1)(2)(3)(4)(5) ); // 15
```
При внимательном взгляде на решение легко заметить, что функция `sum` срабатывает только один раз. Она возвращает функцию `f`.
Затем, при каждом запуске функция `f` добавляет параметр к сумме `currentSum`, хранящейся в замыкании, и возвращает сама себя.
**В последней строчке `f` нет рекурсивного вызова.**
Вот так была бы рекурсия:
```js
function f(b) {
currentSum += b;
return f(); // <-- подвызов
}
```
А в нашем случае, мы просто возвращаем саму функцию, ничего не вызывая.
```js
function f(b) {
currentSum += b;
return f; // <-- не вызывает сама себя, а возвращает ссылку на себя
}
```
Эта `f` используется при следующем вызове, опять возвратит себя, и так сколько нужно раз. Затем, при использовании в строчном или численном контексте -- сработает `toString`, который вернет текущую сумму `currentSum`.

View file

@ -0,0 +1,17 @@
# Сумма произвольного количества скобок
[importance 2]
Напишите функцию `sum`, которая будет работать так:
```js
sum(1)(2) == 3; // 1 + 2
sum(1)(2)(3) == 6; // 1 + 2 + 3
sum(5)(-1)(2) == 6
sum(6)(-1)(-2)(-3) == 0
sum(0)(1)(2)(3)(4)(5) == 15
```
Количество скобок может быть любым.
Пример такой функции для двух аргументов -- есть в решении задачи [](/task/closure-sum).

View file

@ -0,0 +1,238 @@
# Преобразование объектов: toString и valueOf
Ранее, в главе [](/types-conversion) мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты.
Бывают операции, при которых объект должен быть преобразован в примитив.
[cut]
Например:
<ul>
<li>Строковое преобразование -- если объект выводится через `alert(obj)`.</li>
<li>Численное преобразование -- при арифметических операциях, сравнении с примитивом.</li>
<li>Логическое преобразование -- при `if(obj)` и других логических операциях.</li>
</ul>
Рассмотрим эти преобразования по очереди.
## Логическое преобразование
Проще всего -- с логическим преобразованием.
**Любой объект в логическом контексте -- `true`, даже если это пустой массив `[]` или объект `{}`.**
```js
//+ run
if ( {} && [] ) {
alert("Все объекты - true!"); // alert сработает
}
```
## Строковое преобразование
Строковое преобразование проще всего увидеть, если вывести объект при помощи `alert`:
```js
//+ run
var user = {
firstName: 'Василий'
};
alert(user); // [object Object]
```
Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`.
Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.
**Если в объекте присутствует метод `toString`, который возвращает примитив, то он используется для преобразования.**
```js
//+ run
var user = {
firstName: 'Василий',
*!*toString:*/!* function() {
return 'Пользователь ' + this.firstName;
}
};
alert( user ); // Пользователь Василий
```
[smart header="Результатом `toString` может быть любой примитив"]
Метод `toString` не обязан возвращать именно строку.
Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже:
```js
//+ run
var obj = {
toString: function() { return 123; }
};
alert(obj); // 123
```
Поэтому мы и называем его здесь *"строковое преобразование"*, а не "преобразование к строке".
[/smart]
Все объекты, включая встроенные, имеют свои реализации метода `toString`, например:
```js
//+ run
alert( [1,2] ); // toString для массивов выводит список элементов "1,2"
alert( new Date ); // toString для дат выводит дату в виде строки
alert( function() { } ); // toString для функции выводит её код
```
## Численное преобразование
Для численного преобразования объекта используется метод `valueOf`, а если его нет -- то `toString`:
```js
//+ run
var room = {
number: 777,
valueOf: function() { return this.number; },
toString: function() { return this.number; }
};
alert( +room ); // 777, *!*вызвался valueOf*/!*
delete room.valueOf; // *!*valueOf удалён*/!*
alert( +room ); // 777, *!*вызвался toString*/!*
```
Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое.
[smart header="У большинства объектов нет `valueOf`"]
У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково.
Исключением является объект `Date`, который поддерживает оба типа преобразований:
```js
//+ run
alert( new Date() ); // toString: Дата в виде читаемой строки
alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970
```
[/smart]
[smart header="Детали спецификации"]
Если посмотреть в стандарт, то в пункте [15.2.4.4](http://es5.github.com/x15.2.html#x15.2.4.4) говорится о том, что `valueOf` есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется.
[/smart]
## Две стадии преобразования
Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. Далее, вполне возможно,
Если необходимо, что полученный из объекта примитив будет преобразован дальше, уже по правилам для примитивов.
Например, рассмотрим применение к объекту операции `==`:
```js
//+ run
var obj = {
valueOf: function() { return 1; }
};
alert(obj == true); // true
```
Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`.
Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: `true`.
То же самое -- при сложении с объектом при помощи `+`:
```js
//+ run
var obj = {
valueOf: function() { return 1; }
};
alert(obj + "test"); // 1test
```
Или вот, для разности объектов:
```js
//+ run
var a = {
valueOf: function() { return "1"; }
};
var b = {
valueOf: function() { return "2"; }
};
alert(a - b); // "1" - "2" = -1
```
[warn header="Исключение: `Date`"]
Объект `Date`, по историческим причинам, является исключением.
Бинарный оператор плюс `+` обычно использует числовое преобразование, но в случае с `Date` -- строковое:
```js
//+ run
// бинарный вариант, строчное преобразование
alert( new Date + "" ); // "строка даты"
// унарный вариант, как и - * /, приводит к числу
alert( +new Date ); // число миллисекунд
```
[/warn]
[warn header="Как испугать Java-разработчика"]
В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис `new Boolean(true/false)`, например `new Boolean(true)`.
В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения.
Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например:
```js
//+ run
var value = new Boolean(false);
if ( value ) {
alert(true); // сработает!
}
```
Почему запустился `alert`? Ведь в `if` находится `false`... Проверим:
```js
//+ run
var value = new Boolean(false);
*!*
alert(value); // выводит false, все ок..
*/!*
if ( value ) {
alert(true); // ..но тогда почему выполняется alert в if ?!?
}
```
Дело в том, что `new Boolean` -- это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к`true`, в результате работает первый пример.
А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`.
**В JavaScript вызовы `new Boolean/String/Number` не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например `Boolean(val) === !!val`.**
[/warn]
## Итого
<ul>
<li>В логическом контексте объект -- всегда `true`.</li>
<li>При строковом преобразовании объекта используется его метод `toString`. Он должен возвращать примитивное значение, причём не обязательно именно строку.
</li>
<li>Для численного преобразования используется метод `valueOf`, который также может возвратить любое примитивное значение. У большинства объектов `valueOf` не работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используется `toString`.</li>
</ul>
Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3).

View file

@ -2,7 +2,7 @@
Обычный синтаксис `{...}` позволяет создать один объект. Но зачастую нужно создать много однотипных объектов.
Для этого используют функции, запуская их при помощи специального оператора `new`.
Для этого используют "функции-конструкторы", запуская их при помощи специального оператора `new`.
[cut]
## Конструктор
@ -21,9 +21,9 @@ var animal = new Animal("ёжик");
*/!*
```
Технически, никаких ограничений нет -- любую функцию можно вызвать при помощи `new`. Но при этом она работает несколько иным образом, чем обычно, поэтому функции, предназначенные к вызову через `new`, называют с большой буквы.
Технически, любую функцию можно вызвать при помощи `new`. Но при этом она работает несколько иным образом, чем обычно, поэтому функции, предназначенные к вызову через `new`, называют с большой буквы.
**Алгоритм работы оператора `new`:**
**Алгоритм работы функции, запущенной через `new`:**
<ol>
<li>Автоматически создается новый пустой объект.</li>
@ -33,7 +33,7 @@ var animal = new Animal("ёжик");
</ol>
В результате вызова `new Animal("ёжик");` получаем объект:
В результате вызова `new Animal("ёжик");` получаем такой объект:
```js
animal = {
@ -42,15 +42,18 @@ animal = {
}
```
Иными словами, при вызове `new Animal` происходит что-то в таком духе (комментарии -- это то, что делает интерпретатор):
Иными словами, при вызове `new Animal` происходит что-то в таком духе (первая и последняя строка -- это то, что делает интерпретатор):
```js
function Animal(name) {
*!*
// this = {}
*/!*
// в this пишем свойства, методы
this.name = name;
this.canWalk = true;
*!*
// return this
*/!*
@ -67,7 +70,7 @@ function Animal(name) {
<li>При вызове `return` с примитивным значением, оно будет отброшено.</li>
</ul>
Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- прекратит выполнение функции и возвратит `this`.
Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- возвратит, как обычно, `this`.
Например, возврат объекта:
@ -97,9 +100,9 @@ function BigAnimal() {
alert( new BigAnimal().name ); // Мышь, получили this (а Годзилла пропал)
```
Эта особенность работы `new` прописана в стандарте, знать о ней полезно, но используется она весьма редко.
Эта особенность работы `new` прописана в стандарте, но используется она весьма редко.
[smart]
[smart header="Можно без скобок"]
Кстати, при вызове `new` без аргументов скобки можно не ставить:
```js
@ -108,11 +111,14 @@ var animal = new BigAnimal; // <-- без скобок
var animal = new BigAnimal();
```
Не сказать, что выбрасывание скобок -- "хороший стиль", но такой синтаксис допустим стандартом.
[/smart]
## Создание методов в конструкторе
Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать.
Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет "клепать" объекты заданным образом.
Добавим в создаваемый объект ещё и метод.
Например, `new User(name)` создает объект с заданным значением свойства `name` и методом `sayHi`:

View file

@ -0,0 +1,49 @@
```js
//+ run
function User(fullName) {
this.fullName = fullName;
Object.defineProperties(this, {
firstName: {
get: function() {
return this.fullName.split(' ')[0];
},
set: function(newFirstName) {
this.fullName = newFirstName + ' ' + this.lastName;
}
},
lastName: {
get: function() {
return this.fullName.split(' ')[1];
},
set: function(newLastName) {
this.fullName = this.firstName + ' ' + newLastName;
}
}
});
}
var vasya = new User("Василий Попкин");
// чтение firstName/lastName
alert(vasya.firstName); // Василий
alert(vasya.lastName); // Попкин
// запись в lastName
vasya.lastName = 'Сидоров';
alert(vasya.fullName); // Василий Сидоров
```

View file

@ -0,0 +1,32 @@
# Добавить get/set-свойства
[importance 5]
Вам попал в руки код объекта `User`, который хранит имя и фамилию в свойстве `this.fullName`:
```js
function User(fullName) {
this.fullName = fullName;
}
var vasya = new User("Василий Попкин");
```
Имя и фамилия всегда разделяются пробелом.
Сделайте, чтобы были доступны свойства `firstName` и `lastName`, причём не только на чтение, но и на запись, вот так:
```js
var vasya = new User("Василий Попкин");
// чтение firstName/lastName
alert(vasya.firstName); // Василий
alert(vasya.lastName); // Попкин
// запись в lastName
vasya.lastName = 'Сидоров';
alert(vasya.fullName); // Василий Сидоров
```
Важно: не рекомендуется дублировать одни и те же данные в различных свойствах. Поэтому в этой задаче `fullName` должно остаться свойством, а `firstName/lastName` -- реализованы через `get/set`.

View file

@ -0,0 +1,394 @@
# Дескрипторы, геттеры и сеттеры свойств
В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже "невидимые" геттеры-сеттеры.
Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения).
[cut]
## Дескрипторы в примерах
Основной метод для управления свойствами -- [Object.defineProperty](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty).
Он позволяет определить свойство путём задания "дескриптора" -- описания, включающего в себя ряд важных внутренних параметров.
Синтаксис:
```js
Object.defineProperty(obj, prop, descriptor)
```
Аргументы:
<dl>
<dt>`obj`</dt>
<dd>Объект, в котором объявляется свойство.</dd>
<dt>`prop`</dt>
<dd>Имя свойства, которое нужно объявить или модифицировать.</dd>
<dt>`descriptor`</dt>
<dd>Дескриптор -- объект, который описывает поведение свойства. В нём могут быть следующие поля:
<ul>
<li>`value` -- значение свойства, по умолчанию `undefined`</li>
<li>`writable` -- значение свойства можно менять, если `true`. По умолчанию `false`.</li>
<li>`configurable` -- если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи новых вызовов `defineProperty`. По умолчанию `false`.</li>
<li>`enumerable` -- если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.</li>
<li>`get` -- функция, которая возвращает значение свойства. По умолчанию `undefined`.</li>
<li>`set` -- функция, которая записывает значение свойства. По умолчанию `undefined`.</li>
</ul>
Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций.
Далее мы подробно разберём эти свойства на примерах.
## Обычное свойство
Обычное свойство добавить очень просто.
Два таких вызова работают одинаково:
```js
var user = {};
// 1. простое присваивание
user.name = "Вася";
// 2. указание значения через дескриптор
Object.defineProperty(user, "name", { value: "Вася" });
```
## Свойство-константа
Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`:
```js
//+ run
*!*
"use strict";
*/!*
var user = {};
Object.defineProperty(user, "name", {
value: "Вася",
writable: false, // запретить присвоение "user.name="
configurable: false // запретить удаление "delete user.name"
});
// Теперь попытаемся изменить это свойство.
// в strict mode присвоение "user.name=" вызовет ошибку
*!*
user.name = "Петя";
*/!*
```
Заметим, что без `use strict` операция записи "молча" не сработает, а при `use strict` дополнительно генерируется ошибка.
## Свойство, скрытое для for..in
Встроенный метод `toString`, как и большинство встроенных методов, не участвует в цикле `for..in`. Это удобно, так как обычно такое свойство является "служебным".
К сожалению, свойство `toString`, объявленное обычным способом, будет видно в цикле `for..in`, например:
```js
//+ run
var user = {
name: "Вася",
toString: function() { return this.name; }
};
*!*
for(var key in user) alert(key); // name, toString
*/!*
```
Мы бы хотели, чтобы поведение нашего метода `toString` было таким же, как и стандартного.
`Object.defineProperty` может исключить `toString` из списка итерации, поставив ему флаг `enumerable: false`. По стандарту, у встроенного `toString` этот флаг уже стоит.
```js
//+ run
var user = {
name: "Вася",
toString: function() { return this.name; }
};
*!*
// помечаем toString как не подлежащий перебору в for..in
Object.defineProperty(user, "toString", {enumerable: false});
for(var key in user) alert(key); // name
*/!*
```
Обратим внимание, вызов `defineProperty` не перезаписал свойство, а просто модифицировал настройки у существующего `toString`.
## Свойство-функция
Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`.
Например, у объекта `user` есть обычные свойства: имя `firstName` и фамилия `surname`.
Создадим свойство `fullName`, которое на самом деле является функцией:
```js
//+ run
var user = {
firstName: "Вася",
surname: "Петров"
}
Object.defineProperty(user, "fullName", {
*!*get*/!*: function() {
return this.firstName + ' ' + this.surname;
}
});
*!*
alert(user.fullName); // Вася Петров
*/!*
```
Обратим внимание, снаружи `fullName` -- это обычное свойство `user.fullName`. Но дескриптор указывает, что на самом деле его значение возвращается функцией.
Также можно указать функцию, которая используется для записи значения, при помощи дескриптора `set`.
Например, добавим возможность присвоения `user.fullName` к примеру выше:
```js
//+ run
var user = {
firstName: "Вася",
surname: "Петров"
}
Object.defineProperty(user, "fullName", {
get: function() {
return this.firstName + ' ' + this.surname;
},
*!*
set: function(value) {
var split = value.split(' ');
this.firstName = split[0];
this.surname = split[1];
}
*/!*
});
*!*
user.fullName = "Петя Иванов";
*/!*
alert(user.firstName); // Петя
alert(user.surname); // Иванов
```
## Указание get/set в литералах
Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать свойства-функции можно прямо в его определении.
Для этого используется особый синтаксис: `get свойство` или `set свойство`.
Например, ниже объявлен геттер-сеттер `fullName`:
```js
//+ run
var user = {
firstName: "Вася",
surname: "Петров",
*!*
get fullName() {
*/!*
return this.firstName + ' ' + this.surname;
},
*!*
set fullName(value) {
*/!*
var split = value.split(' ');
this.firstName = split[0];
this.surname = split[1];
}
};
*!*
alert(user.fullName); // Вася Петров (из геттера)
user.fullName = "Петя Иванов";
alert(user.firstName); // Петя (поставил сеттер)
alert(user.surname); // Иванов (поставил сеттер)
*/!*
```
## Да здравствуют get/set!
Казалось бы, зачем нам назначать get/set для свойства через всякие хитрые вызовы, когда можно сделать просто функции с самого начала? Например, `getFullName`, `setFullName`...
Конечно, в ряде случаев свойства выглядят короче, такое решение просто может быть красивым. Но основной бонус -- это гибкость, возможность получить контроль над свойством в любой момент!
Например, в начале разработки мы используем обычные свойства, например у `User` будет имя `name` и возраст `age`:
```js
function User(name, age) {
this.name = name;
this.age = age;
}
var pete = new User("Петя", 25);
alert(pete.age); // 25
```
С обычными свойствами в коде меньше букв, они удобны, причины использовать функции пока нет.
...Но рано или поздно может произойти что-то, что потребует более сложной логики.
Например, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`:
```js
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
var pete = new User("Петя", new Date(1987, 6, 1));
```
Что теперь делать со старым кодом, который выводит свойство `age`?
Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно.
Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо:
```js
//+ run
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
*!*
Object.defineProperty(this, "age", {
get: function() {
var todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
*/!*
}
var pete = new User("Петя", new Date(1987, 6, 1));
alert(pete.age); // получает возраст из даты рождения
```
Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя полную совместимость.
## Другие методы работы со свойствами
<dl>
<dt>[Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperties)</dt>
<dd>Позволяет объявить несколько свойств сразу:
```js
//+ run
var user = {}
Object.defineProperties(user, {
*!*
firstName: {
*/!*
value: "Петя"
},
*!*
surname: {
*/!*
value: "Иванов"
},
*!*
fullName: {
*/!*
get: function() {
return this.firstName + ' ' + this.surname;
}
}
});
alert( user.fullName ); // Петя Иванов
```
</dd>
<dt>[Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames)</dt>
<dd>Возвращают массив -- список свойств объекта.
`Object.keys` возвращает только `enumerable`-свойства.
`Object.getOwnPropertyNames` -- возвращает все:
```js
//+ run
var obj = {
a: 1,
b: 2,
internal: 3
};
Object.defineProperty(obj, "internal", {enumerable: false});
*!*
alert( Object.keys(obj) ); // a,b
alert( Object.getOwnPropertyNames(obj) ); // a, internal, b
*/!*
```
</dd>
<dt>[Object.getOwnPropertyDescriptor(prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)</dt>
<dd>Возвращает дескриптор для свойства с `prop`.
Полученный дескриптор можно изменить и использовать `defineProperty` для сохранения изменений, например:
```js
//+ run
var obj = { test: 5 };
*!*
var descriptor = Object.getOwnPropertyDescriptor(obj, 'test');
*/!*
*!*
// заменим value на геттер, для этого...
*/!*
delete descriptor.value; // ..нужно убрать value/writable
delete descriptor.writable;
descriptor.get = function() { // и поставить get
alert("Preved :)");
};
*!*
// поставим новое свойство вместо старого
*/!*
// если не удалить - defineProperty объединит старый дескриптор с новым
delete obj.test;
Object.defineProperty(obj, 'test', descriptor);
obj.test; // Preved :)
```
</dd>
</dl>
...И несколько методов, которые используются очень редко:
<dl>
<dt>[Object.preventExtensions(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)</dt>
<dd>Запрещает добавление свойств в объект.</dd>
<dt>[Object.seal(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)</dt>
<dd>Запрещает добавление и удаление свойств, все текущие свойства делает `configurable: false`.</dd>
<dt>[Object.freeze(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze)</dt>
<dd>Запрещает добавление, удаление и изменение свойств, все текущие свойства делает `configurable: false, writable: false`.</dd>
<dt>[Object.isExtensible(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isExtensible), [Object.isSealed(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isSealed), [Object.isFrozen(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isFrozen)</dt>
<dd>Возвращают `true`, если на объекте были вызваны методы `Object.preventExtensions/seal/freeze`.</dd>
</dl>

View file

@ -50,7 +50,7 @@ Article.showCount(); // (2)
Здесь `Article.count` -- статическое свойство, а `Article.showCount` -- статический метод.
**Обратите внимание на контекст `this`. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`!**
Обратим внимание на использование `this` в примере выше. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`.
## Пример: сравнение объектов

View file

@ -1,6 +1,6 @@
# Явное указание this: "call", "apply"
Итак, мы знаем, что в `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`.
Итак, мы знаем, что `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`.
В этой главе наша цель получить окончательное и полное понимание `this` в JavaScript. Для этого не хватает всего одного элемента: способа явно указать `this` при помощи методов `call` и `apply`.
@ -26,7 +26,7 @@ function showFullName() {
}
```
**Обратите внимание, JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.**
Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.
Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так:
@ -138,13 +138,13 @@ alert( obj.join(';') ); // "A;Б;В"
[/smart]
**...Однако, копирование метода из одного объекта в другой не всегда приемлемо!**
...Однако, копирование метода из одного объекта в другой не всегда приемлемо!
Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`.
Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница.
**Безопасно вызвать метод нам поможет `call`:**
Безопасно вызвать метод нам поможет `call`:
```js
//+ run
@ -208,7 +208,16 @@ showFullName.call(user, 'firstName', 'surname');
showFullName.apply(user, ['firstName', 'surname']);
```
Преимущество `apply` перед `call` отчётливо видно в следующем примере, когда мы формируем массив аргументов динамически:
Преимущество `apply` перед `call` отчётливо видно, когда мы формируем массив аргументов динамически.
Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов:
```js
//+ run
alert( Math.max(1, 5, 2) ); // 5
```
При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так:
```js
//+ run
@ -221,57 +230,46 @@ arr.push(2);
alert( Math.max.apply(null, arr) ); // 5
```
Обратим внимание, в примере выше вызывается метод `Math.max`. Его стандартное применение -- это выбор максимального аргумента из переданных, которых может быть сколько угодно:
```js
//+ run
alert( Math.max(1, 5, 2) ); // 5
alert( Math.max(1, 5, 2, 8) ); // 8
```
В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`.
Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`.
Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max`не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов?
**Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!**
Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!
[smart header="Вызов `call/apply` с `null` или `undefined`"]
В старом стандарте при указании первого аргумента `null` или `undefined` в `call/apply`, функция получала `this = window`, например:
```js
//+ run
function f() {
alert(this);
}
f.call(null); // window
```
Это поведение исправлено в современном стандарте ([15.3](http://es5.github.com/x15.3.html#x15.3.4.3)).
Если функция работает в строгом режиме, то `this` передаётся "как есть":
В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например:
Современный стандарт:
```js
//+ run
function f() {
"use strict";
*!*
alert(this); // null, а не window
alert(this); // null
*/!*
}
f.call(null);
```
Без `use strict`:
```js
//+ run
function f() {
alert(this); // window
}
f.call(null);
```
[/smart]
## Итого про this
Значение `this` устанавливается в зависимости от того, как вызвана функция:
<dl>
<dt>При вызове функции как метода</dt>
<dd>

View file

@ -39,7 +39,7 @@ setTimeout( user.sayHi, 1000); // undefined (не Вася!)
*/!*
```
**При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`!**
При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`!
Это произошло потому, что в примере выше `setTimeout` получил функцию `user.sayHi`, но не её контекст. То есть, последняя строчка аналогична двум таким:
@ -48,7 +48,8 @@ var f = user.sayHi;
setTimeout(f, 1000); // контекст user потеряли
```
**Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно?**
Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно?
Есть несколько способов решения, среди которых мы, в зависимости от ситуации, можем выбирать.
@ -86,24 +87,23 @@ setTimeout(function() {
```js
function bind(func, context) {
return function() {
return function() { // (*)
return func.apply(context, arguments);
};
}
```
Параметры:
<dl>
<dt>`func`</dt>
<dd>Произвольная функция</dd>
<dt>`context`</dt>
<dd>Произвольный объект</dd>
Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно:
Результатом вызова `bind(func, context)` будет, как видно из кода, функция-обёртка, которая передаёт все вызовы `func`, указывая при этом правильный контекст `context`.
```js
function() { // (*)
return func.apply(context, arguments);
};
```
Чтобы понять, как она работает, нужно вспомнить тему "замыкания" -- здесь `bind` возвращает анонимную функцию, которая при вызове получает контекст `context` из внешней области видимости и передаёт вызов в `func` вместе с этим контекстом `context` и аргументами `arguments`.
Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`).
**В результате вызова `bind` мы получаем как бы "ту же функцию, но с фиксированным контекстом".**
Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`.
Пример с `bind`:
@ -151,7 +151,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
</dl>
Результат вызова: `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, но это делается редко, мы поговорим о них позже.
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже.
Пример со встроенным методом `bind`:
@ -234,13 +234,12 @@ alert( triple(4) ); // = mul(3, 4) = 12
alert( triple(5) ); // = mul(3, 5) = 15
```
**При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.**
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
## Задачи
## Функция дла задач
Рассмотрим для дальнейших задач "функцию для вопросов" `ask`:
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
```js
function ask(question, answer, ok, fail) {
@ -250,9 +249,9 @@ function ask(question, answer, ok, fail) {
}
```
Пока в этой функции ничего особого нет. Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
Однако, тем не менее, эта функция взята из реального проекта. Просто обычно она сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
Пример использования:

View file

@ -3,15 +3,14 @@
JavaScript предоставляет удивительно гибкие возможности по работе с функциями: их можно передавать, в них можно записывать данные как в объекты, у них есть свои встроенные методы...
Конечно, этим нужно уметь пользоваться. В этой главе, чтобы более глубоко понимать работу с функциями, мы рассмотрим создание функций-обёрток или, иначе говоря, "декораторов".
[cut]
## Примеры декораторов
[Декоратор](http://en.wikipedia.org/wiki/Decorator_pattern) -- приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
<a href="http://en.wikipedia.org/wiki/Decorator_pattern">Декоратор</a> -- приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
*Декоратор* получает функцию и возвращает обертку, которая делает что-то своё "вокруг" вызова основной функции.
***Декоратор* получает функцию и возвращает обертку, которая делает что-то своё "вокруг" вызова основной функции.**
### bind -- привязка контекста
## bind -- привязка контекста
Один простой декоратор вы уже видели ранее -- это функция [bind](/bind):
@ -25,11 +24,32 @@ function bind(func, context) {
Вызов `bind(func, context)` возвращает обёртку, которая ставит `this` и передаёт основную работу функции `func`.
### Декоратор -- измеритель времени
## Декоратор-таймер
Посмотрим немного более сложный декоратор, замеряющий время выполнения функции:
Создадим более сложный декоратор, замеряющий время выполнения функции.
**При помощи декоратора `timingDecorator` мы можем взять произвольную функцию и одним движением руки прикрутить к ней измеритель времени:**
Он будет называться `timingDecorator` и получать функцию вместе с "названием таймера", а возвращать -- функцию-обёртку, которая измеряет время и прибавляет его в специальный объект `timer` по свойству-названию.
Использование:
```js
function f(x) { } // любая функция
var timers = {}; // объект для таймеров
// отдекорировали
f = timingDecorator(f, "myFunc");
// запускаем
f(1);
f(2);
f(3); // функция работает как раньше, но время подсчитывается
alert(timers.myFunc); // общее время выполнения всех вызовов f
```
При помощи декоратора `timingDecorator` мы сможем взять произвольную функцию и одним движением руки прикрутить к ней измеритель времени.
Его реализация:
```js
//+ run
@ -40,7 +60,7 @@ function timingDecorator(f, timer) {
return function() {
var start = performance.now();
var result = f.apply(this, arguments);
var result = f.apply(this, arguments); // (*)
if (!timers[timer]) timers[timer] = 0;
timers[timer] += performance.now() - start;
@ -70,11 +90,15 @@ alert( timers.fibo + 'мс' );
*/!*
```
Обратим внимание на ключевую строку декоратора`var result = f.apply(this, arguments)`.
Обратим внимание на строку `(*)` внутри декоратора, которая и осуществляет передачу вызова:
**Этот приём называется "форвардинг вызова" (от англ. forwarding): текущий контекст и аргументы через `apply` передаются в функцию, так что изнутри `f` всё выглядит так, как будто это была вызвана она, а не декоратор.**
```js
var result = f.apply(this, arguments); // (*)
```
### Декоратор для проверки типа
Этот приём называется "форвардинг вызова" (от англ. forwarding): текущий контекст и аргументы через `apply` передаются в функцию `f`, так что изнутри `f` всё выглядит так, как была вызвана она напрямую, а не декоратор.
## Декоратор для проверки типа
В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект.
@ -85,6 +109,7 @@ function sum(a, b) {
return a + b;
}
// передадим в функцию для сложения чисел нечисловые значения
alert( sum(true, { name: "Вася", age: 35 }) ); // true[Object object]
```
@ -142,7 +167,7 @@ sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент
**Один раз пишем декоратор и дальше просто применяем этот функционал везде, где нужно.**
### Декоратор проверки доступа
## Декоратор проверки доступа
И наконец посмотрим ещё один, последний пример.

View file

@ -1,3 +1,3 @@
# Методы объектов и контекст вызова
Начинаем изучать объектно-ориентированную разработку -- как работают объекты и функции, что такое контекст вызова и почему его значение нельзя предсказать. Как, всё же, гарантировать правильный контекст и многие другие, не самые простые, темы.
Начинаем изучать объектно-ориентированную разработку -- как работают объекты и функции, что такое контекст вызова и способы его передачи.