en.javascript.info/1-js/6-objects-more/1-object-methods/article.md
2015-02-22 17:04:31 +03:00

244 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Методы объектов, 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(namedObj) {
alert( namedObj.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();
```
Таково поведение в старом стандарте.
А в режиме `use strict` вместо глобального объекта `this` будет `undefined`:
```js
//+ run
function func() {
"use strict";
alert(this); // выведет undefined (кроме IE9-)
}
func();
```
Обычно если в функции используется `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` при этом теряется.
Если хочется понять, почему, то причина кроется в деталях работы вызова `obj.method()`.
Он ведь, на самом деле, состоит из двух независимых операций: точка `.` -- получение свойства и скобки `()` -- его вызов (предполагается, что это функция).
Функция, как мы говорили раньше, сама по себе не запоминает контекст. Чтобы "донести его" до скобок, 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]`.