This commit is contained in:
Ilya Kantor 2014-11-16 01:40:20 +03:00
parent 962caebbb7
commit 87bf53d076
1825 changed files with 94929 additions and 0 deletions

View file

@ -0,0 +1,13 @@
Вызов `arr[2]()` -- это обращение к методу объекта `obj[method]()`, в роли `obj` выступает `arr`, а в роли метода: `2`.
Поэтому, как это бывает при вызове функции как метода, функция `arr[2]` получит `this = arr` и выведет массив:
```js
//+ run
var arr = ["a", "b"];
arr.push( function() { alert(this); } )
arr[2](); // "a","b",function
```

View file

@ -0,0 +1,14 @@
# Вызов в контексте массива
[importance 5]
Каким будет результат? Почему?
```js
var arr = ["a", "b"];
arr.push( function() { alert(this); } )
arr[2](); // ?
```

View file

@ -0,0 +1,24 @@
**Ошибка**!
Попробуйте:
```js
//+ run
var obj = {
go: function() { alert(this) }
}
(obj.go)() // error!
```
Причем сообщение об ошибке в большинстве браузеров не даёт понять, что на самом деле не так.
**Ошибка возникла из-за того, что после объявления `obj` пропущена точка с запятой.**
JavaScript игнорирует перевод строки перед скобкой `(obj.go)()` и читает этот код как:
```js
var obj = { go:... }(obj.go)()
```
Интерпретатор попытается вычислить это выражение, которое обозначает вызов объекта `{ go: ... }` как функции с аргументом `(obj.go)`. При этом, естественно, возникнет ошибка.

View file

@ -0,0 +1,15 @@
# Проверка синтаксиса
[importance 2]
Каков будет результат этого кода?
```js
var obj = {
go: function() { alert(this) }
}
(obj.go)()
```
P.S. Есть подвох :)

View file

