От автора: иногда бывает сложно объяснить понятие ключевого слова JavaScript this, а также для чего оно нужно. Но существует пять общих правил, с помощью которых можно определить рамки this. Есть случаи, которые не подходят под эти правила, но они покрывают большую часть… приступим!
Значение this обычно определяется контекстом выполнения функций. Контекст выполнения – это то, как функция вызывается.
Важно знать, что this может быть разным (отсылать на что-то другое) для каждого вызова функции.
Ничего страшного, если №1 и №2 пока что вам непонятны. Они станут понятны к концу статьи.
№1 Глобальный объект
Так, определение мы теперь знаем. Давайте вернемся на минуточку. Откройте Chrome Developer Console (Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J) и введите следующее:
1 |
console.log(this); |
Что получится?
1 |
// Window {...} |
Объект window! Потому что в глобальной области видимости this отсылает к глобальному объекту. В браузере глобальный объект это window.
Чтобы вы лучше поняли, почему this отсылает к объекту window, давайте разберем его подробнее. Создайте в консоли новую переменную и присвойте ей имя:
1 |
var myName = 'Brandon'; |
Теперь к этой переменной можно обращаться через:
1 2 |
myName // returns -> 'Brandon' |
Но знали ли вы, что все переменные, объявленные в глобальной области видимости, привязаны к объекту window? Проверим это:
1 2 3 4 5 |
window.myName // returns -> 'Brandon' window.myName === myName // returns -> true |
Круто. То есть раньше когда мы запустили console.log(this) в глобальном контексте, вы знали, что this вызывается на глобальном объекте. А так как в браузере глобальный объект это this, то теперь понятно, почему:
1 2 |
console.log(this) // returns -> window{...} |
Теперь поместим this внутрь функции. Вспомните определение: значение this обычно определяется тем, как вызывается функция. Как думаете, что возвращает функция? Скопируйте код ниже в консоль браузера и выполните его.
1 2 3 4 |
function test() { return this; } test() |
И опять ключевое слово this возвращает глобальный объект (window). Все потому что this не внутри объявленного объекта, поэтому берется объект по умолчанию (window). Пока что концепция может показаться сложно, но по мере прочтения статьи все станет понятнее. Замечание – если вы не используете строгий режим, то this в примере выше будет undefined.
№2 Объявленный объект
Когда ключевое слово this используется внутри объявленного объекта, значение this устанавливается к ближайшему родительскому объекту, на котором вызван метод. Посмотрите код ниже, где я определил объект person и использовал this внутри метода full
1 2 3 4 5 6 7 8 9 10 |
var person = { first: 'John', last: 'Smith', full: function() { console.log(this.first + ' ' + this.last); } }; person.full(); // logs => 'John Smith' |
Чтобы лучше проиллюстрировать связь this с объектом person, скопируйте код ниже в консоль браузера. Код почти как сверху, мы просто выводим console.log(this), чтобы посмотреть что вернется.
1 2 3 4 5 6 7 8 9 10 |
var person = { first: 'John', last: 'Smith', full: function() { console.log(this); } }; person.full(); // logs => Object {first: "John", last: "Smith", full: function} |
Как видите, консоль возвращает объект person, что подтверждает, что this приняло значение person.
И еще одно, прежде чем мы продолжим. Помните, что мы сказали, что значение this устанавливается в ближайший родительский объект вызываемого метода? А что будет, если объекты вложены? Посмотрите код ниже. Там у нас объект person с такими же ключами first, last и full. Однако в этот раз мы вложили объект personTwo. У personTwo те же три ключа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var person = { first: 'John', last: 'Smith', full: function() { console.log(this.first + ' ' + this.last); }, personTwo: { first: 'Allison', last: 'Jones', full: function() { console.log(this.first + ' ' + this.last); } } }; |
Что будет, если вызвать два метода full? Узнаем.
1 2 3 4 5 |
person.full(); // logs => 'John Smith' person.personTwo.full(); // logs => 'Allison Jones' |
Значение this устанавливается в ближайший родительский объект вызываемого метода. При выполнении person.full() внутри функции this ограничен объектом person. Однако при вызове person.personTwo.full() внутри функции this ограничен уже объектом personTwo!
№3 Ключевое слово new
При использовании ключевого слова new (конструктор), this ограничен новым создаваемым объектом.
Разберем пример:
1 2 3 4 |
function Car(make, model) { this.make = make; this.model = model; }; |
Вы можете подумать, что this ограничен глобальным объектом – и вы будете правы… пока мы не добавим ключевое слово new. Если использовать new, то значение this устанавливается в пустой объект. В нашем случае myCar.
1 2 3 4 |
var myCar = new Car('Ford', 'Escape'); console.log(myCar); // logs => Car {make: "Ford", model: "Escape"} |
Нужно понять, что делает слово new. Это совершенно новая тема. Пока что просто если видите new, знайте, что this отсылает к новому пустому объекту.
№4 Call, Bind, Apply
Последнее, но не менее важное, мы можем явно задавать значение this через call(), bind() и apply(). Три метода очень похожи, но важно понять их различия.
Call и Apply выполняются сразу же. Call принимает любое количество параметров: this, после которого следуют дополнительные аргументы. Apply принимает только два параметра: this, после которого идет массив дополнительных аргументов.
Вы еще здесь? Пример все прояснит. Посмотрите на код ниже, мы пытаемся сложить числа. Скопируйте его в консоль браузера и вызовите функцию.
1 2 3 4 5 6 |
function add(c, d) { console.log(this.a + this.b + c + d); } add(3,4); // logs => NaN |
Функция add логирует NaN (не число). Потому что this.a и this.b не определены. Они не существуют. Вы не можете сложить число с чем-то неопределенным.
Введем в уравнение объект. С помощью call() и apply() мы можем вызвать функцию с нашим объектом:
1 2 3 4 5 6 7 8 9 10 11 |
function add(c, d) { console.log(this.a + this.b + c + d); } var ten = {a: 1, b: 2}; add.call(ten, 3, 4); // logs => 10 add.apply(ten, [3,4]); // logs => 10 |
Когда мы используем add.call(), this должен быть ограничен первым параметром. Остальные параметры передаются в вызываемую функцию. Поэтому внутри add() this.a отсылает к ten.a и ten.b, а нам возвращается 1+2+3+4 или 10.
Add.apply() работает так же. Первый параметр ограничивает this. Второй параметр – массив аргументов для функции.
А Bidn? Параметры в bind() идентичны call(), но bind() не выполняется незамедлительно. bind() возвращает функцию с контекстом this. Поэтому bind() полезен, когда мы не знаем все аргументы. Пример поможет прояснить:
1 2 3 4 5 6 7 8 9 |
var small = { a: 1, go: function(b,c,d){ console.log(this.a+b+c+d); } } var large = { a: 100 } |
Скопируйте код выше в консоль и выполните следующее.
1 2 |
small.go(2,3,4); // logs 1+2+3+4 => 10 |
Круто. Все по старому. А что если использовать значение large.a? Можно использовать call/apply:
1 2 |
small.go.call(large,2,3,4); // logs 100+2+3+4 => 109 |
А что если мы еще не знаем все три аргумента? Можно взять bind:
1 |
var bindTest = small.go.bind(large,2); |
Если вывести в консоли console.log нашу переменную выше bindTest, мы увидим, с чем работаем
1 2 |
console.log(bindTest); // logs => function (b,c,d){console.log(this.a+b+c+d);} |
Помните, что с bind возвращается функция, у которой уже есть свой this! Поэтому наш this ограничен объектом large. Мы также передали наш второй аргумент 2. Как только мы узнаем остальные аргументы, мы можем их передать:
1 2 |
bindTest(3,4); // logs 100+2+3+4 => 109 |
Весь код одним блоком. Посмотрите его и скопируйте в консоль, чтобы разобраться!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var small = { a: 1, go: function(b,c,d){ console.log(this.a+b+c+d); } } var large = { a: 100 } small.go(2,3,4); // logs 1+2+3+4 => 10 var bindTest = small.go.bind(large,2); console.log(bindTest); // logs => function (b,c,d){console.log(this.a+b+c+d);} bindTest(3,4); // logs 100+2+3+4 => 109 |
№5 Стрелочные функции
Это настолько большая тема, что я написал целую статью Arrow Functions for Beginners
Заключение
У вас получилось! Теперь в большинстве случаев вы сможете понять, к чему отсылает this! Запомните пару вещей:
Значение this обычно определяется контекстом вызова функций.
В глобальной области видимости this отсылает к глобальному объекту (window).
При использовании new (конструктор), this ограничен новым создаваемым объектом.
Значение this можно явно задать через call(), bind() и apply()
Стрелочные функции не привязывают this – this ограничивается лексически (т.е. по оригинальному контексту)
Автор: Brandon Morelli
Источник: //codeburst.io/
Редакция: Команда webformyself.