Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Контекст this в деталях

Лекция



Привет, сегодня поговорим про контекст this в деталях, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое контекст this в деталях , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).


  1. Вызов функции с new
  2. Вызов в контексте объекта
  3. Вызов в режиме обычной функции
  4. Явное указание this: apply и call
    1. Метод call
    2. Метод apply
    3. «Одалживание метода»
    4. Делаем из arguments настоящий Array
    5. «Переадресация» вызова через apply
  5. Итого

Значение this в JavaScript не зависит от объекта, в котором создана функция. Оно определяется во время вызова.

Любая функция может иметь в себе this.

Совершенно неважно, объявлена она в объекте или вне него.

Значение this называется контекстом вызова и будет определено в момент вызова функции.

Например: такая функция вполне допустима:

function sayHi() {
  alert( this.firstName );
}

 

Эта функция еще не знает, каким будет this. Это выяснится при выполнении программы.

Есть несколько правил, по которым JavaScript устанавливает this.

Вызов функции с new

При вызове функции с new, значением this является новосоздаваемый объект. Мы уже обсуждали это в разделе о создании объектов.

Вызов в контексте объекта

Самый распространенный случай — когда функция объявлена в объекте или присваивается ему, как в примере ниже:

 

   
01 var user = {
02   firstName: "Вася"
03 };
04  
05 function func() {
06   alert( this.firstName );
07 }
08  
09 user.sayHi = func;
10  
11 user.sayHi();  // this = user

 

При вызове функции как метода объекта, через точку или квадратные скобки — функция получает вthis этот объект. В данном случае user.sayHi() присвоит this = user.

Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный this:

 

   
01 var user = { firstName: "Вася" };
02 var admin = { firstName: "Админ" };
03  
04 function func() {
05   alert( this.firstName );
06 }
07  
08 user.a = func;  // присвоим одну функцию в свойства
09 admin.b = func; // двух разных объектов user и admin
10  
11 user.a(); // Вася
12 admin['b'](); // Админ (не важно, доступ через точку или квадратные скобки)

 

Значение this не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.

Вызов в режиме обычной функции

Если функция использует this - это подразумевает работу с объектом. Но и прямой вызов func()технически возможен.

Как правило, такая ситуация возникает при ошибке в разработке.

При этом this получает значение windowглобального объекта.

 

   
1 function func() {
2   alert(this); // выведет [object Window] или [object global]
3 }
4  
5 func();

 

В современном стандарте языка это поведение изменено, вместо глобального объекта this будетundefined.

 

   
1 function func() {
2   "use strict";
3   alert(this); // выведет undefined (кроме IE<10)
4 }
5  
6 func();

 

…Но по умолчанию браузеры ведут себя по-старому.

Явное указание this: apply и call

Функцию можно вызвать, явно указав значение this.

Для этого у нее есть два метода: call и apply.

Метод call

Синтаксис метода call:

func.call(context, arg1, arg2,...)

 

При этом вызывается функция func, первый аргумент call становится ее this, а остальные передаются «как есть».

Вызов func.call(context, a, b...) — то же, что обычный вызов func(a, b...), но с явно указанным контекстом context.

Например, функция showName в примере ниже вызывается через call в контексте объекта user:

 

   
01 var user = {
02   firstName: "Василий",
03   lastName: "Петров"
04 };
05  
06 function showName() {
07   alert( this.firstName + ' ' this.lastName );
08 }
09  
10 showName.call(user)  // "Василий Петров"

 

Можно сделать ее более универсальной, добавив аргументы:

 

   
01 var user = {
02   firstName: "Василий",
03   surname: "Петров"
04 };
05  
06 function getName(a, b) {
07   alert( this[a] + ' ' this[b] );
08 }
09  
10 getName.call(user, 'firstName''surname')  // "Василий Петров"

 

Здесь функция getName вызвана с контекстом this = user и выводит user['firstName'] иuser['surname'].

Метод apply

Метод call жестко фиксирует количество аргументов, через запятую:

f.call(context, 1, 2, 3);
..А что, если мы захотим вызвать функцию с четырьмя аргументами? А что, если количество аргументов заранее неизвестно, и определяется во время выполнения?

 

Для решения этой задачи существует метод apply.

Вызов функции при помощи func.apply работает аналогично func.call, но принимает массив аргументов вместо списка:

 

