Ключевое слово/переменная this
: это специальный идентификатор, который указывает на данные в зависимости от условий выполнения кода. Создается для каждого контекста выполнения. Обычно указывает на владельца функции, в которой this
используется.
Очень часто можно услышать, что this
это некий "контекст". Это неверно, this
находится в контексте, но никак не является им.
Теперь давайте посмотрим, что будет содержаться по умолчанию в this
в разных условиях.
Внутри функции this
равняется объекту window
, но если у вас стоит строгий режим use strict
тогда undefiend
.
"use strict";
function showThis (){
console.log(this);
}
showThis(); // мы получим undefined
Без строгого режима:
function showThis (){
console.log(this);
}
showThis(); // получим объект window
А что если внутри функции будет еще функция с this
?
"use strict"
function showThis2(a, b) { // создаем функцию с 2 аргументами,
console.log(this); // undefined
function sum() { // внутри еще одну функцию
console.log(this); // здесь значение this будет таким же либо window либо undefiend.
return a + b;
}
console.log(sum()); // undefined
}
showThis2(1, 2); // 3
В общем в обычной функции будет window
либо undefined
в зависимоси от режима. Даже если функция находится внутри другой функции!
Метод принадлежит объекту, поэтому тут this
будет равна этому объекту.
"use strict"
const obj = { // Объект
a: 20,
b: 15,
sum() { // внутри метод
function what() {
console.log(this); // будет undefiend. Потому что это функция внутри метода, а не сам метод объекта,
}
console.log(this); // this будет объектом в котором находится функция
what();
}
};
obj.sum()
Важно понимать, что функция внутри метода не является методом.
this
в конструкторах и классах - это новый экземпляр объекта.
function User(name, age) { // создаем функцию конструктор.
this.name = name; // эти this обращаются к объекту которые мы будем создавать
this.age = age; // Посути вместо this каждый раз подставляется новый экземпляр объекта.
this.human = true; // если мы создадим объект lox, то выглядело бы это так lox.age и тд.
this.hello = function () {
console.log('hello' + this.name);
};
}
let ivan = new User('ivan', 24); // this принимает этот экземпляр
let dima = new User('Dima', 30); // и теперь этот. Здесь при создании мы сразу передаем значения в наши свойства name и age
dima.hello(); // выдаст (helloDima)
console.log(ivan);
Немного рассказываю, что происходит. Мы создаем переменную ivan
, в нее помещаем наш конструктор и указываем свойства. Внутри конструктора под this
подставляется наш ivan
, и ему записываются свойства (значения из аргументов в функции) ivan.name = name
, ivan.age = age
и так далее. Мы таким образом конструируем объект. Когда мы создадим dima
, вместо this
уже как бы подставится dima
и так далее.
Главное отличие стрелочной функции это то, что при вызове стрелочной функции у this
нет поведения по умолчанию из за этого, во время вызова стрелочной функции this
остается нетронутым и будет указывать на this
из родительской области делает он это с уже известной нам scope chain(цепочке областей видимости). Из этого делаем вывод, что неважно как мы вызываем стрелочную функцию, важно то, где она создается.
const obj = { // наш объект
num: 2,
sayNumber: function () { // метод этого объекта
const say = () => {
console.log(this); // стрелочная функция внутри метода
};
say();
}
};
obj.sayNumber(); // значение this будет объект {num: 2, sayNumber: ƒ}
// у метода в объекте this сам объект, стрелочная функция взяла тот же this, что и у метода в котором она находится.
const obj = {
x: 10,
f1() {
// используем стрелочную функцию
setTimeout(() => {
console.log(this.x); // this будет ссылаться на родительский объект obj
}, 1000);
}
};
obj.f1(); // выведет 10 через 1 секунду
Вот тут я скажу пару важных слов, а то можно уже запутаться во всем этом. Во время фазы создания мы находим нашу стрелочную функцию, и она попадает в лексическое окружение функции f1. То есть наша стрелочная функция - часть окружения функции f1, поэтому не важно, передаем мы ее куда-то или нет и где она там уже вызывается - не важно. Её this будет равна this родителя и все.
"use strict";
const obj = {
name: "Dima",
getName: () => {
console.log(`hello ${this.name} `);
}
};
obj.getName(); // hello undefined
///////////////////////////
// Метод с обычной функцией.
const obj = {
name: "Dima",
getName: function () {
console.log(`hello ${this.name} `);
}
};
obj.getName(); // hello Dima
В этом примере мы получили undefined
потому что родитель объекта obj
это глобальная область и тут как мы помним this
будет undefined
в строгом режиме.
При работе с DOM
это api
устанавливает значение this
в значение элемента к которому привязано событие. То есть если обработчик с обычной функцией, то this
как и event.target
будет сам элемент события.
const btnThis = document.querySelector('.btn-this');
btnThis.addEventListener('click', function () {
this.style.backgroundColor = 'red'; // при нажатии перекрасим кнопку в красный, так как this - элемент события
});
Со стрелочной функцией мы будем получать this
все так же от родителя.
"use strict" // вне зависимости от режима
const btnThis = document.querySelector('.btn-this');
btn.addEventListener('click', () => {
console.log(this); // window
})
Допустим есть у нас функция в строгом режиме, this
которой будет undefined
. А нам нужно, что бы ее this
имело другое значение:
function sayName(sername) {
};
const us = {
name: 'diman',
age: 10
};
Для этого есть два метода и один который сам создает новую функцию.
function sayName(surname) {
console.log(this);
console.log(`${this.name} ${surname}`);
console.log(this.age);
}
let us = {
name: 'dima',
age: 10
}
sayName.call(us, 'anime');// просто вызываем у функции метод.
// далее передаем объект, а после аргументы для функции.
Как видим теперь this
это объект us
и мы можем использовать его свойства внутри функции с аргументами.
Этот метод делает одно и тоже, что и call
. Но у них разный синтаксис.
sayName.apply(us, ['anime']); // здесь все тоже самое только аргументы внутри массива
Тут все почти также, только мы создаем новую функцию.
function count(num) {
return this * num; // здесь в this будет двойка, мы ее передали ниже.
}
const double = count.bind(2); // здесь мы по сути поместли в count новую функцию и забиндили в this = 2
// мы навсегда забиндили функцию! создали ей this ввиде двойки и теперь просто вызываем нашу double и передам нужный аргумент
console.log(double(10)); // 20
Также внутри стрелочной функции нельзя использовать методы call()
, apply()
и bind()
, потому что стрелочная функция автоматически указывает this
из родительской области и это поведение мы никак не можем изменить.
const a = {c:1};
const b = {c:2};
function hi() {
return () => {
console.log(this.c); // 1
};
}
const hello = hi.call(a); // явно указали, что this в контексте hi будет объект a
hello.call(b); // Тут мы ссылаемся на нашу стрелочную анонимную функцию и пытаемся изменить this
В итоге мы получим 1
. Потому что стрелочная функция берет this
родителя.
const a = {c:1};
const b = {c:2};
function hi() {
return () => {
return () => {
return () => {
console.log(this.c) // 1
}
}
};
}
const hello = hi.call(a);
hello.call(b);
Даже если мы сделаем такую цепь стрелочных функций, то ничего не изменится. Стрелочная функция будет идти по scope chain
из одной функции в другую пока не найдет значение для this
, все как и с обычными переменными(идентификаторами). При этом this
не является частью лексического окружения, но цепочка областей видимости используется для определения того, какое значение this
должно быть унаследовано стрелочной функцией.
Тут пару слов скажем о том как this
работает на уровне реализации языка. Этот раздел не сказать, что сильно нужен. Так что вам решать читать его или нет.
ReferenceType
- это встроенный тип ecmascript который используется на уровне реализации языка. Комбинирует в себе три значения (base, name, strict)
, где: base
- это объект, name
- имя свойства и strict
- true, если используется строгий режим "use strict"
. ReferenceType используется внутри языка для передачи информации об объекте и его методе между точкой .
и вызывающими скобками ()
.Так вот ReferenceType
возвращается при доступе к свойству объекта с помощью точки obj.property
или квадратных скобках obj[property]
. Так же ReferenceType
возвращается когда выполняется обращение к переменной и так же для деклараций функции. Теперь давайте сразу посмотрим на пример, так будет сразу ясно что к чему:
const user = {
name: "Dimasik",
hi() { console.log(this.name); }
};
user.hi(); // Dimasik
В этом прекрасном примере мы вызываем hi
объекта user
с помощью точки
, так вот точка нам возвращает не функцию, а значения Reference Type
: где base
= user
, name
= hi
, strict
= true
или false
. После скобки ()
вызываются на Reference Type и таким образом они получают всю информацию об объекте и его методе и используется это для того, что бы установить правильное значение this
на основе значения base
и вызвать нужный метод на основе значения name
.
точку
и получаем ReferenceType
, то значение base
будет значением this
.const user = {
name: "Dimasik",
hi() { console.log(this.name); } // у глобального объекта нет такого свойство, поэтому будет undefined
};
const hello = user.hi;
// в строгом режиме, если мы обращаемся к несуществующему свойству, то будет TypError
hello(); // undefined || TypError
Вот пример того, как ReferenceType
теряется. Мы сделали из переменной ссылку на метод и после просто вызвали, таким образом мы не получаем ReferenceType
в итоге this
будет указывать на window
или undefined
.
()
вызова нет ReferenceType
, тогда this
будет undefiend
или window
.GetValue - это встроенный метод ReferenceType. Он возвращает истинный тип получаемого через ReferenceType объекта. То есть если это функция, то вернется Function
. GetValue всегда срабатывает в зоне выражения.
Зона выражения - это часть в контексте выполнения, где мы ожидаем выражение. Или это часть, где что-то может быть вычислено в значение. То есть все наши выражения создают зону выражения: присваивание =,
операторы ||
или другие логические операторы, тернарный оператор, перечисление через запятую и тому подобное.
"use strict";
// вместо ReferenceType мы получим Function. Вспоминаем, что если !ReferenceType, то this будет undefiend или window
const hi = function() {
console.log(this); // undefined
};
hi();
Функция hi
вызывается в зоне выражения. Поэтому значение this
внутри функции будет равно undefined
.
Еще пару примеров:
var x = 10;
const obj = {
x:1,
f1: function () {
return console.log(this.x);
}
}
obj.f1(); // 1
// в этих случаях у нас this = window. Так как слева от скобок в зоне выражения, мы получаем Function
(obj.f1 = obj.f1)(); // 10
(obj.f1 || obj.f1)(); // 10
(obj.f1, obj.f1)(); // 10
Так как я использовал var
то эта переменная станет частью объекта window. Поэтому мы ее и получим.
Теперь немного взглянем что происходит с this
в конструкторах. Конструктором является та функция которую мы вызываем оператором new
.
[[construct]] - это метод, который вызывается при создании нового объекта с помощью оператора new. Он создает новый объект и устанавливает его прототипом функцию-конструктор.
[[call]] - это метод, который вызывается при вызове функции. Он проставляет в this
объект, на котором была вызвана функция.
function Person(name, age) {
this.name = name;
this.age = age;
}
const diman = new Person('Dimasiks', 26);
new
.[[construct]]
и этот же метод устанавливает новому объекту прототип функцию-конструктор. diman
будет иметь прототип Person.prototype
.[[call]]
для функции-конструктора и передает ему созданный объект в качестве значения this
. То есть внутри функции-конструктора this
ссылается на созданный объект.this
для определения свойств объекта. Мы устанавливаем свойства name
и age
объекта this
на значения ‘Dimasiks’
и 26
.