@ -0,0 +1,31 @@
<ol>
<li>Обычный вызов функции в контексте объекта.</li>
<li>То же самое, скобки ни на что не влияют.</li>
<li>Здесь не просто вызов `obj.method()`, а более сложный вызов вида `(выражение).method()`. Такой вызов работает, как если бы он был разбит на две строки:
```js
f = obj.go; // сначала вычислить выражение
f(); // потом вызвать то, что получилось
```
При этом `f()` выполняется как обычная функция, без передачи `this`.
</li>
<li>Здесь также слева от точки находится выражение, вызов аналогичен двум строкам.</li>
</ol>
В спецификации это объясняется при помощи специального внутреннего типа [Reference Type](http://es5.github.com/x8.html#x8.7).
Если подробнее -- то `obj.go()` состоит из двух операций:
<ol>
<li>Сначала получить свойство `obj.go`.</li>
<li>Потом вызвать его как функцию.</li>
</ol>
Но откуда на шаге 2 получить `this`? Как раз для этого операция получения свойства `obj.go` возвращает значение особого типа `Reference Type`, который в дополнение к свойству `go` содержит информацию об `obj`. Далее, на втором шаге, вызов его при помощи скобок `()` правильно устанавливает `this`.
**Любые другие операции, кроме вызова, превращают `Reference Type` в обычный тип, в данном случае -- функцию `go` (так уж этот тип устроен).**
Поэтому получается, что `(method = obj.go)` присваивает в переменную `method` функцию `go`, уже без всякой информации об объекте `obj`.
Аналогичная ситуация и в случае `(4)`: оператор ИЛИ `||` делает из `Reference Type` обычную функцию.

View file

@ -0,0 +1,26 @@
# Почему this присваивается именно так?
[importance 3]
Вызовы `(1)` и `(2)` в примере ниже работают не так, как `(3)` и `(4)`:
```js
//+ run
"use strict"
var obj, f;
obj = {
go: function() { alert(this); }
};
obj.go(); // (1) object
(obj.go)(); // (2) object
(method = obj.go)(); // (3) undefined
(obj.go || obj.stop)(); // (4) undefined
```
В чём дело? Объясните логику работы `this`.

View file

@ -0,0 +1,22 @@
**Ответ: пустая строка.**
```js
//+ run
var name = "";
var user = {
name: "Василий",
*!*
export: this // (*)
*/!*
};
alert(user.export.name);
```
Объявление объекта само по себе не влияет на `this`. Никаких функций, которые могли бы повлиять на контекст, здесь нет.
Так как код находится вообще вне любых функций, то `this` в нём равен `window` (при `use strict` было бы `undefined`).
Получается, что в строке `(*)` мы имеем `export: window`, так что далее `alert(user.export.name)` выводит свойство `window.name`, то есть глобальную переменную `name`, которая равна пустой строке.

View file

@ -0,0 +1,18 @@
# Значение this в объявлении объекта
[importance 5]
Что выведет `alert` в этом коде? Почему?
```js
var name = "";
var user = {
name: "Василий",
export: this
};
alert(user.export.name);
```

View file

@ -0,0 +1,5 @@
**Ответ: `Василий`.**
Вызов `user.export()` использует `this`, который равен объекту до точки, то есть внутри `user.export()` строка `return this` возвращает объект `user`.
В итоге выводится свойство `name` объекта `user`, равное `"Василий"`.

View file

@ -0,0 +1,21 @@
# Возврат this
[importance 5]
Что выведет `alert` в этом коде? Почему?
```js
var name = "";
var user = {
name: "Василий",
export: function() {
return this;
}
};
alert(user.export().name);
```

View file

@ -0,0 +1,7 @@
**Ответ: `Василий`.**
Во время выполнения `user.export()` значение `this = user`.
При создании объекта `{ value: this }`, в свойство `value` копируется ссылка на текущий контекст, то есть на `user`.
Получается что `user.export().value == user`.

View file

@ -0,0 +1,23 @@
# Возврат объекта с this
[importance 5]
Что выведет `alert` в этом коде? Почему?
```js
var name = "";
var user = {
name: "Василий",
export: function() {
return {
value: this
};
}
};
alert(user.export().value.name);
```

View file

@ -0,0 +1,15 @@
var calculator = {
sum: function() {
return this.a + this.b;
},
mul: function() {
return this.a * this.b;
},
read: function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
}

View file

@ -0,0 +1,22 @@
sinon.stub(window, "prompt");
prompt.onCall(0).returns("2");
prompt.onCall(1).returns("3");
describe("calculator", function() {
before(function() {
calculator.read();
});
it("при вводе 2 и 3 сумма равна 5", function() {
assert.equal( calculator.sum(), 5 );
});
it("при вводе 2 и 3 произведение равно 6", function() {
assert.equal( calculator.mul(), 6 );
});
});
after(function() {
prompt.restore();
});

View file

@ -0,0 +1,24 @@
```js
//+ run demo
var calculator = {
sum: function() {
return this.a + this.b;
},
mul: function() {
return this.a * this.b;
},
read: function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
}
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```

View file

@ -0,0 +1,22 @@
# Создайте калькулятор
[importance 5]
Создайте объект `calculator` с тремя методами:
<ul>
<li>`read()` запрашивает `prompt` два значения и сохраняет их как свойства объекта</li>
<li>`sum()` возвращает сумму этих двух значений</li>
<li>`mul()` возвращает произведение этих двух значений</li>
</ul>
```js
var calculator = {
... ваш код...
}
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```
[demo /]

View file

@ -0,0 +1,23 @@
Решение состоит в том, чтобы каждый раз возвращать текущий объект. Это делается добавлением `return this` в конце каждого метода:
```js
//+ run
var ladder = {
step: 0,
up: function() {
this.step++;
return this;
},
down: function() {
this.step--;
return this;
},
showStep: function() {
alert(this.step);
return this;
}
}
ladder.up().up().down().up().down().showStep(); // 1
```

View file

@ -0,0 +1,38 @@
# Цепочка вызовов
[importance 2]
Есть объект "лестница" ladder:
```js
var ladder = {
step: 0,
up: function() { // вверх по лестнице
this.step++;
},
down: function() { // вниз по лестнице
this.step--;
},
showStep: function() { // вывести текущую ступеньку
alert(this.step);
}
};
```
Сейчас, если нужно последовательно вызвать несколько методов объекта, это можно сделать так:
```js
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1
```
Модифицируйте код методов объекта, чтобы вызовы можно было делать цепочкой, вот так:
```js
ladder.up().up().down().up().down().showStep(); // 1
```
Такой подход называется "чейнинг" (chaining) и используется, например, во фреймворке jQuery.

View file

@ -0,0 +1,228 @@
# Методы объектов, this
До этого мы говорили об объекте лишь как о хранилище значений. Теперь пойдём дальше и поговорим об объектах как о сущностях со своими функциями ("методами").
[cut]
## Методы у объектов
При объявлении объекта можно указать свойство-функцию, например:
```js
//+ run
var user = {
name: 'Василий',
*!*
// метод
*/!*
sayHi: function() {
alert('Привет!');
}
};
*!*
// Вызов
user.sayHi();
*/!*
```
Свойства-функции называют "методами" объектов. Их можно добавлять и удалять в любой момент, в том числе и явным присваиванием:
```js
//+ run
var user = {
name: 'Василий'
};
*!*
user.sayHi = function() { // присвоили метод после создания объекта
alert('Привет!');
};
*/!*
// Вызов метода:
*!*user.sayHi();*/!*
```
## Доступ к объекту через this
Для полноценной работы метод должен иметь доступ к данным объекта. В частности, вызов `user.sayHi()` может захотеть вывести имя пользователя.
**Для доступа к текущему объекту из метода используется ключевое слово `this`**.
Значением `this` является объект перед "точкой", в контексте которого вызван метод, например:
```js
//+ run
var user = {
name: 'Василий',
sayHi: function() {
alert( *!*this.name*/!* );
}
};
user.sayHi(); // sayHi в контексте user
```
Здесь при выполнении функции `user.sayHi()` в `this` будет храниться ссылка на текущий объект `user`.
Вместо `this` внутри `sayHi` можно было бы обратиться к объекту, используя переменную `user`:
```js
...
sayHi: function() {
alert( *!*user.name*/!* );
}
...
```
...Однако, такое решение нестабильно. Если мы решим скопировать объект в другую переменную, например `admin = user`, а в переменную `user` записать что-то другое -- обращение будет совсем не по адресу:
```js
//+ run
var user = {
name: 'Василий',
sayHi: function() {
alert( *!*user.name*/!* ); // приведёт к ошибке
}
};
var admin = user;
user = null;
admin.sayHi(); // упс! внутри sayHi обращение по старому имени, ошибка!
```
**Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана!**
Через `this` метод может обратиться к любому свойству объекта, а, при желании, и передать объект куда-либо:
```js
//+ run
var user = {
name: 'Василий',
*!*
sayHi: function() {
showName(this); // передать текущий объект в showName
}
*/!*
};
function showName(obj) {
alert( obj.name );
}
user.sayHi(); // Василий
```
## Подробнее про this
Любая функция может иметь в себе `this`. Совершенно неважно, объявлена она в объекте или вне него.
Значение `this` называется *контекстом вызова* и будет определено в момент вызова функции.
Например, такая функция, объявленная без объекта, вполне допустима:
```js
function sayHi() {
alert( *!*this.firstName*/!* );
}
```
Эта функция ещё не знает, каким будет `this`. Это выяснится при выполнении программы.
**Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный `this`:**
```js
//+ run
var user = { firstName: "Вася" };
var admin = { firstName: "Админ" };
function func() {
alert( this.firstName );
}
user.f = func;
admin.g = func;
*!*
// this равен объекту перед точкой:
user.f(); // Вася
admin.g(); // Админ
admin['g'](); // Админ (не важно, доступ к объекту через точку или квадратные скобки)
*/!*
```
**Значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.**
## Значение this при вызове без контекста
Если функция использует `this` -- это подразумевает работу с объектом. Но и прямой вызов `func()` технически возможен.
Как правило, такая ситуация возникает при ошибке в разработке.
При этом `this` получает значение `window`, глобального объекта:
```js
//+ run
function func() {
alert(this); // выведет [object Window] или [object global]
}
func();
```
В современном стандарте языка это поведение изменено, вместо глобального объекта `this` будет `undefined`.
```js
//+ run
function func() {
"use strict";
alert(this); // выведет undefined (кроме IE<10)
}
func();
```
Это стоит иметь в виду для общего развития, но обычно если в функции используется `this`, то она, всё же, проектируется для вызова в контексте объекта.
[warn header="`this` теряется при операциях с методом"]
Ещё раз обратим внимание: контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта.
Чтобы `this` передался правильно, нужно вызвать функцию именно через точку (или квадратные скобки). Любой более хитрый вызов приведёт к потере контекста, например:
```js
//+ run
var user = {
name: "Вася",
hi: function() { alert(this.name); },
bye: function() { alert("Пока"); }
};
user.hi(); // Вася (простой вызов работает)
*!*
// а теперь вызовем user.hi или user.bye в зависимости от имени
(user.name == "Вася" ? user.hi : user.bye)(); // undefined
*/!*
```
В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. При этом `this` теряется.
Иначе говоря, такой вызов эквивалентен двум строкам:
```js
var method = (user.name == "Вася" ? user.hi : user.bye);
method(); // без this
```
[/warn]
## Задачи