func.call(context, arg1, arg2...)
// то же что и:
func.apply(context, [arg1, arg2 ... Об этом говорит сайт https://intellect.icu . ]);

 

Эти две строчки cработают одинаково:

getName.call(user, 'firstName''surname');
 
getName.apply(user, ['firstName''surname']);

 

Метод apply гораздо мощнее, чем call, так как можно сформировать массив аргументов динамически:

 

1 var args = [];
2 args.push('firstName');
3 args.push('surname');
4  
5 func.apply(user, args); // вызовет func('firstName', 'surname') c this=user

 

 

Вызов call/apply с null или undefined

При указании первого аргумента null или undefined в call/apply, функция получаетthis = window:

 

   
1 function f() { alert(this) }
2  
3 f.call(null); // window

 

Это поведение исправлено в современном стандарте (15.3).

Если функция работает в строгом режиме, то this передается «как есть»:

 

   
1 function f() {
2   "use strict";
3  
4   alert(this); // null, "как есть"
5 }
6  
7 f.call(null);

 

 

«Одалживание метода»

При помощи call/apply можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого.

В JavaScript методы объекта, даже встроенные — это функции. Поэтому можно скопировать функцию, даже встроенную, из одного объекта в другой.

Это называется «одалживание метода» (на англ. method borrowing).

Используем эту технику для упрощения манипуляций с arguments. Как мы знаем, это не массив, а обычный объект.. Но как бы хотелось вызывать на нем методы массива.

 

   
1 function sayHi() {
2   arguments.join = [].join; // одолжили метод (1)
3  
4   var argStr = arguments.join(':');  // (2)
5  
6   alert(argStr);  // сработает и выведет 1:2:3
7 }
8  
9 sayHi(1, 2, 3);

 

В строке (1) создали массив. У него есть метод [].join(..), но мы не вызываем его, а копируем, как и любое другое свойство в объект arguments. В строке (2) запустили его, как будто он всегда там был.

 

Почему вызов сработает?

Здесь метод join массива скопирован и вызван в контексте arguments. Не произойдет ли что-то плохое от того, что arguments — не массив? Почему он, вообще, сработал?

Ответ на эти вопросы простой. В соответствии со спецификацией, внутри joinреализован примерно так:

 

01 function join(separator) {
02   if (!this.length) return '';
03  
04   var str = this[0];
05  
06   for (var i = 1; i<this.length; i++) {
07     str += separator + this[i];
08   }
09    
10   return str;
11 }

 

Как видно, используется this, числовые индексы и свойство length. Если эти свойства есть, то все в порядке. А больше ничего и не нужно. Подходит даже обычный объект:

 

   
1 var obj = {  // обычный объект с числовыми индексами и length
2   0: "А",
3   1: "Б",
4   2: "В",
5   length: 3
6 };
7  
8 obj.join = [].join;
9 alert( obj.join(';') ); // "A;Б;В"

 

 

…Однако, прямое копирование метода не всегда приемлемо.

Представим на минуту, что вместо arguments у нас — произвольный объект, и мы хотим вызвать в его контексте метод [].join. Копировать этот метод, как мы делали выше, опасно: вдруг у объекта есть свой собственный join? Перезапишем, а потом что-то сломается..

Для безопасного вызова используем apply/call:

 

   
01 function sayHi() {
02   var join = [].join; // ссылка на функцию теперь в переменной
03  
04   // вызовем join с this=arguments,
05   // этот вызов эквивалентен arguments.join(':') из примера выше
06   var argStr = join.call(arguments, ':');
07  
08   alert(argStr);  // сработает и выведет 1:2:3
09 }
10  
11 sayHi(1, 2, 3);

 

Мы вызвали метод без копирования. Чисто, безопасно.

Делаем из arguments настоящий Array

В JavaScript есть очень простой способ сделать из arguments настоящий массив. Для этого возьмем метод массива: arr.slice(start, end).

По стандарту он копирует часть массива arr от start до end в новый массив. А если start и end не указаны, то копирует весь массив.

Вызовем его в контексте arguments:

 

   
1 function sayHi() {
2   // вызов arr.slice() скопирует все элементы из this в новый массив
3   var args = [].slice.call(arguments);
4   
5   alert( args.join(':') ); // args -- массив аргументов
6 }
7  
8 sayHi(1,2);

 

Как и в случае с join, такой вызов возможен потому, что slice использует от массива только нумерованные свойства и length. Все это в arguments есть.

«Переадресация» вызова через apply

При помощи apply мы можем сделать универсальную «переадресацию» вызова из одной функции в другую.

Например, функция f вызывает g в том же контексте, с теми же аргументами:

function f(a, b) {
  g.apply(this, arguments);
}

 

Плюс этого подхода — в том, что он полностью универсален:

  • Его не понадобится менять, если в f добавятся новые аргументы.
  • Если f является методом объекта, то текущий контекст также будет передан. Если не является — то this здесь вроде как не при чем, но и вреда от него не будет.

 

Важность: 5

Напишите функцию f, которая будет оберткой вокруг другой функции g. Функция f обрабатывает первый аргумент сама, а остальные аргументы передает в функцию g, сколько бы их ни было.

Например:

1 function f() { /* ваш код */ }
2  
3 function g(a, b, c) {
4   alert( a + b + (c || 0) );
5 }
6  
7 f("тест", 1, 2); // f выведет "тест", дальше g посчитает сумму "3"
8 f("тест2", 1, 2, 3); // f выведет "тест2", дальше g посчитает сумму "6"

 

Код функции f не должен зависеть от количества аргументов.

Решение

 

   
01 function f(a) {
02   alert(a);
03   var args = [].slice.call(arguments, 1);
04   g.apply(this, args);
05 }
06  
07 function g(a, b, c) {
08   alert( a + b + (c || 0) );
09 }
10  
11 f("тест", 1, 2);
12 f("тест2", 1, 2, 3);

 

[Открыть задачу в новом окне]

 

 

Важность: 3

Вызовы (1) и (2) в примере ниже работают не так, как (3) и (4):

 

   
01 "use strict"
02  
03 var obj, f;
04  
05 obj = {
06   go: function() { alert(this); }
07 };
08  
09 obj.go();            // (1) object
10  
11 (obj.go)();          // (2) object
12  
13 (f = obj.go)();      // (3) undefined
14  
15 (obj.x || obj.go)(); // (4) undefined

 

В чем дело? Объясните логику работы this.

Решение
  1. Обычный вызов функции в контексте объекта.
  2. То же самое, скобки ни на что не влияют.
  3. Здесь не просто вызов obj.method(), а более сложный вызов вида(выражение).method(). Такой вызов работает, как если бы он был разбит на две строки:
    f = obj.go; // сначала вычислить выражение
    f();             // потом вызвать то, что получилось
    При этом f() выполняется как обычная функция, без передачи this.
  4. Здесь также слева от точки находится выражение, вызов аналогичен двум строкам.

В спецификации это объясняется при помощи специального внутреннего типаReference Type.

Если подробнее — то obj.go() состоит из двух операций:

  1. Сначала получить свойство obj.go.
  2. Потом вызвать его как функцию.

Но откуда на шаге 2 получить this? Как раз для этого операция получения свойстваobj.go возвращает значение особого типа Reference Type, который в дополнение к свойству go содержит информацию об obj. Далее, на втором шаге, вызов его при помощи скобок () правильно устанавливает this.

Любые другие операции, кроме вызова, превращают Reference Type в обычный тип, в данном случае — функцию go (так уж этот тип устроен).

Поэтому получается, что (a = obj.go) присваивает в переменную a функцию go, уже без всякой информации об объекте obj.

Аналогичная ситуация и в случае (4): оператор ИЛИ || делает из Reference Typeобычную функцию.

[Открыть задачу в новом окне]

 

 

Важность: 2

Каков будет результат этого кода?

1 var obj = {
2   go: function() { alert(this) }
3 }
4  
5 (obj.go || 0)()

 

P.S. Есть подвох.

Решение
Решение, шаг 1

Ошибка!

Попробуйте:

   
1 var obj = {
2   go: function() { alert(this) }
3 }
4  
5 (obj.go || 0)()  // error!

 

Причем сообщение об ошибке - очень странное. В большинстве браузеров этоobj is undefined.

Дело, как ни странно, ни в самом объявлении obj, а в том, что после него пропущена точка с запятой.

JavaScript игнорирует перевод строки перед скобкой (obj.go || ..) и читает этот код как:

 

var obj = { go:... }(obj.go || 0)()

 

Интерпретатор попытается вычислить это выражение, которое обозначает вызов объекта { go: ... } как функции с аргументом (obj.go || 0). При этом, естественно, возникнет ошибка.

А что будет, если добавить точку с запятой?

 

1 obj = {
2   go: function() { alert(this); }
3 };
4  
5 (obj.go || 0)();

 

Все ли будет в порядке? Каков будет результат?

Решение, шаг 2

Результат — window, поскольку вызов obj.go || 0 аналогичен коду:

 

   
1 obj = {
2   go: function() { alert(this); }
3 };
4  
5 var f = obj.go || 0; // эти две строки - аналог (obj.go || 0)();
6 f();  // window

 

[Открыть задачу в новом окне]

 

 

Важность: 5

Каким будет результат? Почему?

1 arr = ["a""b"];
2  
3 arr.push( function() { alert(this); } )
4  
5 arr[2]();  // ?

 

Решение

Вызов arr[2]() — это обращение к методу объекта obj[method](), в роли objвыступает arr, а в роли метода: 2.

Поэтому, как это бывает при вызове функции как метода, функция arr[2] получитthis = arr и выведет массив:

 

   
1 arr = ["a""b"];
2  
3 arr.push( function() { alert(this); } )
4  
5 arr[2](); // "a","b",function

 

[Открыть задачу в новом окне]

 

Итого

Значение this устанавливается в зависимости от того, как вызвана функция:

При вызове функции как метода
obj.func(...)    // this = obj
obj["func"](...)
При обычном вызове
func(...)        // this = window
В new
new func()       // this = {} (новый объект)
Явное указание
func.apply(ctx, args) // this = ctx (новый объект)
func.call(ctx, arg1, arg2, ...)

К сожалению, в одной статье не просто дать все знания про контекст this в деталях. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое контекст this в деталях и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Ответы на вопросы для самопроверки пишите в комментариях, мы проверим, или же задавайте свой вопрос по данной теме.

создано: 2014-10-07
обновлено: 2021-03-13
132597



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)