Ключевое слово this в JavaScript

Понятие "this"

This — это ключевое слово, используемое в JavaScript, которое имеет особое значение, зависящее от контекста в котором оно применяется.

Иначе, This — неявный объект контекста, доступный из кода, обеспечивающий возможность применения данного кода для разных объектов.

ВАЖНО! Почему предпочтительно использовать this?

Отсюда...

Технически возможно получить доступ к объекту без ключевого слова this, ссылаясь на него через внешнюю переменную (в которой хранится ссылка на этот объект):

Но если мы решим скопировать ссылку на объект user в другую переменную, например, admin = user, и перезапишем переменную user, тогда при вызове метода на admin будет осуществлён доступ к неправильному объекту:

Так как объекты хранятся и копируются «по ссылке» (подробнее...), переменная хранит не сам объект, а его «адрес в памяти», другими словами «ссылку» на него. Когда переменная объекта копируется – копируется ссылка, сам же объект не дублируется.

И если мы используем this.name вместо user.name внутри console.log(), тогда вышеприведенный код будет работать.

[свернуть]

This можно считать динамическим ключевым словом: контекст всегда является значением ключевого слова this, которое ссылается на объект, «владеющий» кодом, выполняемым в текущий момент. Однако, тот контекст, который имеет отношение к this, это не то же самое, что область видимости (подробнее...). В общем случае, контекст вызова функции содержит область видимости функции, все её переменные и this.

Итак, когда мы пользуемся ключевым словом this, то обращаемся с его помощью к некоему объекту, которым может являться:

  1. объект window;
  2. объект, внутри которого находится this;

