Обзор

Ключевое слово this может быть одним из самых запутанных понятий в JavaScript. Мы объясним это this с точки зрения непрофессионала с достаточным количеством примеров.

this — это объект, свойством которого является функция

Один лайнер,

obj.someFunc(this);

Здесь this находится в функции someFunc, а someFunc является свойством obj. Итак, this представляет obj.

Примеры

Локальный объект

const myObject = {
  name: 'foo',
  task: function () {
    return `This is ${this.name} task.`;
  }
};
myObject.task();

Это вернет текст This is foo task..

По определению здесь this относится к объекту myObject. Функция task является собственностью myObject.

Глобальный объект

В глобальной области this — это объект window.

В глобальной области видимости, если мы определим функцию

function a() {
  console.log(this);
}

Он напечатает объект window.

Поскольку метод a() — это window.a(), то, согласно нашему первому определению, для a() this является объектом window.

«это» определяется тем, кто вызвал метод?

this определяется объектом, называемым методом.

const a = function () {
  console.log('For a, this is ', this);
  const b = function () {
    console.log('For b, this is ', this);
    const c = {
      hi: function () {
        console.log('For c, this is ', this);
      }
    };
    c.hi();
  };
  b();
};
a();

В этом случае и a, и b имеют объект window как this.

А для c, поскольку он вызывается c, this является объектом c.

Теперь вопрос может заключаться в том, как b() имеет this объект window, а не a. Поскольку b() вызывается не так, как a.b(). Вместо этого он вызывается как window.a(b())

"this" не имеет лексической области действия, она имеет динамическую область видимости

const obj = {
  name: 'Billy',
  sing() {
    console.log('a', this);
    var anotherMethod = function () {
      console.log('b', this);
    };
    anotherMethod();
  }
};
obj.sing();

Здесь для a контекст this равен obj.

Для b контекст this должен быть obj. Но так как this не имеет лексической области видимости и следует за вызовами методов, b имеет this контекст window.

Вот почему this следует за dynamic scoped вместо lexical scoped.

Сохранить этот контекст

Мы можем сохранить this контекст,

  • Сохранив его в переменной
  • Используя метод стрелки
  • Использование метода привязки (например, сначала привязка, а затем obj.prop()())

Чтобы решить эту проблему dynamic scope, мы можем использовать файл arrow method. Arrow method привязан к lexical scoped.

Использование метода стрелки

const obj = {
  name: 'Billy',
  sing() {
    console.log('a', this);
    var anotherMethod = () => {
      console.log('b', this);
    };
    anotherMethod();
  }
};
obj.sing();

Здесь и a, и b имеют this контекст obj.

Использование привязки:

Другим было сделать this до lexical scoped методом bind()

const obj = {
  name: 'Billy',
  sing() {
    console.log('a', this);
    var anotherMethod = function () {
      console.log('b', this);
    };
    return anotherMethod.bind(this);
  }
};
obj.sing()();

Здесь a и b имеют this контекст obj.

Сохранение в лексическом объеме:

Удержание this в другом объекте может использоваться для сохранения контекста this.

const obj = {
  name: 'Billy',
  sing() {
    console.log('a', this);
    var self = this;
    var anotherMethod = function () {
      console.log('b', self);
    };
    anotherMethod();
  }
};
obj.sing();

Здесь и a, и b имеют this контекст obj.

Манипулирование «этим»

Мы можем манипулировать/изменять ключевое слово this, используя следующие методы:

  • вызов
  • применять
  • связывать

Использование вызова

Мы можем использовать метод call для вызова метода, подобного следующему:

var myMethod = function () {};
// following both statements are similar
myMethod();
myMethod.call();

Мы будем использовать тот же механизм для ввода this,

const wizard = {
    name: 'Wizard',
    health: 50,
    heal() {
      this.health = 100;
    }
};
const archer = {
    name: 'Archer',
    health: 30
};
wizard.heal.call(archer);
console.log(archer);

