Как работает ключевое слово "this" и когда его следует использовать?
Я ищу четкое объяснение того, что делает ключевое слово this
и как правильно его использовать.
Кажется, оно ведет себя странно, и я не полностью понимаю, почему это так.
Как работает this
и когда его следует использовать?
5 ответ(ов)
this
в JavaScript
Простая функция
Рассмотрим следующую функцию:
function foo() {
console.log("bar");
console.log(this);
}
foo(); // вызываем функцию
Обратите внимание, что мы выполняем это в обычном режиме, то есть строгий режим не используется.
При выполнении в браузере значение this
будет выведено как window
. Это происходит потому, что window
является глобальной переменной в области видимости веб-браузера.
Если запустить этот же код в среде, такой как node.js, this
будет ссылаться на глобальную переменную в вашем приложении.
Теперь, если мы запустим это в строгом режиме, добавив строку "use strict";
в начало определения функции, значение this
больше не будет ссылаться на глобальную переменную ни в одной из сред. Это сделано для того, чтобы избежать путаницы в строгом режиме. В этом случае this
просто выведет undefined
, потому что оно не определено.
Теперь рассмотрим, как можно манипулировать значением this
.
Вызов функции на объекте
Существует несколько способов сделать это. Если вы использовали встроенные методы в JavaScript, такие как forEach
и slice
, вы уже знаете, что переменная this
в этом случае ссылается на Object
, на котором была вызвана эта функция (Обратите внимание, что в JavaScript практически все является Object
, включая Array
и Function
). Например, рассмотрим следующий код:
var myObj = {key: "Obj"};
myObj.logThis = function () {
// Я метод
console.log(this);
}
myObj.logThis(); // выводит myObj
Если у Object
есть свойство, содержащее Function
, это свойство называется методом. Этот метод, когда его вызывают, всегда будет иметь переменную this
, установленную на Object
, с которым он ассоциирован. Это справедливо как для строгого, так и для нестрогого режима.
Обратите внимание, что если метод будет сохранен (или скопирован) в другую переменную, ссылка на this
больше не будет сохранена в новой переменной. Например:
// продолжаем с предыдущего фрагмента кода
var myVar = myObj.logThis;
myVar();
// выводит либо window/global/undefined в зависимости от режима выполнения
Рассмотрим более практичный сценарий:
var el = document.getElementById('idOfEl');
el.addEventListener('click', function() { console.log(this) });
// функция, вызываемая addEventListener, содержит this как ссылку на элемент
// так что клик по элементу выведет сам элемент
Ключевое слово new
Рассмотрим функцию-конструктор в JavaScript:
function Person(name) {
this.name = name;
this.sayHello = function() {
console.log("Hello", this);
}
}
var awal = new Person("Awal");
awal.sayHello();
// В `awal.sayHello`, `this` содержит ссылку на переменную `awal`
Как это работает? Давайте посмотрим, что происходит, когда мы используем ключевое слово new
.
- Вызов функции с ключевым словом
new
мгновенно инициализируетObject
типаPerson
. - Конструктор этого
Object
устанавливает свой конструктор наPerson
. Также обратите внимание, чтоtypeof awal
вернет толькоObject
. - Этот новый
Object
ассоциируется с прототипомPerson.prototype
. Это означает, что любой метод или свойство в прототипеPerson
будет доступен для всех экземпляровPerson
, включаяawal
. - Функция
Person
теперь вызывается;this
является ссылкой на вновь созданный объектawal
.
Довольно просто, не так ли?
Обратите внимание, что официальная спецификация ECMAScript нигде не утверждает, что такие типы функций являются настоящими функциями-конструкторами. Это просто обычные функции, и new
может быть использован с любой функцией. Просто так мы их используем и поэтому так и называем.
Вызов функций на функциях: call
и apply
Да, поскольку function
также являются Objects
(и на самом деле первоклассными переменными в JavaScript), даже функции имеют методы, которые, ну, являются функциями сами по себе.
Все функции наследуются от глобального Function
, и два из его множества методов — это call
и apply
, и оба могут быть использованы для манипулирования значением this
в функции, на которой они вызываются.
function foo() { console.log(this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);
Это типичный пример использования call
. Он в основном принимает первый параметр и устанавливает this
в функции foo
как ссылку на thisArg
. Все остальные параметры, переданные в call
, передаются функции foo
как аргументы.
Таким образом, указанный код выведет в консоль {myObj: "is cool"}, [1, 2, 3]
. Прекрасный способ изменить значение this
в любой функции.
apply
почти такой же, как call
, за исключением того, что он принимает только два параметра: thisArg
и массив, содержащий аргументы, которые нужно передать функции. Так что вышеуказанный вызов call
можно переписать на apply
так:
foo.apply(thisArg, [1, 2, 3]);
Обратите внимание, что call
и apply
могут переопределить значение this
, установленное с помощью точечной нотации, о которой мы говорили во втором пункте. Довольно просто 😃
Представляем.... bind
!
bind
— это брат call
и apply
. Это также метод, унаследованный всеми функциями от глобального конструктора Function
в JavaScript. Разница между bind
и call
/apply
заключается в том, что оба call
и apply
фактически вызывают функцию. bind
, с другой стороны, возвращает новую функцию с предустановленным thisArg
и аргументами. Давайте рассмотрим пример, чтобы лучше понять это:
function foo(a, b) {
console.log(this, arguments);
}
var thisArg = {myObj: "даже еще более круто!"};
var bound = foo.bind(thisArg, 1, 2);
console.log(typeof bound); // выводит `function`
console.log(bound);
/* выводит `function () { native code }` */
bound(); // вызываем функцию, возвращенную методом `.bind`
// выводит `{myObj: "даже еще более круто!"}, [1, 2]`
Видите разницу между этими тремя? Она тонкая, но они используются по-разному. Как и call
, и apply
, bind
также переопределяет значение this
, установленное с помощью точечной нотации.
Также обратите внимание, что ни одна из этих трех функций не вносит изменения в исходную функцию. call
и apply
возвращают значение из вновь созданных функций, тогда как bind
возвращает саму свежесозданную функцию, готовую к вызову.
Дополнительные сведения, скопируйте это
Иногда вам не нравится, что this
меняется с областью видимости, особенно в вложенных областях. Рассмотрим следующий пример.
var myObj = {
hello: function() {
return "world"
},
myMethod: function() {
// копируем this, имена переменных чувствительны к регистру
var that = this;
// callbacks ftw \o/
foo.bar("args", function() {
// Я хочу вызвать `hello` здесь
this.hello(); // ошибка
// но `this` ссылается на foo, черт возьми!
// о, подождите, у нас есть резервная копия \o/
that.hello(); // "world"
});
}
};
В приведенном выше коде мы видим, что значение this
изменилось в вложенной области, но мы хотели сохранить значение this
из исходной области. Поэтому мы 'скопировали' this
в that
и использовали копию вместо this
. Умно, не правда ли?
Индекс:
- Что по умолчанию хранится в
this
? - Что если мы вызываем функцию как метод с помощью точечной нотации объекта?
- Что если мы используем ключевое слово
new
? - Как манипулировать
this
с помощьюcall
иapply
? - Использование
bind
. - Копирование
this
, чтобы решить проблемы с вложенной областью видимости.
"this" в JavaScript — это вопрос области видимости. Каждая функция имеет свою собственную область видимости, и поскольку все в JS является объектом, даже функция может хранить значения внутри себя с помощью "this". Основы объектно-ориентированного программирования учат, что "this" применяется только к экземплярам объекта. Таким образом, каждый раз, когда функция выполняется, новое "экземпляр" этой функции имеет новое значение "this".
Многие люди путаются, когда пытаются использовать "this" внутри анонимных функций (closure), например:
(function(value) {
this.value = value;
$('.some-elements').each(function(elt){
elt.innerHTML = this.value; // о нет!! возможно, undefined
});
})(2);
Здесь, внутри функции each(), "this" не содержит ожидаемое вами значение "value" из строки
this.value = value;
выше. Чтобы решить эту проблему (без игры слов), разработчик может сделать следующее:
(function(value) {
var self = this; // небольшое изменение
self.value = value;
$('.some-elements').each(function(elt){
elt.innerHTML = self.value; // фух!! == 2
});
})(2);
Попробуйте этот подход; вам может начать нравиться такая схема программирования!
В JavaScript ключевое слово this
всегда ссылается на "владельца" функции, которая выполняется.
Если явный владелец не определен, то будет использоваться объект верхнего уровня, то есть объект window
.
Например, если у вас есть следующий код:
function someKindOfFunction() {
this.style = 'foo';
}
и вы присваиваете функцию обработчику клика:
element.onclick = someKindOfFunction;
то в этом случае this
будет ссылаться на объект element
. Но будьте осторожны, многие делают эту ошибку.
Если же вы напишете в HTML:
<element onclick="someKindOfFunction()"></element>
то в этом случае вы просто ссылаетесь на функцию, а не передаете её элементу. Поэтому в данном случае this
будет ссылаться на объект window
.
Дэниел, отличное объяснение! Пару слов по этому поводу и хорошая подборка указателей контекста исполнения this
в случае обработчиков событий.
В двух словах, this
в JavaScript указывает на объект, от которого (или из контекста исполнения которого) была вызвана текущая функция. Этот указатель всегда доступен только для чтения; задать его значение не получится (попытка сделать это приведет к сообщению 'Invalid left-hand side in assignment').
Что касается обработчиков событий: встроенные обработчики событий, такие как <element onclick="foo">
, переопределяют любые другие ранее прикрепленные обработчики, поэтому будьте осторожны, и лучше избегать встроенной делегации событий вообще. И спасибо Заре Алавердян, которая вдохновила меня на составление этого списка примеров в ходе разгоревшегося спора 😃
el.onclick = foo; // в foo - obj
el.onclick = function () {this.style.color = '#fff';} // obj
el.onclick = function() {doSomething();} // в doSomething - Window
el.addEventListener('click', foo, false) // в foo - obj
el.attachEvent('onclick', function () { // this }') // window, полностью соответствует IE :)
<button onclick="this.style.color = '#fff';"> // obj
<button onclick="foo"> // в foo - window, но вы можете использовать <button onclick="foo(this)">
Существует много путаницы по поводу того, как интерпретируется ключевое слово "this" в JavaScript. Надеемся, что эта статья окончательно развеет все сомнения. И многое другое. Пожалуйста, внимательно прочитайте всю статью. Предупреждаем, что она довольно длинная.
Независимо от контекста, в котором используется "this", оно всегда ссылается на "текущий объект" в JavaScript. Однако то, что является "текущим объектом", различается в зависимости от контекста. Этот контекст может быть одним из 6 следующих:
- Глобальный контекст (т.е. за пределами всех функций)
- Внутри прямого вызова "непривязанной функции" (т.е. функции, которая не была привязана с помощью functionName.bind)
- Внутри косвенного вызова "непривязанной функции" через functionName.call и functionName.apply
- Внутри вызова "привязанной функции" (т.е. функции, которая была привязана с помощью functionName.bind)
- При создании объекта с помощью "new"
- Внутри встроенного обработчика событий DOM
Рассмотрим каждый из этих контекстов по очереди:
Глобальный контекст (т.е. за пределами всех функций):
За пределами всех функций (т.е. в глобальном контексте) "текущий объект" (а следовательно, значение "this") всегда является объектом "window" для браузеров.
Внутри прямого вызова "непривязанной функции":
Внутри прямого вызова "непривязанной функции" объект, вызвавший функцию, становится "текущим объектом" (а следовательно, значением "this"). Если функция вызывается без явного текущего объекта, то текущий объект либо является объектом "window" (в нестрогом режиме), либо undefined (в строго режиме). Любая функция (или переменная), определенная в глобальном контексте, автоматически становится свойством объекта "window".
Например, если функция определена в глобальном контексте как:
function UserDefinedFunction() { alert(this); }
она становится свойством объекта window, как если бы вы определили её так:
window.UserDefinedFunction = function() { alert(this); };
В "нестрогом режиме" вызов этой функции напрямую через "UserDefinedFunction()" автоматически вызовет её как "window.UserDefinedFunction()", что сделает "window" "текущим объектом" (и следовательно, значением "this") внутри "UserDefinedFunction". Вызов этой функции в "нестрогом режиме" даст следующий результат:
UserDefinedFunction(); // выводит [object Window] поскольку автоматически вызывается как window.UserDefinedFunction()
В "строгом режиме" вызов этой функции напрямую через "UserDefinedFunction()" "НЕ" приведет к автоматическому вызову как "window.UserDefinedFunction()". Следовательно, "текущий объект" (и значение "this") внутри "UserDefinedFunction" будет undefined. Вызов этой функции в "строгом режиме" даст следующий результат:
UserDefinedFunction(); // выводит undefined
Однако явный вызов с использованием объекта window даст следующий результат:
window.UserDefinedFunction(); // "всегда выводит [object Window] независимо от режима."
Посмотрим на другой пример:
function UserDefinedFunction() { alert(this.a + "," + this.b + "," + this.c + "," + this.d); } var o1 = { a: 1, b: 2, f: UserDefinedFunction }; var o2 = { c: 3, d: 4, f: UserDefinedFunction }; o1.f(); // Выведет 1,2,undefined,undefined o2.f(); // Выведет undefined,undefined,3,4
В этом примере, когда "UserDefinedFunction" была вызвана через o1, "this" принимает значение o1, и значения его свойств "a" и "b" отображаются. Значения "c" и "d" отображаются как undefined, поскольку o1 не определяет эти свойства. Аналогично, когда "UserDefinedFunction" была вызвана через o2, "this" принимает значение o2, и значения его свойств "c" и "d" отображаются; значения "a" и "b" отображаются как undefined, так как o2 не определяет эти свойства.
**Внутри косвенного вызова "непривязанной функции" через functionName.call и functionName.apply:
Когда "непривязанная функция" вызывается через functionName.call или functionName.apply, "текущий объект" (а следовательно, значение "this") устанавливается в значение параметра "this" (первый параметр), переданного в call/apply:
function UserDefinedFunction() { alert(this.a + "," + this.b + "," + this.c + "," + this.d); } var o1 = { a: 1, b: 2, f: UserDefinedFunction }; var o2 = { c: 3, d: 4, f: UserDefinedFunction }; UserDefinedFunction.call(o1); // Выведет 1,2,undefined,undefined UserDefinedFunction.apply(o1); // Выведет 1,2,undefined,undefined UserDefinedFunction.call(o2); // Выведет undefined,undefined,3,4 UserDefinedFunction.apply(o2); // Выведет undefined,undefined,3,4 o1.f.call(o2); // Выведет undefined,undefined,3,4 o1.f.apply(o2); // Выведет undefined,undefined,3,4 o2.f.call(o1); // Выведет 1,2,undefined,undefined o2.f.apply(o1); // Выведет 1,2,undefined,undefined
Данный код явно показывает, что значение "this" для любой "непривязанной функции" может быть изменено через call/apply. Также, если параметр "this" не был явно передан в call/apply, "текущий объект" (и следовательно, значение "this") установлен в "window" в нестрогом режиме и "undefined" в строгом режиме.
Внутри вызова "привязанной функции" (т.е. функции, которая была привязана с помощью functionName.bind):
Привязанная функция – это функция, значение "this" которой было зафиксировано. Следующий код демонстрирует, как работает "this" в случае привязанной функции:
function UserDefinedFunction() { alert(this.a + "," + this.b + "," + this.c + "," + this.d); } var o1 = { a: 1, b: 2, f: UserDefinedFunction, bf: null }; var o2 = { c: 3, d: 4, f: UserDefinedFunction, bf: null }; var bound1 = UserDefinedFunction.bind(o1); // указывает значение "this" для функции "bound1" как объект o1 bound1(); // Выведет 1,2,undefined,undefined var bound2 = UserDefinedFunction.bind(o2); // указывает значение "this" для функции "bound2" как объект o2 bound2(); // Выведет undefined,undefined,3,4 var bound3 = o1.f.bind(o2); // указывает значение "this" для функции "bound3" как объект o2 bound3(); // Выведет undefined,undefined,3,4 var bound4 = o2.f.bind(o1); // указывает значение "this" для функции "bound4" как объект o1 bound4(); // Выведет 1,2,undefined,undefined o1.bf = UserDefinedFunction.bind(o2); // указывает значение "this" для функции "o1.bf" как объект o2 o1.bf(); // Выведет undefined,undefined,3,4 o2.bf = UserDefinedFunction.bind(o1); // указывает значение "this" для функции "o2.bf" как объект o1 o2.bf(); // Выведет 1,2,undefined,undefined bound1.call(o2); // Всё равно выведет 1,2,undefined,undefined. "call" не может изменить значение "this" для привязанной функции bound1.apply(o2); // Всё равно выведет 1,2,undefined,undefined. "apply" не может изменить значение "this" для привязанной функции o2.bf.call(o2); // Всё равно выведет 1,2,undefined,undefined. "call" не может изменить значение "this" для привязанной функции o2.bf.apply(o2); // Всё равно выведет 1,2,undefined,undefined. "apply" не может изменить значение "this" для привязанной функции
Как указано в приведенном коде, "значение this для любой "привязанной функции" НЕ МОЖЕТ быть изменено через call/apply. Также, если параметр "this" не был явно передан в bind, "текущий объект" (и следовательно, значение "this") установлен в "window" в нестрогом режиме и "undefined" в строгом режиме. Ещё одно замечание: привязка уже привязанной функции не меняет значение "this". Оно остается установленным тем же, что было установлено первой функцией bind.
При создании объекта с помощью "new":
Внутри функции-конструктора, "текущий объект" (и следовательно, значение "this") ссылается на объект, который в настоящее время создается с помощью "new", независимо от статуса привязки функции. Однако если конструктор является привязанной функцией, он будет вызван с заранее определенным набором аргументов, установленным для привязанной функции.
**Внутри встроенного обработчика событий
Как получить правильный `this` внутри колбэка?
В чем разница между String.slice и String.substring?
Проверка соответствия строки регулярному выражению в JS
Существует ли ссылка на "последнюю" библиотеку jQuery в Google APIs?
Как создать диалог с кнопками "Ок" и "Отмена"