Значение this:

  • при вызове функции (в т.ч. вложенной):
    1. в нестрогом режиме - глобальный объект window;
    2. в строгом режиме ("use strict") - undefined;
  • при вызове метода, принадлежащего объекту:
    1. в общем случае - объект, которому принадлежит метод (справедливо и для вложенных объектов);
    2. если метод передаётся в качестве аргумента в другую функцию (например, setTimout(object.method) - глобальный объект window или undefined (нестрогий или строгий режим) (метод отделяется от объекта);
  • при создании объекта с помощью функции-конструктора (с new):
    1. объект, созданный при вызове конструктора; 
  • при попытке создания объекта с помощью функции-конструктора (без new):
    1. в нестрогом режиме - глобальный объект window;
    2. в строгом режиме ("use strict") - undefined;

This указывает на объект window

Ключевое слово this указывает на объект window:

  1. при обращении к ключевому слову this в глобальной области видимости;
  2. при вызове функций (не методов объектов);
  3. при вызове функции-конструктора без new;
  4. при вызове метода, принадлежащего объекту, если метод передаётся в качестве аргумента в другую функцию (например, setTimout(object.method)).

Примеры:

При обращении к ключевому слову this в глобальной области видимости

[свернуть]
При вызове функций (не методов объектов)

[свернуть]
При вызове функции-конструктора без new

[свернуть]

При вызове метода, принадлежащего объекту, если метод передаётся в качестве аргумента в другую функцию

[свернуть]

В строгом режиме работы функции (‘use strict’) при запуске функции в глобальном окружении this равно undefined (а не window).

This указывает на объект, внутри которого находится

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

Например, если обратиться к методу res объекта user,  ключевое слово this ссылается на сам объект user:

Использование this во вложенных объектах

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

При использовании приёма chaining (или цепочка), т.е. при вызове методов объекта через точку, строя цепочку вызовов someObject.method1().method2().method3().method1().method3()..., функция (метод объекта) всегда должна возвращать этот объект с использованием конструкции return this.

This в функциях

Использование this в обычных функциях в строгом режиме ( 'use strict' )

В обычной функции this всегда представляет собой объект:

  • либо это непосредственно объект, в случае вызова с this, представляющим объект-значение;
  • либо значение, упакованное в объект, в случае вызова с this типа Booleanstring, или number;
  • либо глобальный объект window, если тип this это undefined или null.

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

Для точного определения конкретного this  необходимо использовать callapply, или bind.

Значение, передаваемое в функцию как this, в строгом режиме не приводится к объекту (не "упаковывается"), таким образом, если функция выполняется в строгом режиме, то в this будет записано undefined, так как в этом режиме запрещены привязки по умолчанию. 

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

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

Функция chase, объявленная за пределами объекта dog, при попытке её вызова как самостоятельной функции, через this будет будет указывать на глобальный объект, в котором нет тех свойств, к которым мы обращаемся через this.

Особенности this в стрелочных функциях

Утверждение о том, что при обращении к this в методе объекта этому ключевому слову соответствует объект, которому принадлежит метод, не относится к стрелочным функциям.

Стрелочные функции (arrow functions) являются исключением из правила в случае определения значения this: их this всегда лексический (статический), а не динамический. Т.е. их функциональная запись окружения не предоставляет значение this, и оно наследуется из родительского окружения.

Таким образом, в отношении стрелочных функций действует правило:

  • this будет таким, каким он был на момент создания стрелочной функции, т.е. захватывается из текущего контекста (и вовсе не обязательно это будет window).

Стрелочные функции не содержат собственный контекст this, а используют значение this окружающего контекста. 

Примеры:

This в стрелочной функции - глобальный объект window

[свернуть]
This в стрелочной функции - объект, в котором она находится

[свернуть]

Так как значение this определяется лексикой:

  1. правила строгого режима (strict mode) относительно this игнорируются;
  2. вызов стрелочных функций с помощью методов call() или apply(), даже если передать аргументы в эти методы, не влияет на значение this.

This в функциях-конструкторах

Обычный литерал {...} позволяет создать только один объект, но часто требуется создать несколько однотипных объектов (содержащих, например, данные пользователей или настройки элементов меню и т.д.). Для этого используются  функции-конструкторы и оператор new.

Функция-конструктор - это обычная функция, с помощью которой создаются однотипные объекты. В синтаксисе функции-конструктора применяются следующие соглашения:

  1. именование функции-конструктора должно начинаться с заглавной (прописной) буквы;
  2. функция-конструктор должна вызываться при помощи оператора new.

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

При вызове функции-конструктора с использованием ключевого слова new ключевое слово this указывает на новый объект, который, после некоторой работы над ним, будет возвращён из этой функции.

Когда функция вызывается как new Pilot(...), движок выполняет следующее:

  1. создаётся новый пустой объект, который присваивается this;
  2. выполняется код функции (обычно он модифицирует объект, переданный в this: добавляет или изменяет его  свойства);
  3. возвращается значение this.

Таким образом, используя единственную функцию-конструктор, с помощью ключевого слова this можно создавать множество однотипных объектов Pilot. Это позволяет масштабировать приложение и сокращать дублирование кода.

Ещё примеры можно посмотреть здесь...

Потеря контекста (потеря this)

Потеря контекста при неверном вызове метода объекта

Пример отсюда...

Некоторые способы вызова метода приводят к потере значения this, например:

В последней строчке кода используется условный (тернарный) оператор ?, который определяет, какой будет вызван метод (user.hi или user.bye) в зависимости от выполнения условия. Т.к. user.name == "Alex", то будет выбран user.hi, который сразу же вызывается с помощью скобок (). При вызове вернется ошибка, т.к. в данном случае в строгом режиме значением this внутри функции становится undefined.

Подробнее о работе вызова метода вида obj.method()

В выражении obj.method() можно заметить две операции:

  1. оператор точка '.' возвращает свойство объекта – его метод obj.method (если вывести obj.method в консоль - это будет код метода, т.е. код функции);
  2. скобки () вызывают этот метод (исполняется код метода).

Каким же образом информация о this передаётся из первой части во вторую?

Если мы поместим указанные выше операции ( '.' и () ) в отдельные строки, то значение this будет потеряно:

Здесь hi = user.hi сохраняет функцию метода объекта user в переменной как отдельный объект (отдельную функцию, не привязанную к объекту user). Поэтому, если её далее вызывать hi(), то она вызывается в глобальном контексте, при этом в строгом режиме значением this внутри функции становится undefined (в обычном режиме значением this станет глобальный объект Window).

Для сохранения значения this при вызове метода объекта используется синтаксис obj.method(), который корректно передает this.

При вызове типа obj.method()  точка '.' возвращает не саму функцию, а специальное значение «ссылочного типа», называемого Reference Type. Этот ссылочный тип (Reference Type) является внутренним типом, который используется внутри движка.

Значение ссылочного типа – это «триплет»: комбинация из трёх значений (base, name, strict), где:

  1. base – это объект;
  2. name – это имя свойства объекта;
  3. strict – это режим исполнения (true, если действует строгий режим use strict).

Таким образом, результатом доступа к свойству user.hi при использовании синтаксиса obj.method() является не функция, а значение ссылочного типа, которое в строгом режиме будет таким:

Когда скобки () применяются к значению ссылочного типа (происходит вызов), то они получают полную информацию об объекте и его методе, и могут поставить правильный this по значению base (в данном случае base = user).

Ссылочный тип – исключительно внутренний, промежуточный, используемый чтобы передать информацию от точки . до вызывающих скобок (). При любой другой операции, например, присваивании hi = user.hi, ссылочный тип заменяется на собственно значение user.hi (т.е. функцию без привязки к объекту), и дальше работа уже идёт только с ней.

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

  • точечной нотации obj.method() или
  • скобочной нотации obj['method']()

Для сохранения значения this можно применить, например, методы CALL(), APPLY() и BIND(), которые рассмотрены ниже. Например:

[свернуть]

Потеря контекста при вызове функции, вложенной в метод объекта (внутренней функции)

Обычной ошибкой при работе с вызовом функции является уверенность в том, что this во внутренней функции такой же, как и во внешней. Однако, контекст внутренней функции зависит только от вызова, а не от контекста внешней функции, например:

Для решения проблемы потери контекста вложенной функцией calculate() она должна быть исполнена в том же контексте, что и метод sum. Это можно сделать при помощи методов явного указания контекста call(), apply() и bind().

Пример

[свернуть]

Методы вызова функции с указанным значением this ( call(), apply() и bind() )

Вызов функции с указанным значением this и аргументами в виде списка (метод call() )

Метод call()

Метод call() вызывает функцию с указанным значением this и индивидуально предоставленными аргументами в виде списка.

Функция call() принимает список аргументов, функция apply() - одиночный массив аргументов (или массивоподобный объект).

Параметры метода call():

  • thisArg - значение this, предоставляемое для вызова функции fun:
    1. именование объекта;
    2. если метод является функцией в нестрогом режиме, значения null и undefined будут заменены глобальным объектом, а примитивные значения будут упакованы в объекты.
  • arg1, arg2, ... - аргументы для объекта.

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

Ещё примеры

Ещё пример:

[свернуть]

[свернуть]
Вызов функции с указанным значением this и аргументами в виде массива (метод apply() )

Метод apply ()

Метод apply() вызывает функцию с указанным значением this и аргументами, предоставленными в виде массива (либо массивоподобного объекта).

ВАЖНО! хотя синтаксис этой функции практически полностью идентичен функции call(), фундаментальное различие между ними заключается в том, что функция call() принимает список аргументов, в то время, как функция apply() принимает единичный массив аргументов.

Параметры метода apply():

  • thisArg (опциональный параметр) - значение this, предоставляемое для вызова функции fun:
    1. именование объекта;
    2. если метод является функцией в нестрогом режиме, значения null и undefined будут заменены глобальным объектом, а примитивные значения будут упакованы в объекты.
  • argsArray (опциональный параметр):
    1. массивоподобный объект, определяющий аргументы, с которыми функция fun должна быть вызвана, либо
    2. null или undefined, если в функцию не надо передавать аргументы.

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

Метод apply() очень похож на метод call(), за исключением поддерживаемого типа аргументов. Вместе с apply() вы можете использовать:

  • литерал массива, например, fun.apply(this, ['есть', 'бананы']), либо
  • объект Array, например, fun.apply(this, new Array('есть', 'бананы'));
  • псевдомассив arguments (для передачи всех аргументов в вызываемый объект);
  • любой вид массивоподобного объекта (который должен иметь свойство length и целочисленные свойства в диапазоне 0...length), например, NodeList или любой объект вида { 'length': 2, '0': 'есть', '1': 'бананы' }.

Интересные примеры

[свернуть]
Установка в качестве this предоставленного значения (без вызова) (метод bind() )

Метод bind()

Метод bind() создаёт новый функциональный объект - новую "привязанную функцию" (ПФ), которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. Результатом вызова fun.bind(thisArg) является особый «необычный объект» ПФ (термин взят из спецификации), который является оберткой над исходным функциональным объектом, вызывается как функция и прозрачно передаёт вызов в fun, при этом устанавливая this = thisArg.

This в "привязанной функции" -  это первый аргумент .bind().

Замечание по повторному bind()

Повторный вызов bind() в цепочке методов смысла не имеет, так как для функции контекст будет создан оберткой при вызове первого bind(), а последующие bind() будут создавать контекст уже для обертки, а не для функции (пример отсюда):

[свернуть]

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

Параметры метода bind():

  • thisArg - значение, передаваемое в качестве this в целевую функцию при вызове привязанной функции; игнорируется, если привязанная функция конструируется с помощью оператора new;
  • arg1, arg2, ... - аргументы исходной (оборачиваемой) функции, передаваемые перед аргументами привязанной функции при вызове исходной функции.

Метод bind() создаёт новую "привязанную функцию" (ПФ - "обёртку" над исходной функцией), которая подменяет контекст исходной функции. Поведение bind() похоже на call() и apply(), но, в отличие от них, bind() не вызывает функцию, а лишь возвращает "обёртку", которую можно вызвать позже. Вызов ПФ приводит к исполнению кода обернутой функции с подмененным контекстом. Т. о. результатом вызова fun.bind(thisArg) является особый «необычный объект» ПФ (термин взят из спецификации), который является оберткой над исходным функциональным объектом, вызывается как функция и прозрачно передаёт вызов в fun, при этом устанавливая this = thisArg.

Привязанная функция также может быть сконструирована с помощью оператора new: это работает так, как если бы вместо неё конструировалась целевая функция. Предоставляемое значение this в этом случае игнорируется, хотя ведущие аргументы всё ещё передаются в эмулируемую функцию.

Ещё примеры

Пример работы bind() с аргументами, передаваемыми в исходную функцию:

[свернуть]

[свернуть]

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.