- Ключевое слово this в javascript — учимся определять контекст на практике
- 1. Теория
- 2. Разбираем задачу
- Методы объекта, «this»
- Примеры методов
- Сокращённая запись метода
- Ключевое слово «this» в методах
- «this» не является фиксированным
- Внутренняя реализация: Ссылочный тип
- У стрелочных функций нет «this»
- Итого
- Привязка контекста (this) к функции в javascript и частичное применение функций
- Введение
- 1. jQuery.proxy
- 2. Function.prototype.bind
Ключевое слово this в javascript — учимся определять контекст на практике
По просьбам некоторых читателей решил написать топик про контекст в javascript. Новички javascript часто не понимают значение ключевого слова this в javascript. Данный топик будет интересен не только новичкам, а также тем, кто просто хочет освежить данный аспект в памяти. Посмотрите пример ниже. Если вы затрудняетесь ответить на вопрос «что будет выведено в логе» хотя бы в одном из пунктов или хотите просто посмотреть ответы — добро пожаловать под кат.
1. Теория
В отличие от многих других языков программирования ключевое слово this в javascript не привязывается к объекту, а зависит от контекста вызова. Для упрощения понимания будем рассматривать примеры применительно к браузеру, где глобальным объектом является window.
1.1. Простой вызов функции
В данном случае this внутри функции f равен глобальному объекту (например, в браузере это window, в Node.js — global).
Самовызывающиеся функции (self-invoking) работают по точно такому же принципу.
1.2. В конструкторе
При вызове функции с использованием ключевого слова new функция выступает в роли конструктора, и в данном случе this указывает на создаваемый объект.
1.3. В методе объекта
Если функция запускается как свойство объекта, то в this будет ссылка на этот объект. При этом не имеет значения, откуда данная функция появилась в объекте, главное — как она вызывается, а именно какой объект стоит перед вызовом функции:
1.4. Методы apply, call
Методы apply и call позволяют задать контекст для выполняемой функции. Разница между apply и call — только в способе передачи параметров в функцию. Первый параметр обеих функций определяет контекст выполнения функции (то, чему будет равен this).
2. Разбираем задачу
Применим полученные знания к приведенной в начале топика задаче. Опять же для упрощения будем рассматривать примеры применительно к браузеру, где глобальным объектом является window.
Источник
Методы объекта, «this»
Объекты обычно создаются, чтобы представлять сущности реального мира, будь то пользователи, заказы и так далее:
И так же, как и в реальном мире, пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п.
Такие действия в JavaScript представлены свойствами-функциями объекта.
Примеры методов
Для начала давайте научим нашего пользователя user здороваться:
Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию для приветствия, и присвоили её свойству user.sayHi нашего объекта.
Затем мы вызвали её. Теперь пользователь может говорить!
Функцию, которая является свойством объекта, называют методом этого объекта.
Итак, мы получили метод sayHi объекта user .
Конечно, мы могли бы заранее объявить функцию и использовать её в качестве метода, примерно так:
Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированное программирование или сокращённо: «ООП».
ООП является большой предметной областью и интересной наукой само по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это – создание архитектуры, и есть хорошие книги по этой теме, такие как «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или «Объектно-ориентированный анализ и проектирование с примерами приложений» Гради Буча, а также ещё множество других книг.
Сокращённая запись метода
Существует более короткий синтаксис для методов в литерале объекта:
Как было показано, мы можем пропустить ключевое слово «function» и просто написать sayHi() .
Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. В большинстве случаев сокращённый синтаксис предпочтителен.
Ключевое слово «this» в методах
Как правило, методу объекта необходим доступ к информации, которая хранится в объекте, чтобы выполнить с ней какие-либо действия (в соответствии с назначением метода).
Например, коду внутри user.sayHi() может понадобиться имя пользователя, которое хранится в объекте user .
Для доступа к информации внутри объекта метод может использовать ключевое слово this .
Значение this – это объект «перед точкой», который использовался для вызова метода.
Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).
Технически также возможно получить доступ к объекту без ключевого слова this , ссылаясь на него через внешнюю переменную (в которой хранится ссылка на этот объект):
…Но такой код будет ненадёжным. Если мы решим скопировать ссылку на объект user в другую переменную, например, admin = user , и перезапишем переменную user чем-то другим, тогда будет осуществлён доступ к неправильному объекту при вызове метода из admin .
Это показано ниже:
Если мы используем this.name вместо user.name внутри alert , тогда этот код будет работать.
«this» не является фиксированным
В JavaScript ключевое слово «this» ведёт себя иначе, чем в большинстве других языков программирования. Оно может использоваться в любой функции.
В этом коде нет синтаксической ошибки:
Значение this вычисляется во время выполнения кода и зависит от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» при вызовах:
Правило простое: при вызове obj.f() значение this внутри f равно obj . Так что, в приведённом примере это user или admin .
Мы даже можем вызвать функцию вовсе без использования объекта:
В строгом режиме ( «use strict» ) в таком коде значением this будет являться undefined . Если мы попытаемся получить доступ к name , используя this.name – это вызовет ошибку.
В нестрогом режиме значением this в таком случае будет глобальный объект ( window для браузера, мы вернёмся к этому позже в главе Глобальный объект). Это – исторически сложившееся поведение this , которое исправляется использованием строгого режима ( «use strict» ).
Обычно подобный вызов является ошибкой программирования. Если внутри функции используется this , тогда ожидается, что она будет вызываться в контексте какого-либо объекта.
Если вы до этого изучали другие языки программирования, тогда вы, скорее всего, привыкли к идее «фиксированного this » – когда методы, определённые внутри объекта, всегда сохраняют в качестве значения this ссылку на свой объект (в котором был определён метод).
В JavaScript this является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а зависит от того, какой объект вызывает метод (какой объект стоит «перед точкой»).
Эта идея вычисления this в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок.
Здесь мы не будем судить о том, является ли это решение в языке хорошим или плохим. Мы должны понимать, как с этим работать, чтобы получать выгоды и избегать проблем.
Внутренняя реализация: Ссылочный тип
Этот раздел объясняет сложную тему, чтобы лучше понимать некоторые запутанные случаи.
Если вы хотите продвигаться быстрее, его можно пропустить или отложить.
Некоторые хитрые способы вызова метода приводят к потере значения this , например:
В последней строчке кода используется условный оператор ? , который определяет, какой будет вызван метод ( user.hi или user.bye ) в зависимости от выполнения условия. В данном случае будет выбран user.hi .
Затем метод тут же вызывается с помощью скобок () . Но вызов не работает как положено!
Вы можете видеть, что при вызове будет ошибка, потому что значением «this» внутри функции становится undefined (полагаем, что у нас строгий режим).
Так работает (доступ к методу объекта через точку):
Так уже не работает (вызываемый метод вычисляется):
Почему? Если мы хотим понять, почему так происходит, давайте разберёмся (заглянем под капот), как работает вызов методов ( obj.method() ).
Присмотревшись поближе, в выражении obj.method() можно заметить две операции:
- Сначала оператор точка ‘.’ возвращает свойство объекта – его метод ( obj.method ).
- Затем скобки () вызывают этот метод (исполняется код метода).
Итак, каким же образом информация о this передаётся из первой части во вторую?
Если мы поместим эти операции в отдельные строки, то значение this , естественно, будет потеряно:
Здесь hi = user.hi сохраняет функцию в переменной, и далее в последней строке она вызывается полностью сама по себе, без объекта, так что нет this .
Для работы вызовов типа user.hi() , JavaScript использует трюк – точка ‘.’ возвращает не саму функцию, а специальное значение «ссылочного типа», называемого Reference Type.
Этот ссылочный тип (Reference Type) является внутренним типом. Мы не можем явно использовать его, но он используется внутри языка.
Значение ссылочного типа – это «триплет»: комбинация из трёх значений (base, name, strict) , где:
- base – это объект.
- name – это имя свойства объекта.
- strict – это режим исполнения. Является true, если действует строгий режим ( use strict ).
Результатом доступа к свойству user.hi является не функция, а значение ссылочного типа. Для user.hi в строгом режиме оно будет таким:
Когда скобки () применяются к значению ссылочного типа (происходит вызов), то они получают полную информацию об объекте и его методе, и могут поставить правильный this ( =user в данном случае, по base ).
Ссылочный тип – исключительно внутренний, промежуточный, используемый, чтобы передать информацию от точки . до вызывающих скобок () .
При любой другой операции, например, присваивании hi = user.hi , ссылочный тип заменяется на собственно значение user.hi (функцию), и дальше работа уже идёт только с ней. Поэтому дальнейший вызов происходит уже без this .
Таким образом, значение this передаётся правильно, только если функция вызывается напрямую с использованием синтаксиса точки obj.method() или квадратных скобок obj[‘method’]() (они делают то же самое). Позднее в этом учебнике мы изучим различные варианты решения проблемы потери значения this . Например, такие как func.bind().
У стрелочных функций нет «this»
Стрелочные функции особенные: у них нет своего «собственного» this . Если мы используем this внутри стрелочной функции, то его значение берётся из внешней «нормальной» функции.
Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :
Это является особенностью стрелочных функций. Они полезны, когда мы на самом деле не хотим иметь отдельное значение this , а хотим брать его из внешнего контекста. Позднее в главе Повторяем стрелочные функции мы увидим больше примеров на эту тему.
Итого
- Функции, которые находятся в объекте в качестве его свойств, называются «методами».
- Методы позволяют объектам «действовать»: object.doSomething() .
- Методы могут ссылаться на объект через this .
Значение this определяется во время исполнения кода.
- При объявлении любой функции в ней можно использовать this , но этот this не имеет значения до тех пор, пока функция не будет вызвана.
- Эта функция может быть скопирована между объектами (из одного объекта в другой).
- Когда функция вызывается синтаксисом «метода» – object.method() , значением this во время вызова является объект перед точкой.
Также ещё раз заметим, что стрелочные функции являются особенными – у них нет this . Когда внутри стрелочной функции обращаются к this , то его значение берётся снаружи.
Источник
Привязка контекста (this) к функции в javascript и частичное применение функций
В предыдущем посте я описал, что this в javascript не привязывается к объекту, а зависит от контекста вызова. На практике же часто возникает необходимость в том, чтобы this внутри функции всегда ссылался на конкретный объект.
В данной статье мы рассмотрим два подхода для решения данной задачи.
1. jQuery.proxy — подход с использованием популярной библиотеки jQuery
2. Function.prototype.bind — подход, добавленный в JavaScript 1.8.5. Рассмотрим также его применение для карринга (частичного применения функции) и некоторые тонкости работы, о которых знают единицы.
Введение
Рассмотрим простой объект, содержащий свойство x и метод f, который выводит в консоль значение this.x
Как я указывал в предыдущем посте, при вызове object.f() в консоли будет выведено число 3. Предположим теперь, что нам нужно вывести данное число через 1 секунду.
Каждый раз использовать функцию обертку — неудобно. Нужен способ привязать контекст функции, так, чтобы this внутри функции object.f всегда ссылался на object
1. jQuery.proxy
Ни для кого не секрет, что jQuery — очень популярная библиотека javascript, поэтому вначале мы рассмотрим применение jQuery.proxy для привязки контекста к функции.
jQuery.proxy возвращает новую функцию, которая при вызове вызывает оригинальную функцию function в контексте context. С использованием jQuery.proxy вышеописанную задачу можно решить так:
Если нам нужно указать несколько раз одинаковый callback, то вместо дублирования
лучше вынести результат работы $.proxy в отдельную переменную
Обратим теперь внимание на то, что мы дважды указали object внутри $.proxy (первый раз метод объекта — object.f, второй — передаваемй контекст — object). Может быть есть возможность избежать дублирования? Ответ — да. Для таких случаев в $.proxy добавлена альтернативная возможность передачи параметров — первым параметром должен быть объект, а вторым — название его метода. Пример:
Обратите внимание на то, что название метода передается в виде строки.
2. Function.prototype.bind
Перейдем к рассмотрению Function.prototype.bind. Данный метод был добавлен в JavaScript 1.8.5.
Function.prototype.bind имеет 2 назначения — статическая привязка контекста к функции и частичное применение функции.
По сути bind создаёт новую функцию, которая вызывает func в контексте context. Если указаны аргументы arg1, arg2… — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове новой функции.
2.1. Привязка контекста
Использовать bind для привязки контекста очень просто, достаточно рассмотреть пример:
Таким образом пример из введения можно записать в следующем виде:
2.2. Частичное применение функций
Для упрощения рассмотрим сразу пример использования bind для частичного применения функций
Как видно из примера — суть частичного применения функций проста — создание новой функции с уменьшенным количеством аргументов, за счет «фиксации» первых аргументов с помощью функции bind.
На этом можно было бы закончить статью, но… Функции, полученные с использованием метода bind имеют некоторые особенности в поведении
2.3. Особенности bind
В комментариях к предыдущей статье было приведено 2 примера, касающихся bind (раз, два).
Я решил сделать микс из этих примеров, попутно изменив строковые значения, чтобы было проще с ними разбираться.
Пример (попробуйте угадать ответы)
Прежде чем разобрать — я перечислю основные особенности bind в соответствии со стандартом.
2.3.1. Внутренние свойств
У объектов Function, созданных посредством Function.prototype.bind, отсутствует свойство prototype или внутренние свойства [[Code]], [[FormalParameters]] и [[Scope]].
Это ограничение отличает built-in реализацию bind от вручную определенных методов (например, вариант из MDN)
2.3.2. call и apply
Поведение методов call и apply отличается от стандартного поведения для функций, а именно:
В коде видно, что thisValue не используется нигде. Таким образом подменить контекст вызова для функций полученных с помощью Function.prototype.bind с использованием call и apply — нельзя!
2.3.3. В конструкторе
В конструкторе this ссылается на новый (создаваемый) объект. Иначе говоря, контекст заданный при помощи bind, просто игнорируется. Конструктор вызывает обычный [[Call]] исходной функции.
Важно! Если в конструкторе отсутствует return this, то возвращаемое значение в общем случае неопределено и зависит от возвращаемого значения новой функции!
Источник