Открытый бесплатный вебинар об ООП в JavaScript




Тема "Методы объектов и контекст вызова" в учебнике Кантора казалась мне невероятно сложной и непонятной. Сергей Мелюков сумел объяснить её очень доступно и доходчиво. у человека определённо талант, такое понятное изложение сложного материала встречается не часто.

https://www.youtube.com/watch?v=5l01s6Vkqp0

 This

This это ключевое слово, которое указывает на объект, в контексте которого вызывается функция.
Не из которого вызывается, а в контексте которого вызывается. Это важно.
Чаще всего эти понятия совпадают, но не всегда.
Контекст вызова функции можно изменить и тогда функция будет вызываться из одного объекта, но указывать при этом на другой объект.

Пример:

var a = {
  prop: "a",
  fn: function () {
    return this.prop;
  }
};
var b = {
  prop: "b",
  fn: function () {
    return this.prop;
  }
};
a.fn()   // "a"

b.fn()   // "b"

Пока всё просто: есть два объекта а и b, у каждого своя переменная prop с таким же значением и функция, которая this.prop возвращает. Ожидаемо a.fn() возвращает "a", b.fn()   возвращает "b". В данном случае контекст вызова совпадает с объектом, из которого функция вызывается.

Перепишем вызов функций по-другому

a.fn.bind(b)()       // "b"  
b.fn.bind(a)()   // "a"

Теперь  a.fn.bind(b)()  возвращает "b", b.fn.bind(a)() возвращает "a". Мы изменили контекст вызова.
Можно сделать иначе:

a.fn = a.fn.bind(b)
a.fn()  // "b"  

Работает.
А если сделать так?

a.fn = b.fn;
a.fn()   // "a" 

Не работает.Мы присвоили функции a.fn ссылку на функцию b.fn, но как только вызвали её из объекта a, контекст изменился на a. Поэтому для смены контекста применяют специальные методы, одним из которых является bind.

Ещё один момент: сменить контекст вызова можно только один раз:

var newFn =  a.fn.bind(b);
newFn =  newFn.bind(a);
newFn()  // "b"  

У функции, которую получили в результате работы bind, мы больше не можем сменить контекст. Контекст вызова можно изменить только один раз.

В повторном вызове bind есть ещё один эффект: bind способен накапливать аргументы.
К примеру, нам нужно вызвать функцию summ с двумя аргументами, мы вызываем через bind  с одним аргументом, получаем ошибку вычисления. Вызываем ещё раз через bind  и снова с одним аргументом, всё считает как нужно.




Смена контекста

Изменить контекст вызова функции позволяют три метода: bind(), call() и apply(). Эти методы очень похожи между собой, отличия незначительные. Каждый из них принимает первым параметром объект из которого вызывается функция или null и аргументы, которые в неё должны быть переданы.

Методы bind, call, apply


Контекст функции

(в порядке увеличения приоритета)

  1. функция вызывается из объекта
    через точку a.f();
    или через квадратные скобки a["f"]();
  2. для вызова функции используются методы
    bind
    call
    apply
  3. функция вызывается с ключевым словом new
В остальных случаях контекст функции сбрасывается на глобальный объект.
В браузере глобальный объект это window, в node.js - global.

Распространённая ошибка: часто считают, что если у внешней функции есть контекст, то и у вложенной в неё функции тоже будет контекст. На самом деле это не так. Контекст вложенной функции будет равен глобальному объекту.

var a = {
  prop: "a",
  f: function () {
    var fn = function (){    

      console.log(this.prop);
    };
    fn();
  }
};

a.f()   //  undefined


Если нужно передать контекст во внутреннюю функцию, можем использовать два варианта.
Первый вариант: используем методы bind, call,apply.

var a = {
  prop: "a",
  f: function () {
    var fn = function (){
      console.log(this.prop);
    };
    fn.call(this);
  }
};

a.f()    //   a


Второй вариант: кеширование контекста. Для этого сохраняем значение this в переменную that и передаём его во внутреннюю функцию через замыкание.

var a = {
  prop: "a",
  f: function () {
    var that = this;
// название переменной that общепринятое для таких случаев
    var fn = function (){
      console.log( that.prop);
    };
    fn();
  }
};

a.f()     //   a

l
// И всё-таки здесь есть вопрос. Если для вызова результата функции fn использовать не consoe.log, а return, результат будет undefined. Непонятно почему так.

Продолжение https://studyjavascript.blogspot.com/2019/03/blog-post_23.html