Pull to refresh

Коротко о this в функциях javascript

Reading time4 min
Views25K

Предисловие


На просторах интернета довольно много информации о том, как работает this, но мне всё время не хватало буквально чуть-чуть, чтобы до конца в этом разобраться.
Возможно, именно эта статья станет для вас той, которая расставит всё по полочкам!

Без лишних слов


Мы разберем как простые, так и сложные примеры — так что всем будет интересно.

Начнем с определения.
This — это ссылка на контекст исполнения функции. Таким образом получается, что this тесно связан именно с функциями и рассматривается относительно них. Вне функции this будет ссылаться на глобальный контекст
Два основных тезиса, которые мы рассмотрим:
Тезис 1
Для функций, объявленных через function f( ) { }, this вычисляется в момент вызова и равен объекту перед точкой. Если такого объекта нет — тогда this будет указывать на глобальный контекст (window)

Тезис 2
Для стрелочных функций this определяется в момент их создания и больше никогда не изменяется
Договоримся, что везде используется 'use strict', поэтому в глобальной области видимости this всегда будет undefined, а не window.

Пример 1

Для начала посмотрим на самый простой пример.

function globalFunc() {
  console.log(this);
}
const globalArrowFunc = () => {
  console.log(this);
}

console.log(this); // ?
globalFunc(); // ?
globalArrowFunc(); // ?

Ответ с объяснением
console.log(this); // undefined

Простое обращение к this указывает на ближайший контекст исполнения — window.

globalFunc(); // undefined

Функция, объявленная через function, указывает на window, потому что нет никакого объекта перед точкой.

globalArrowFunc(); // undefined

Стрелочная функция указывает на window, потому что она была создана внутри глобального контекста (window).



Пример 2

Давайте двинемся дальше и рассмотрим всё то же самое внутри объекта.

const user = {
  name: 'Bob',
  userThis: this,
  func() {
    console.log(this);
  },
  arrowFunc: () => {
    console.log(this);
  }
};

console.log(user.userThis); // ?
user.func(); // ?
user.arrowFunc(); // ?

Ответ с объяснением
console.log(user.userThis); // undefined

Здесь мы вновь обратим свое внимание на то, что this имеет смысл только относительно функции, потому что this указывает на контекст исполнения функции. Поскольку этот объект находится внутри глобального контекста (window), то и наше this указывает на window.

user.func(); // { user }

Здесь объектом перед точкой является user, поэтому this ссылается на объект user.

user.arrowFunc(); // undefined

Поскольку стрелочная функция запомнила свой контекст в момент создания, то и при вызове она будет ссылаться именно на него. Таким образом мы получаем window.
Однако вы можете задать вопрос «Но почему в момент создания контекст был window, а не объект, внутри которого она была объявлена как метод?».
Стрелочная функция в момент создания ищет ближайший к ней контекст и запоминает его, а он у нас точно такой же как и в случае простого присвоения внутри user.userThis.


Пример 3

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

Обычная функция, возвращающая обычную функцию — funcFunc.
Обычная функция, возвращающая стрелочную функцию — funcArrow.
Стрелочная функция, возвращающая обычную функцию — arrowFunc.
Стрелочная функция, возвращающая стрелочную функцию — arrowArrow.

const user = {
  name: 'Bob',
  funcFunc() {
    return function() {
      console.log(this);
    }
  },
  funcArrow() {
    return () => {
      console.log(this);
    }
  },
  arrowFunc: () => {
    return function() {
      console.log(this);
    }
  },
  arrowArrow: () => {
    return () => {
      console.log(this);
    }
  },
};

user.funcFunc()(); // ?
user.funcArrow()(); // ?
user.arrowFunc()(); // ?
user.arrowArrow()(); // ?


Ответ с объяснением
user.funcFunc()(); // undefined

Когда вызывается user.funcFunc() — нам возвращается функция function. Обратим внимание, что мы сразу же вызываем ее и в этот момент у нее нет никакого объекта перед точкой, а значит this ссылается на window.
Мы могли бы разбить это на 2 строки
const foo = user.funcFunc()
foo(); — нет объекта перед точкой

user.funcArrow()(); // { user }

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

user.arrowFunc()(); // undefined

Здесь все то же самое, что и первом примере. Нам не важно, каким образом была создана function. Важно лишь что в момент ее вызова у нее нет объекта перед точкой.

user.arrowArrow()(); // undefined

Как мы помним, внутри метода arrowArrow this указывал на window. Таким образом и только что созданная стрелочная функция будет указывать на него же.



Пример 4

Давайте еще немного поиграем с предыдущим примером. Теперь мы не просто вытащим новые функции и вызовем их, а запишем их в другой объект и вызовем из него.

// Объект user остался без изменений
const user = {
  name: 'Bob',
  funcFunc() {
    return function() {
      console.log(this);
    }
  },
  funcArrow() {
    return () => {
      console.log(this);
    }
  },
  arrowFunc: () => {
    return function() {
      console.log(this);
    }
  },
  arrowArrow: () => {
    return () => {
      console.log(this);
    }
  },
};

const user2 = {
  name: 'Jim',
  funcFunc: user.funcFunc(),
  funcArrow: user.funcArrow(),
  arrowFunc: user.arrowFunc(),
  arrowArrow: user.arrowArrow()
}

user2.funcFunc(); // ?
user2.funcArrow(); // ?
user2.arrowFunc(); // ?
user2.arrowArrow(); // ?


Ответ с объяснением
user2.funcFunc(); // { user2 }

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

user2.funcArrow(); // { user }

Когда мы вызвали user.funcArrow(), мы создали новую стрелочную функцию, но в момент создания она запомнила ближайший к ней контекст (user) и теперь она всегда и везде будет возвращать именно его.

user2.arrowFunc(); // { user2 }

Здесь точно то же самое, что и в первом случае. Мы вернули функци и вызвали ее. Объект перед точкой user2, поэтому this указывает именно на него.

user2.arrowArrow(); // undefined

Когда мы вызвали user.arrowArrow(), то создали стрелочную функцию, которая запомнила ближайший контекст (window). Теперь она всегда будет указывать на него.



Ну вот и всё


Надеюсь, что после прочтения данного материала, определение контекста исполнения уже не так пугает вас, как раньше :)
Only registered users can participate in poll. Log in, please.
Статья вам помогла?
76.74% да33
23.26% нет10
0% я всё это уже знал0
43 users voted. 1 user abstained.
Tags:
Hubs:
Total votes 17: ↑12 and ↓5+7
Comments11

Articles