Это будет печатать,

{
    name: 'Archer',
    health: 100
}

Здесь свойство здоровья такое же, как и у объекта wizard.

Теперь давайте посмотрим на другой пример передачи параметров с помощью метода call.

const wizard = {
  name: 'Wizard',
  health: 50,
  heal(param1, param2) {
    this.health = this.health + param1 + param2;
  }
};
const archer = {
  name: 'Archer',
  health: 30
};
wizard.heal.call(archer, 10, 20);
console.log(archer);

Это добавит параметры 10 и 20 с существующим значением 30.

Таким образом, напечатанное значение равно

{
  name: 'Archer',
  health: 60
}

Применить

apply() похож на call(), в основе лежит call(). Единственная разница между call() и apply() заключается в том, что в apply() параметр передается через круглые скобки.

var myMethod = function () {};
// following both statements are similar
myMethod();
myMethod.apply();

Мы можем обновить/изменить this с помощью apply следующим образом:

const wizard = {
  name: 'Wizard',
  health: 50,
  heal(param1, param2) {
    this.health = this.health + param1 + param2;
  }
};
const archer = {
  name: 'Archer',
  health: 30
};
wizard.heal.apply(archer, [10, 20]);
console.log(archer);

Это напечатает то же значение, что и предыдущий метод call().

{
  name: 'Archer',
  health: 60
}

Использование привязки

За исключением call() и bind(), bind() не вызывает метод мгновенно. Вместо этого он возвращает метод, который можно вызвать позже.

const wizard = {
  name: 'Wizard',
  health: 50,
  heal(param1, param2) {
    this.health = this.health + param1 + param2;
  }
};
const archer = {
  name: 'Archer',
  health: 30
};
const archerHeal = wizard.heal.bind(archer, 10, 20);
archerHeal();
console.log(archer);

Это напечатает то же значение, что и предыдущий метод call() или apply().

{
  name: 'Archer',
  health: 60
}

Мы можем использовать bind и получить интересный образец. Пусть у нас есть метод, который принимает два числа и возвращает умноженный результат.

const multiplyMethod = (num1, num2) => {
  return num1 * num2;
};

Теперь с помощью функции bind мы создадим два метода из предыдущего метода. Оба метода предоставляют только один параметр.

  • Один вернется умножить на 4
  • Другой вернется, умноженный на 10
let multiplyByTwo = multiplyMethod.bind(this, 4);
let multiplyByTen = multiplyMethod.bind(this, 10);
console.log(multiplyByTwo(2));
console.log(multiplyByTen(2));

Это вернется,

8
20

Преимущества

Разрешить методам использовать свойства самого объекта

const myObject = {
  name: 'foo',
  task: function () {
    return `${this.name} task.`;
  },
  doTask: function () {
    return `Do ${this.task()}`;
  }
};
myObject.doTask();

Это вернет Do foo task.

Выполнять один и тот же код для нескольких объектов

function showMyName() {
  console.log(this.name);
}
const foo = {
  name: 'foo',
  showMyName
};
const bar = {
  name: 'bar',
  showMyName
};
foo.showMyName();
bar.showMyName();

Это вернет

foo
bar

Логические

Заметим, пара примеров

Пример 01:

const myObj = {
  name: 'myName',
  myMethod() {
    console.log(this);
  }
};
myObj.myMethod();

Здесь this — это и есть myObj.

Пример 02:

const myObj = {
  name: 'myName',
  myMethod() {
    return function () {
      return console.log(this);
    };
  }
};
myObj.myMethod()();

Поскольку функция возврата не вызывается myObj, здесь объект this является window. Он использует dynamic scope вместо lexical scope.

Пример 03:

const myObj = {
  name: 'myName',
  myMethod() {
    return () => {
      return console.log(this);
    };
  }
};
myObj.myMethod()();

Поскольку arrow method строго поддерживает lexical scope, здесь this представляет myObj.