this в JavaScript
this — это ключевое слово, которое ссылается на тот или иной объект в зависимости от того, где оно записано или как была вызвана функция, где this присутствует.
this или другими словами контекст выполнения — это одна из тем понимание которой необходимо для изучения объектно-ориентированного программирования (ООП) в JavaScript.
Синтаксис
this
Посмотрим чему равен this с помощью метода console.log()
console.log(this) // this === window
или запросим значение свойства объекта, предварительно установив его, как this .
let student = < name: this >console.log(student.name) // this === window
В глобальном контексте, за пределами функций this равен объекту window в независимости от того, какой режим разработки установлен строгий ‘use strict’ или нестрогий.
Кстати, уже здесь мы можем обратиться не только к объекту window в целом, но также и к его свойствам.
console.log(this.onclick) // null
this в функциях
Если this записывать в отдельной функции, то его значение не всегда равно window . В нестрогом режиме всё без сюрпризов.
function testThis() < console.log(this) >testThis() // в браузере window testThis() // в Node global
В строгом режиме.
'use strict' function testThis() < console.log(this) >testThis() // undefined
Ключевое слово this всегда динамично и указывает на тот объект в контексте которого было вызвано. В примерах выше контекст глобальный оттуда мы и видим window . Кстати, если this записать в функцию, которая лежит внутри другой функции, результат будет таким же.
function testThis() < (function () < console.log(this === window) >)() > testThis() // true
Методы объекта и this
Теперь рассмотрим другой контекст отличный от глобального.
let student = < name: 'Дмитрий', course: 'HTML + CSS', level: 'junior', objMethod() < console.log(this) >> student.objMethod()
Вызывая функцию objMethod , как метод объекта student мы видим, что ключевое слово this принимает значение этого объекта, то есть значение того объекта по отношению к которому был вызван этот метод.
let student = < name: 'Дмитрий', course: 'HTML + CSS', level: 'junior', statement() < console.log(`$c уровнем $ хочет поступить на курс $`) > > student.statement()
Таким образом, мы можем обращаться через this к значениям свойств объекта через их имена. В принципе запись ниже аналогична варианту с this .
let student = < name: 'Дмитрий', course: 'HTML + CSS', level: 'junior', statement() < console.log(`$c уровнем $ хочет поступить на курс $`) > > student.statement()
Вместо this мы прописываем имя объекта, тем самым также обращаясь к его свойствам. Однако, если мы захотим эту функцию назначить нескольким объектам первый вариант отработает без ошибок в отличие от второго.
function statement() < console.log(`$c уровнем $ хочет поступить на курс $`) > let student1 = < name: 'Дмитрий', course: 'HTML + CSS', level: 'junior', statement >let student2 = < name: 'Ольга', course: 'Basic JavaScript', level: 'junior', statement >student1.statement() // Дмитрий c уровнем junior хочет поступить на курс HTML + CSS student2.statement() // Ольга c уровнем junior хочет поступить на курс Basic JavaScript
Имея в арсенале this нет надобности писать для каждого объекта одну и ту же функцию, в примере мы записали statement() один раз, а далее назначили ее двум разным объектам. При вызове метода для student1 и student2 получили корректный результат.
Работая с this стоит помнить, что ключевое слово определяется в момент вызова функции. Таким образом, если функцию положить в переменную и вызвать уже ее, результат будет отличным.
let student1 = < name: 'Пётр', statement() < console.log(`Имя студента $`) > > student1.statement() // Имя студента Дмитрий let anotherVar = student1.statement anotherVar() // Имя студента
Стрелочные функции
Стрелочная функция не имеют собственного контекста и если в таком случае прописать this то ссылаться ключевое слово будет на объект ближайший по иерархии. Такая особенность удобна, когда мы хотим передать в функцию внешний контекст.
let student = < name: 'Дмитрий', objMethod() < console.log(this) // let arrowFunc = () => console.log(this) arrowFunc() // function funcWithinFunc() < console.log(this) >funcWithinFunc() // window > > student.objMethod() // student
В первых двух случаях this ссылается на значение объекта student , в свою очередь this , записанная в функции funcWithinFunc , будет равна window .
Функция-конструктор
Функция-конструктор позволяет создавать новые объекты по шаблону на основе других данных. При вызове функции конструктора this принимает значение вновь созданного объекта, который наполняется вследствие выполнения тела функции.
function Student(name, course) < this.name = name this.course = course this.school = 'LearnJS' >let student1 = new Student('Вера', 'JavaScript Basic') console.log(student1)
Методы call() и apply()
call() и apply() позволяют явно настроить контекст выполнения функции, результатом работы методов будет выполнение этой функции. Методы идентичны между собой, единственное их различие, это формат в котором записываются аргументы, для call() через запятую, для appy() в массиве.
let student1 = let student2 = let sayName = function(course) < console.log(`$изучает $`) > sayName.call(student1, 'JS') // Степан sayName.apply(student2, ['HTML + CSS']) // Юлия
В первом случае мы определили контекст выполнения функции sayName относительно объекта student1 , во втором относительно student2 и получили в console Степан и Юлия соответственно.
Метод bind()
bind() – это встроенный метод, который также даёт возможность зафиксировать контекст выполнения, чтобы заранее быть уверенным, какое значение будет у this . В отличие от call() и apply() метод bind() возвращает новую функцию, а не вызывает изначальную.
let student1 = let student2 = let sayName = function() < console.log(this.name) >sayName.bind(student1)() // Степан sayName.bind(student2)() // Юлия
Итого
1. Ключевое слово this позволяет работать с контекстом, получая доступ до объектов, а также имеет в арсенале методы, для того, чтобы менять этот контекст.
2. this не является фиксированным и определяется во время выполнения кода. Таким образом, пока функция не вызвана this не имеет значения.
3. Стрелочные функции не имеют собственного контекста и связываются с ближайшим по иерархии.
4. bind() , call() и apply() — методы позволяющие управлять контекстом.
5. При вызове функции-конструктора this принимает значение вновь созданного объекта.
Skypro — научим с нуля
Методы объекта, «this»
Объекты обычно создаются, чтобы представлять сущности реального мира, будь то пользователи, заказы и так далее:
// Объект пользователя let user = < name: "John", age: 30 >;
И так же, как и в реальном мире, пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п.
Такие действия в JavaScript представлены функциями в свойствах.
Примеры методов
Для начала давайте научим нашего пользователя user здороваться:
let user = < name: "John", age: 30 >; user.sayHi = function() < alert("Привет!"); >; user.sayHi(); // Привет!
Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию приветствия, и присвоили её свойству user.sayHi нашего объекта.
Затем мы можем вызвать ee как user.sayHi() . Теперь пользователь может говорить!
Функцию, которая является свойством объекта, называют методом этого объекта.
Итак, мы получили метод sayHi объекта user .
Конечно, мы могли бы использовать заранее объявленную функцию в качестве метода, вот так:
let user = < // . >; // сначала, объявляем function sayHi() < alert("Привет!"); >// затем добавляем в качестве метода user.sayHi = sayHi; user.sayHi(); // Привет!
Объектно-ориентированное программирование
Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированным программированием или сокращённо: «ООП».
ООП является большой предметной областью и интересной наукой самой по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это – создание архитектуры, и на эту тему есть отличные книги, такие как «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или «Объектно-ориентированный анализ и проектирование с примерами приложений» Гради Буча, а также ещё множество других книг.
Сокращённая запись метода
Существует более короткий синтаксис для методов в литерале объекта:
// эти объекты делают одно и то же user = < sayHi: function() < alert("Привет"); >>; // сокращённая запись выглядит лучше, не так ли? user = < sayHi() < // то же самое, что и "sayHi: function()" alert("Привет"); > >;
Как было показано, мы можем пропустить ключевое слово «function» и просто написать sayHi() .
Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. Почти во всех случаях сокращённый синтаксис предпочтителен.
Ключевое слово «this» в методах
Как правило, методу объекта обычно требуется доступ к информации, хранящейся в объекте, для выполнения своей работы.
Например, коду внутри user.sayHi() может потребоваться имя пользователя, которое хранится в объекте user .
Для доступа к информации внутри объекта метод может использовать ключевое слово this .
Значение this – это объект «перед точкой», который используется для вызова метода.
let user = < name: "John", age: 30, sayHi() < // "this" - это "текущий объект". alert(this.name); >>; user.sayHi(); // John
Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).
Технически также возможно получить доступ к объекту без ключевого слова this , обратившись к нему через внешнюю переменную (в которой хранится ссылка на этот объект):
let user = < name: "John", age: 30, sayHi() < alert(user.name); // "user" вместо "this" >>;
…Но такой код ненадёжен. Если мы решим скопировать ссылку на объект user в другую переменную, например, admin = user , и перезапишем переменную user чем-то другим, тогда будет осуществлён доступ к неправильному объекту при вызове метода из admin .
Это показано ниже:
let user = < name: "John", age: 30, sayHi() < alert( user.name ); // приведёт к ошибке >>; let admin = user; user = null; // перезапишем переменную для наглядности, теперь она не хранит ссылку на объект. admin.sayHi(); // TypeError: Cannot read property 'name' of null
Если бы мы использовали this.name вместо user.name внутри alert , тогда этот код бы сработал.
«this» не является фиксированным
В JavaScript ключевое слово «this» ведёт себя иначе, чем в большинстве других языков программирования. Его можно использовать в любой функции, даже если это не метод объекта.
В следующем примере нет синтаксической ошибки:
function sayHi()
Значение this вычисляется во время выполнения кода, в зависимости от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» в вызовах:
let user = < name: "John" >; let admin = < name: "Admin" >; function sayHi() < alert( this.name ); >// используем одну и ту же функцию в двух объектах user.f = sayHi; admin.f = sayHi; // эти вызовы имеют разное значение this // "this" внутри функции - это объект "перед точкой" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (нет разницы между использованием точки или квадратных скобок для доступа к объекту)
Правило простое: если вызывается obj.f() , то во время вызова f , this – это obj . Так что, в приведённом выше примере это либо user , либо admin .
Вызов без объекта: this == undefined
Мы даже можем вызвать функцию вообще без объекта:
function sayHi() < alert(this); >sayHi(); // undefined
В строгом режиме ( «use strict» ) в таком коде значением this будет являться undefined . Если мы попытаемся получить доступ к this.name – это вызовет ошибку.
В нестрогом режиме значением this в таком случае будет глобальный объект ( window в браузерe, мы вернёмся к этому позже в главе Глобальный объект). Это – исторически сложившееся поведение this , которое исправляется использованием строгого режима ( «use strict» ).
Обычно подобный вызов является ошибкой программирования. Если внутри функции используется this , тогда она ожидает, что будет вызвана в контексте какого-либо объекта.
Последствия свободного this
Если вы до этого изучали другие языки программирования, то вы, вероятно, привыкли к идее «фиксированного this » – когда методы, определённые в объекте, всегда имеют this , ссылающееся на этот объект.
В JavaScript this является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а скорее от того, какой объект вызывает метод (какой объект стоит «перед точкой»).
Эта концепция вычисления this в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок.
Здесь наша позиция заключается не в том, чтобы судить, является ли это архитектурное решение в языке хорошим или плохим. Скоро мы поймем, как с этим работать, как получить выгоду и избежать проблем.
У стрелочных функций нет «this»
Стрелочные функции особенные: у них нет своего «собственного» this . Если мы ссылаемся на this внутри такой функции, то оно берётся из внешней «нормальной» функции.
Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :
let user = < firstName: "Ilya", sayHi() < let arrow = () =>alert(this.firstName); arrow(); > >; user.sayHi(); // Ilya
Это особенность стрелочных функций. Она полезна, когда мы на самом деле не хотим иметь отдельное this , а скорее хотим взять его из внешнего контекста. Позже в главе Повторяем стрелочные функции мы увидим больше примеров на эту тему.
Итого
- Функции, которые находятся в свойствах объекта, называются «методами».
- Методы позволяют объектам «действовать»: object.doSomething() .
- Методы могут ссылаться на объект через this .
Значение this определяется во время исполнения кода.
- При объявлении любой функции в ней можно использовать this , но этот this не имеет значения до тех пор, пока функция не будет вызвана.
- Функция может быть скопирована между объектами (из одного объекта в другой).
- Когда функция вызывается синтаксисом «метода» – object.method() , значением this во время вызова является object .
Также ещё раз заметим, что стрелочные функции являются особенными – у них нет this . Когда внутри стрелочной функции обращаются к this , то его значение берётся извне.
Задачи
Использование «this» в литерале объекта
важность: 5
Здесь функция makeUser возвращает объект.
Каким будет результат при обращении к свойству объекта ref ? Почему?
function makeUser() < return < name: "John", ref: this >; > let user = makeUser(); alert( user.ref.name ); // Каким будет результат?
Ответ: ошибка.
function makeUser() < return < name: "John", ref: this >; > let user = makeUser(); alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
Это потому, что правила, которые определяют значение this , никак не смотрят на объявление объекта. Важен лишь момент вызова.
Здесь значение this внутри makeUser() равно undefined , потому что оно вызывается как функция, а не через «точечный» синтаксис как метод.
Значение this одно для всей функции, блоки кода и объектные литералы на него не влияют.
Таким образом, ref: this фактически принимает текущее this функции makeUser() .
Мы можем переписать функцию и вернуть то же самое this со значением undefined :
function makeUser() < return this; // на этот раз нет литерала объекта >alert( makeUser().name ); // Error: Cannot read property 'name' of undefined
Как вы можете видеть, результат alert( makeUser().name ) совпадает с результатом alert( user.ref.name ) из предыдущего примера.
Вот противоположный случай:
function makeUser() < return < name: "John", ref() < return this; >>; > let user = makeUser(); alert( user.ref().name ); // John
Теперь это работает, поскольку user.ref() – это метод. И значением this становится объект перед точкой . .
Что такое this в javascript
Поведение ключевого слова this зависит от контекста, в котором оно используется, и от того, в каком режиме оно используется — строгом или нестрогом.
Глобальный контекст и объект globalThis
В глобальном контексте this ссылается на глобальный объект. Что такое «глобальный объект» в JavaScript? Это зависит от среды, в которой выполняется код. Так, в веб-браузере this представляет объект window — объект, который представляет окно браузера. В среде Node.js this представляет объект global . А для веб-воркеров this представляет объект self
Например, в веб-браузере при выполнении следующего кода:
console.log(this);
мы получим консольный вывод вроде следующего
Window
В стандарт ES2020 было добавлено определение объекта globalThis , который позволяет ссылаться на глобальный конекст вне зависимости, в какой среде и в какой ситуации выполняется код:
console.log(globalThis);
Контекст функции
В пределах функции this ссылается на внешний контекст. Для функций, определенных в глобальном контексте, — это объект globalThis . Например:
function foo() < var bar = "local"; console.log(this.bar); >var bar = "global"; foo(); // global
Если бы мы не использовали бы this , то обращение шло бы к локальной переменной, определенной внутри функции.
function foo() < var bar = "local"; console.log(bar); >var bar = "global"; foo(); // local
Но если бы мы использовали строгий режим (strict mode), то this в этом случае имело бы значение undefined :
"use strict"; function foo() < var bar = "local"; console.log(this.bar); >var bar = "global"; foo(); // ошибка - this - undefined
Контекст объекта
В контексте объекта, в том числе в его методах, ключевое слово this ссылается на этот же объект:
const obj = < bar: "object", foo: function()< console.log(this.bar); >> var bar = "global"; obj.foo(); // object
Динамическое определение контекста
Код функции всегда использует в качестве this внешний контекст, в которым этот код вызывается (именно вызывается, а не определяется). Рассмотрим более сложный пример:
function foo() < var bar = "foo_bar"; console.log(this.bar); >const obj1 = ; const obj2 = ; var bar = "global_bar"; foo(); // global_bar obj1.foo(); // obj1_bar obj2.foo(); // obj2_bar
Здесь определена глобальная переменная bar («global_bar»). И также в функции foo определена локальная переменная bar («foo_bar»). Значение какой переменной будет выводиться в функции foo? Функция foo использует определение переменной, которое определено во внешнем контексте. Для функции foo по умолчанию это глобальный контекста, поэтому она выводит значение глобальной переменной (так как данный скрипт запускается в нестрогом режиме, а значит ключевое слово this в функции foo ссылается на внешний контекст).
Иначе дело обстоит с объектами. Они определяют свой собственный контекст, в котором существует свое свойство bar. И при вызове метода foo внешним контекстом по отношению к функции будет контекст объектов obj1 и obj2.
Подобное поведение может привести к некоторому непонимаю в отдельных случаях. Так, рассмотрим другую ситуацию:
var bar = "global_bar"; const obj1 = < bar: "obj1_bar", foo: function()< console.log(this.bar); // bar = "obj1_bar" >> const obj2 = ; // bar = "obj2_bar" const foo = obj1.foo; // bar = "global_bar" obj1.foo(); // obj1_bar obj2.foo(); // obj2_bar foo(); // global_bar
Здесь в объекте obj1 определена функция foo:
const obj1 = < bar: "obj1_bar", foo: function()< console.log(this.bar); // bar = "obj1_bar" >>
Эта функция foo будет брать значение для this.bar из внешнего контекста — из объекта obj1, соответственно this.bar = «obj1_bar» .
Объект obj2 использует функцию foo из объекта obj1:
const obj2 = ;
Однако функция obj1.foo также будет искать значение для this.bar опять же во внешнем котексте, а здесь это объект obj2. А в объекте obj2 это значение равно «obj2_bar».
То же самое с глобальной переменной foo , которая ссылается на ту же функцию, что и метод obj1.foo :
const foo = obj1.foo;
В этом случае также будет происходить поиск значения для this.bar , но теперь в контексте функции foo. А это глобальный контекст, где определена переменная var bar = «global_bar» .
Контекст во вложенных функциях
Если мы вызываем функцию из другой функции, вызываемая функция также будет использовать внешний контекст:
var bar = "global bar"; function foo() < var bar = "foo bar"; function moo()< console.log(this.bar); >moo(); > foo(); // global bar
Здесь функция foo() в качестве this.bar использует значение переменной bar из внешнего контекста, то есть значение глобальной переменной bar. Функция moo() также в качестве this.bar использует значение переменной bar из внешнего контекста, то есть this.bar для внешней функции foo, которое в свою очередь представляет значение глобальной переменной bar. Поэтому в итоге консоль выведет «global bar», а не «foo bar».
Явная привязка
С помощью методов call() и apply() можно задать явную привязку функции к определенному контексту:
function foo() < console.log(this.bar); >var obj = var bar = "global_bar"; foo(); // global_bar foo.apply(obj); // obj_bar // или // foo.call(obj);
Во втором случае функция foo привязывается к объекту obj, который и определяет ее контекст. Поэтому во втором случае консоль выведет «obj_bar».
Метод bind
Метод f.bind(o) позволяет создать новую функцию с тем же телом и областью видимости, что и функция f, но с привязкой к объекту o:
function foo() < console.log(this.bar); >const obj = var bar = "global"; foo(); // global const func = foo.bind(obj); func(); // object
this и стрелочные функции
В стрелочных функциях в качестве this используется контекст окружения, а не само окружение, в котором определена стрелочная функция. Рассмотрим следующий пример:
const person = < name: "Tom", say:()=>console.log(`Меня зовут $`) > person.say(); // Меня зовут
Здесь стрелочная функция say() обращается к некому свойству this.name , но что здесь представляет this ? Для внешнего контекста, в котором определена стрелочная функция — то есть для контекста объекта person this представляет глобальный объект (объект окна браузера). Однако глобальной переменной name не определено, поэтому на консоль будет выведено:
Меня зовут
Теперь немного изменим пример:
const person = < name: "Tom", hello()< console.log("Привет"); let say = ()=>console.log(`Меня зовут $`); say(); > > person.hello();
Теперь стрелочная функция определена в методе hello() . this для этого метода представляет текущий объект person, где определен данный метод. Поэтому и в стрелочной функции this будет представлять объект person, а this.name — свойство name этого объекта. Поэтому при выполнении программы мы получим:
Привет Меня зовут Tom
Несмотря на то что, стрелочные функции могут добавить забот при работе this, в то же время они могут решить ряд проблем. Так, при работе с несколькими контекстами мы вынуждены учитывать, в каком контексте определяется переменная. Например, возьмем следующий код:
const school =< title: "Oxford", courses: ["JavaScript", "TypeScript", "Java", "Go"], printCourses()< this.courses.forEach(function(course)< console.log(this.title, course); >) > > school.printCourses();
Функция printCourses проходит по всем курсам из массива и при их выводе предваряет их значением свойства title. Однако на консоли при запуске программы мы увидим следующее:
undefined "JavaScript" undefined "TypeScript" undefined "Java" undefined "Go"
Мы видим, что значение this.title не определено, так как this как контекст объекта замещается глобальным контекстом. В этом случае нам надо передать подобное значение this.title или весь контекст объекта.
const school =< title: "Oxford", courses: ["JavaScript", "TypeScript", "Java", "Go"], printCourses()< const that = this; this.courses.forEach(function(course)< console.log(that.title, course); >) > > school.printCourses();
Стрелочные функции также позволяют решить данную проблему:
const school =< title: "Oxford", courses: ["JavaScript", "TypeScript", "Java", "Go"], printCourses()< this.courses.forEach((course)=>console.log(this.title, course)) > > school.printCourses();
Контекстом для стрелочной функции в данном случае будет выступать контекст объекта school. Соответственно, нам не надо определять дополнительные переменые для передачи данных в функцию.
this в JavaScript — что такое контекст
Одна из причин, по которой ключевое слово this такое не простое для многих программистов JavaScript — даже опытных — заключается в том, что к чему относится this , полностью зависит от контекста, иногда довольно сложным образом. Так, чтобы разобраться с this , важно не только понять, как «this» ведет себя в различных контекстах, но и понять сами контексты — и как их определить в живом коде.
- Методы объекта, «this»
- .bind() — Привязка контекста к функции
- .call() и .apply()
Оглавление:
- Теоретические основы
- this по умолчанию
- this в методах
- this в функции-конструктора
- Усложним: вызов функции внутри конструктора
- Итого про this
Теоретические основы
this — это ключевое слово в JavaScript которое содержит в себе объект (контекст) выполняемого кода.
Мне кажется, что проще всего представить, что this — это уникальная переменная, которая хранит в себе контекст исполняемого кода. И наоборот — контекст — это значение ключевого слова this.
this имеет различные значения в зависимости от того, где используется:
- Сама по себе — this относится к глобальному объекту (window).
- В методе — this относится к родительскому объекту.
- В функции — this относится к глобальному объекту.
- В функции в ‘strict mode’ — this = undefined.
- В стрелочной функции — this относится к контексту где функция была создана.
- В событии — this ссылается на элемент запустивший событие.
Что такое контекст?
Любое определение this, так или иначе крутится вокруг слова контекст. Давайте разберемся что же это такое.
JavaScript является одно-поточным языком, то есть одновременно может выполняться только одна задача. Интерпретатор JavaScript всегда начинает выполнять код с глобального контекста (в браузере это объект window). С этого момента у самой первой вызванной функции контекстом будет глобальный объект (window). Наша функция может создать новый объект, создать в нем методы и запустить один из этих методов, теперь контекст вызова изменился и стал указывать на новый объект, который был создан из глобального контекста.
Таким образом, контекстом всегда является какой-то объект из под которого был вызван метод (функция).
По ходу обработки кода внутри глобального контекста window создаются другие объекты, они представляют собой новые контексты в которых выполняется код. Таким образом, в одном коде может быть замешено много контекстов. Одна функция может вызывать методы разных объектов и у каждого из них будет свой контекст.
Контекст — это всегда значение ключевого слова this, которое является ссылкой на объект, который запустил метод (функцию). Контекст — это объект «владеющий» исполняемым кодом. А this всегда ссылаться на объект (контекст) запустивший функцию.
Методы и функции
В теле каждого метода или функции, которая по сути всегда является методом какого-то объекта мы можем использовать this , чтобы обратится к родительскому объекту который вызвал функцию, или в котором находится метод. Упрощенно можно сказать — this — это родительский объект который вызвал метод (функцию).
Например, когда this используется внутри функции my_function() , можно сказать что this это универсальная переменная в значении которой лежит объект, который вызвал функцию my_function() .
Это удобно, потому что:
- Неудобно каждый раз писать в коде название родительского объекта, чтобы вызвать его метод (функцию), а можно написать this.my_function() вместо window.my_object.my_function() .
- Иногда мы вообще можем заранее не знать объект, который вызвал функцию. Потому что программист написал код так, что объект (контекст) нужно указать при вызове функции (этот подход позволяет использовать одну и ту же функцию/метод, для разных объектов).
Представить что this это переменная ссылающаяся на объект, который вызвал метод (функцию), сильно упрощает использование this в вашем коде.
Важно понять, что this не зависит от того, где была объявлена функция, а зависит от того, как (кем) функция была вызвана. То где функция была объявлена имеет отношение к области её видимости (scope), а не к контексту.
Область видимости (scope) и Контекст
Кроме контекста нужно еще понимать что такое область видимости. Эти понятия часто путают. Называя одно другим. Однако контекст и область видимости это разные вещи.
Каждый вызов функции имеет как область видимости, так и контекст, связанный с ней. Область видимости основана на том, где функция вызывается (какие переменные ей доступны), а контекст основан на том, кем функция вызывается (каким объектом). Другими словами, область видимости относится к доступу функции к переменным при ее вызове и является уникальной для каждого вызова. Контекст — это всегда значение ключевого слова this, которое является ссылкой на объект, «владеющий» текущим исполняемым кодом.
this по умолчанию
Чаще всего this используется внутри функции/метода. Однако this также работает в глобальной области.
Когда мы пишем простую функцию и затем её используем, она все равно вызывается каким-то объектом и this ссылается на этот вызывающий объект. Для браузера — это объект window.
Запустим такой код в консоли браузера:
function get_this() < return this >get_this() // ▸ Window this // ▸ Window
Как мы видим в обоих случаях this будет объектом Window. Это равносильно такому коду:
window.get_this = function() < return this >window.get_this() // ▸ Window window.this // ▸ Window
Однако если функция выполняется в строгом режиме, то в this будет записано undefined , так как в этом режиме запрещены привязки по умолчанию:
function get_this() < 'use strict' return this >get_this() // ▸ undefined window.get_this() // ▸ Window
Т.е. в строгом режиме нужно прямо указывать контекст из которого вызывается метод:
this в методах
Метод вызывается наглядно: сначала идет название объекта ourObject , затем точка . , затем название метода ourMethod .
this очень легко отследить, когда метод вызывается в сочетании с объектом object.method() — что находится с левой стороны от точки, то и будет в this вызываемого метода (в данном случае this = object):
let object = < method: function()< console.log( this ) >> object.method() // ▸
Это самый простой вариант, чтобы понять что будет находится в this. Тут метод вызван из того же объекта в котором он определен. Поэтому this ссылается на экземпляр object потому что method вызывается этим объектом. this тут это вызывающий объект.
this в функции-конструктора
Когда для создания новых объектов используется функция, её нужно вызывать с помощью ключевого слова new. При таком вызове функция будет вызвана как конструктор нового объекта и новый объект будет возвращен.
Название функции в этом случае принято писать с заглавной буквы. Это название станет названием экземпляра объекта.
Когда вызывается конструктор, this указывает на вновь созданный объект, а не на объект который запустил построение.
function Doge( param ) < // this = <>— новый пустой объект, который вызвал эту функцию благодаря new this.saying = param // return this // это конструктор, он не может ничего возвращать > new Doge( ‘Привет’ ) // ▸ Doge new Doge( ‘Браузер’ ) // ▸ Doge
Заглянем под капот, чтобы лучше понять почему this в функции-конструктора работает именно так. Когда функция вызывается через new вызов этой функции происходит иначе:
- Создается пустой объект, название которого берется из названия функции.
- Созданный объект вызывает функцию как конструктор. Так как функцию вызывает новый объект, то this внутри этой функции ссылается на этот новый объект.
- Функция-конструктор отрабатывает и new возвращает вновь созданный объект.
В такой функции не нужно использовать return — в этом нет смысла, потому что она является конструктором объекта. В задачи функции-конструктора входит установка свойств и методов нового объекта и она не может ничего возвращать.
Усложним: вызов функции внутри конструктора
Что будет если мы поместим обычную функцию внутрь функции-конструктора и вызовем её там?
У нас есть глобальная функция getThis() , функция-конструктора и новый объект, созданный функцией-конструктора. В этом примере глобальная getthis() вызываются изнутри функции-конструктора:
function get_this() < console.log( this ) >function Doge( saying ) < this.saying = saying get_this() >new Doge( ‘Не Window!’ ) // увидим в консоли: // ▸ Window // ▸ Doge
get_this() все еще указывает на Window, потому что, несмотря на то что get_this() вызывается внутри функции конструктора Doge() , она фактически вызывается из глобальной области — window.get_this() .
А что, если мы создадим метод внутри конструктора и вызовем его в конструкторе при создании нового объекта?
function Doge( saying ) < this.saying = saying this.get_this = function()< console.log( this ) >this.get_this() > new Doge( ‘не Window’ ) // создадим экземпляр Doge // ▸ Doge // ▸ Doge
get_this() по-прежнему указывает на вновь созданный объект. Потому что именно он его вызвал.
А что если, мы вызовем Doge без ключевого слова new?
function Doge( saying ) < this.saying = saying this.get_this = function()< console.log( this ) >this.get_this() console.log( this ) > Doge( ‘Кто здесь?’ ) // запускаем Doge как простую функцию // Получим в консоли: // ▸ Window // ▸ Window
Как мы видим функция была вызвана объектом window и она добавила в вызываемый объект window новое свойство saying и метод get_this() . Убедимся в этом, посмотрим значение свойства saying и вызовем метод get_this().
window.saying // «Кто здесь?» window.get_this() // Window
Итого про this
Значение this устанавливается в зависимости от того, как вызвана функция:
При вызове функции в качестве метода, контекстом будет вызываемый объект:
obj.func(. ) // this = obj obj["func"](. ) // this = obj
При обычном вызове, контекстом будет глобальный объект она будет вызвана без контекста:
func(. ) // this = window (ES3) / undefined (ES5 - 'use strict')
При использовании ключевого слова new, создается новый чистый контекст.
new func() // this = <> (новый объект)
