Это мои заметки о this Теперь все имеет смысл!, второй главе Кайла Симпсона в книге Вы не знаете Javascript: это и прототипы объектов.

Call-сайт

сайт вызова – это место в коде, где вызывается функция (а не там, где она объявлена).

Стек вызовов – это стек функций, которые были вызваны для перехода к текущему моменту выполнения.

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

Вы можете визуализировать стек вызовов в уме как цепочку вызовов функций по порядку. Например:

function baz() {
  // call-stack is: `baz`
  // so, our call-site is in the global scope
  
  console.log( "baz" );
  bar(); // <-- call-site for `bar`
}
function bar() {
  // call-stack is: `baz` -> `bar`
  // so, our call-site is in `baz`
  
  console.log( "bar");
}
baz(); // <-- call-site for `baz`

Вы можете легко найти сайт вызова, поместив debugger; в первую строку рассматриваемой функции, а затем проверив встроенные инструменты разработчика вашего браузера. Отладчик JS покажет сайт вызова во втором элементе сверху стека вызовов.

Ничего, кроме правил

Привязка по умолчанию

Привязка по умолчанию для this указывает на глобальный объект (если содержимое функции с this не выполняется в strict mode).

Неявная привязка

Когда функция с this принадлежит объекту, правило неявной привязки говорит, что объект — это то, что используется для привязки this вызова функции. Этот объект обычно называют «контекстным объектом».

Только верхний/последний уровень цепочки ссылок на свойства объекта имеет значение для call-site. Например:

function foo() {
  console.log( this.a );
}
var obj2 = {
  a: 42,
  foo: foo
};
var obj1 = {
  a: 2,
  obj2: obj2
};
obj1.obj2.foo(); // 42

Одна из распространенных проблем с привязкой this возникает, когда неявно связанная функция теряет эту привязку и возвращается к привязке по умолчанию. Например:

function foo() {
  console.log( this.a );
}
var obj = {
  a: 2,
  foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"

Важно помнить, что obj.foo — это ссылка на функцию foo. Назначение obj.foo функции bar — это просто присвоение ссылки функции foo функции bar. Неявная привязка foo к obj теряется, и поэтому глобальное свойство a регистрируется в консоли.

Это также часто происходит при передаче функции обратного вызова. Если бы у нас было:

function doFoo(fn) {
  fn(); // <-- call-site!
}

И прошел obj.foo так:

dooFoo( obj.foo );

"oops, global" будет зарегистрирован в консоли. fn — это еще одна ссылка на foo.

То же самое происходит при использовании функции, встроенной в язык;

setTimeout( obj.foo, 100 ); // "oops, global"

Одним из распространенных реальных примеров неявно потерянной привязки является среда ReactJS. Обработчик событий в теле класса, который передается в JSX, потеряет свою неявную привязку, если вы не привяжете обработчик жестко в конструкторе класса или не определите обработчик с помощью функции стрелки. Более подробно вы можете прочитать об этом в моих заметках в туториале по ReactJS на сайте egghead.io.

Явная привязка

Вы можете прямо указать, что вы хотите, чтобы ваш this был с помощью методов call(..) и apply(..). Они берут объект для использования в качестве this в первом параметре, а затем вызывают функцию с указанным this. Это называется явной привязкой.

Например:

function foo() {
  console.log( this.a );
}
var obj = {
  a: 2
};
foo.call(obj); // 2

Жесткая привязка — это разновидность явной привязки, при которой вы присваиваете явную привязку (с помощью call(..) или apply(..)) ссылке на функцию.

Например:

function foo() {
  console.log( this.a );
}
var obj = {
  a: 2
};
var bar = function() {
  foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2

bar жестко связывает this foo с obj.

Жесткая привязка — настолько распространенный шаблон, что он предоставляется как встроенная утилита начиная с ES5: Function.prototype.bind.

bind(..) возвращает новую функцию, которая жестко запрограммирована для вызова исходной функции с указанным контекстом this.

Например:

function foo(something) {
  console.log( this.a, something );
  return this.a + something;
}
var obj = {
  a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

new Привязка

В JS конструкторы — это просто функции, которые вызываются с оператором new перед ними. Они не привязаны к классам, не создают экземпляры классов и не являются специальными типами функций. Это обычные функции, которые перехватываются использованием new в их вызове. Любые функции могут быть вызваны с new перед ним, что делает его вызовом конструктора.

Когда функция вызывается с new перед ней, автоматически выполняются следующие действия:

  1. совершенно новый объект создается (иначе, конструируется) из воздуха
  2. новый созданный объект является [[Prototype]]-связанным
  3. вновь созданный объект устанавливается как привязка this для этого вызова функции
  4. если функция не вернет свой собственный альтернативный объект, newвызванный вызов функции автоматически вернет вновь созданный объект

Привязка new — это когда мы вызываем конструктор, а новый объект является привязкой для this. Например:

function foo(a) {
  this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2

Запустив bar; в консоли, вы увидите Object { a: 2 }.

Все по порядку

Это правила определения this по порядку старшинства.

  1. Если функция вызывается с new (новая привязка), this — это вновь созданный объект.
var bar = new foo()

2. Если функция вызывается с call или apply (явная привязка), даже внутри bind жесткой привязки this является явно указанным объектом.

var bar = foo.call( obj2 )

3. Если функция вызывается с «владеющим» или содержащим (контекстным) объектом, this является этим объектом контекста.

var bar = obj1.foo()

4. Если ничего из вышеперечисленного не применимо, this является объектом global или undefined в строгом режиме (привязка по умолчанию).

var bar = foo()

Исключения привязки

Если вы передаете null или undefined в качестве параметра привязки this для call, apply или bind, то к вызову применяется правило привязки по умолчанию.

Обычно apply(..) используется для распространения массивов значений в качестве параметров вызова функции. bind(..) также можно использовать для каррирования параметров. В ES6 есть оператор распространения ..., который делает то же самое без использования apply(..), но bind(..) по-прежнему необходим для каррирования.

Одним из способов избежать случайного изменения объекта global с правилом привязки по умолчанию является использование пустого объекта-заполнителя, такого как ø = Object.create(null).

Будьте осторожны с «косвенными ссылками» на функции. В этом случае применяется правило привязки по умолчанию. Например:

function foo() {
  console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

o.foo — это просто ссылка на функцию foo в глобальной области видимости, поэтому глобальная переменная a записывается в консоль.

Лексический this

Стрелочные функции принимают привязку this из объемлющей (функциональной или глобальной) области. Например:

function foo() {
  return (a) => {
    console.log( this.a );
  };
}
var obj1 = {
  a: 2
};
var obj2 = {
  a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3!

Стрелочная функция лексически захватывает все foo()s this во время вызова. bar this связан с obj1, так как foo() был this связан с obj1 с call(..). Лексическая привязка стрелочной функции не может быть переопределена.

Программа может эффективно использовать как лексический, так и this стиль кода, но смешивание двух механизмов внутри одной и той же функции усложняет сопровождение кода.