Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Динамический "this"

  1. Вызов метода
  2. Вызов в режиме обычной функции
  3. Вызов функции с new
  4. Передача this при помощи call/apply
    1. call
    2. apply
    3. call/apply(null/undefined)
  5. Итого

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

Вспомним «стандартное» поведение this. Обычно, когда функция вызывается в контексте объекта, то этот объект доступен как this:

var user = {
  firstName: "Вася",
 
  sayHi: function() { 
    alert( this.firstName ); // при вызове user.sayHi() будет this = user
  }
}

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

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

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

function func() { 
  alert( *!*this.firstName*/!* );
}

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

Вызов метода

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

var user = { 
  firstName: "Вася" 
};

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

*!*
user.sayHi = func; 
*/!*

user.sayHi();  // this = user

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

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

var user = { firstName: "Вася" };
var admin = { firstName: "Админ" };

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

*!*
user.a = func;  // присвоим одну функцию в свойства
admin.b = func; // двух разных объектов user и admin

user.a(); // Вася
admin.b(); // Админ
*/!*

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

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

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

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

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

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

func();

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

Контекст вызова и выражение

Как вы думаете, эти вызовы obj.go работают одинаково?

obj = {
  go: function() { alert(this) }
};

obj.go(); 

(obj.go)();  

(a = obj.go)(); 

(0 || obj.go)();

.. При запуске легко видеть - не одинаково. Почему?

Чтобы быть переданным в this, значение слева от точки должно быть объектом, но не выражением. Допустимы варианты obj.a или obj['a'], но ничего более сложного.

Скобки не влияют на это, так как операция получения свойства obj.go возвращает значение специального внутреннего типа Reference Type, который содержит информацию о go и obj. При любой операции Reference Type теряется и остаётся информация только о go.

Поэтому любое другое выражение, такое как (a = obj.method)() или (a.method || b.method)() интерпретируется как два шага: a = obj.method, а затем вызов без контекста a().

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

obj = {
  go: function() { alert(this) }
}

(obj.go || 0)()

P.S. Решение неожиданное, в задаче - «подводный камень»

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

Ошибка! Попробуйте:

obj = {
  go: function() { alert(this) }
}

(obj.go || 0)()  // error!

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

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

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

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

Интерпретатор пытается вызывать объект { go: ... } как функцию, что и дает ошибку.

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

obj = {
  go: function() { alert(this); }
}*!*;*/!*

(obj.go || 0)();

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

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

obj = {
  go: function() { alert(this); }
};

var f = obj.go || 0; // эти две строки - аналог (obj.go || 0)();
f();  // window

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

arr = ["a", "b"];

arr.push( function() { alert(this); } )

arr[2]();  // ?

Решение
Решение

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

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

arr = ["a", "b"];

arr.push( function() { alert(this); } )

arr[2](); // "a","b",function

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

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

Передача this при помощи call/apply

Функцию можно вызвать, явно указав значение this. Для этого у неё есть два метода: call и apply.

call

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

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

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

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

var user = { firstName: "Вася" };

function send(who, message) { 
  alert( 'от ' + this.firstName + ': ' + who + ', ' + message );
}

*!*
send.call(user, 'Петя', 'Привет')  // "от Вася: Петя, Привет"
*/!*

Вызов func.call(_this, arg1, arg2...) — то же, что обычный вызов func(arg1, arg2...), но с дополнительно указанным контекстом _this.

Еще пример:

var user = { 
  firstName: "Василий",
  surname: "Петров"
};

function getName(a, b) { 
  alert( this[a] + ' ' + this[b] )
}

*!*
getName.call(user, 'firstName', 'surname')  // "Василий Петров"
*/!*
Здесь функция getName вызвана с контекстом this = user и выводит user['firstName'] и user['surname'].

apply

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

func.call(_this, arg1, arg2...)
// то же что и:
func.apply(_this, [arg1, arg2 ... ]);

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

getName.call(user, 'firstName', 'surname'); 

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

Использование массива вместо перечисления аргументов дает больше возможностей. Например, можно сформировать массив аргументов динамически:

var args = [];
args.push('firstName');
args.push('surname');

func.apply(user, args)

call/apply(null/undefined)

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

function f() { alert(this) }

f.call(null); // window

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

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

function f() { 
  "use strict";

  alert(this); // null, "как есть"
}

f.call(null);

Итого

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

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

Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!