Конспект JS-course

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

Источник: http://learn.javascript.ru/this

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

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

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

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

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

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

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

Есть несколько правил, по которым JavaScript устанавливает 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.

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

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:

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

function showName() {
  alert(this.firstName + ' ' + this.lastName);
}

showName.call(user)  // "Василий Петров"

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

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

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

getName.call(user, 'firstName', 'surname')  // "Василий Петров"

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

Метод apply

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

f.call(context, 1, 2, 3);

..А что, если мы захотим вызвать функцию с четырьмя аргументами? А что, если количество аргументов заранее неизвестно, и определяется во время выполнения? Для решения этой задачи существует метод apply.

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

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

// то же что и:

func.apply(context, [arg1, arg2 ... ]);

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

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

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

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

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

func.apply(user, args); // вызовет func('firstName', 'surname') c this=user

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

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

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

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

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

function sayHi() {
  arguments.join = [].join; // одолжили метод (1)

  var argStr = arguments.join(':');  // (2)

  alert(argStr);  // сработает и выведет 1:2:3
}

sayHi(1, 2, 3);

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

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

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

function sayHi() {

  var join = [].join; // ссылка на функцию теперь в переменной

  // вызовем join с this=arguments,
  // этот вызов эквивалентен arguments.join(':') из примера выше
  var argStr = join.call(arguments, ':');

  alert(argStr);  // сработает и выведет 1:2:3
}

sayHi(1, 2, 3);

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

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

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

sayHi(1,2);

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

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

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

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

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

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

Итого

Значение 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, ...)