Преглед

Ключовата дума 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())

„това“ не е с лексикален обхват, той е с динамичен обхват

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.