javascript - with - Как получить доступ к правильному `this` внутри обратного вызова?




set context js (7)

У меня есть функция конструктора, которая регистрирует обработчик событий:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        alert(this.data);
    });
}

// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
var obj = new MyConstructor('foo', transport);

Однако я не могу получить доступ к свойству данных созданного объекта внутри обратного вызова. Похоже, что this относится не к объекту, который был создан, а к другому.

Я также попытался использовать объектный метод вместо анонимной функции:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

но это показывает те же проблемы.

Как я могу получить доступ к нужному объекту?


Вот несколько способов получить доступ к родительскому контексту внутри дочернего контекста:

  1. Вы можете использовать функцию bind () .
  2. Сохраните ссылку на context / this внутри другой переменной (см. Пример ниже).
  3. Используйте функции ES6 Arrow .
  4. Изменить код / ​​дизайн функции / архитектуру - для этого вы должны иметь команду над шаблонами проектирования в javascript.

1. Используйте функцию bind()

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', ( function () {
        alert(this.data);
    }).bind(this) );
}
// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};
// called as
var obj = new MyConstructor('foo', transport);

Если вы используете underscore.js - http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 Сохраните ссылку на context / this внутри другой переменной

function MyConstructor(data, transport) {
  var self = this;
  this.data = data;
  transport.on('data', function() {
    alert(self.data);
  });
}

3 Стрелка

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

Что вы должны знать об this

this (он же «контекст») является специальным ключевым словом внутри каждой функции, и его значение зависит только от того, как была вызвана функция, а не от того, как / когда / где она была определена. На него не влияют лексические области, как и другие переменные (кроме функций со стрелками, см. Ниже). Вот некоторые примеры:

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

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

Чтобы узнать больше об this , посмотрите документацию MDN .

Как правильно this исправить

Не используйте this

Вы на самом деле не хотите обращаться к this в частности, к объекту, на который он ссылается . Вот почему простое решение - просто создать новую переменную, которая также ссылается на этот объект. Переменная может иметь любое имя, но наиболее распространенными являются self и that .

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

Так как self - нормальная переменная, она подчиняется лексическим правилам области видимости и доступна внутри обратного вызова. Это также имеет то преимущество, что вы можете получить доступ к значению this самого обратного вызова.

Явно установить this обратного вызова - часть 1

Может показаться, что вы не можете контролировать это значение, потому что его значение устанавливается автоматически, но на самом деле это не так.

Каждая функция имеет метод .bind [docs] , который возвращает новую функцию с this привязкой к значению. Функция имеет точно такое же поведение, как и та, которую вы вызвали .bind , только то, что this было установлено вами. Независимо от того, как или когда вызывается this функция, она всегда будет ссылаться на переданное значение.

function MyConstructor(data, transport) {
    this.data = data;
    var boundFunction = (function() { // parenthesis are not necessary
        alert(this.data);             // but might improve readability
    }).bind(this); // <- here we are calling `.bind()` 
    transport.on('data', boundFunction);
}

В этом случае мы привязываем обратный вызов this к значению MyConstructor 's this .

Примечание. При связывании контекста для jQuery используйте jQuery.proxy [docs] . Причина этого заключается в том, что вам не нужно сохранять ссылку на функцию при отмене привязки обратного вызова события. JQuery обрабатывает это внутренне.

ECMAScript 6: использовать функции стрелок

ECMAScript 6 представляет функции стрелок , которые можно рассматривать как лямбда-функции. У них нет своей привязки. Вместо this это выглядит как область видимости обычной переменной. Это означает, что вам не нужно звонить .bind . Это не единственное специальное поведение, которое они имеют, пожалуйста, обратитесь к документации MDN для получения дополнительной информации.

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}

Установите this из обратного вызова - часть 2

Некоторые функции / методы, которые принимают обратные вызовы, также принимают значение, к которому относится обратный вызов. По сути, это то же самое, что связывать его самостоятельно, но функция / метод делает это за вас. Array#map [docs] - такой метод. Его подпись:

array.map(callback[, thisArg])

Первый аргумент - это обратный вызов, а второй аргумент - это значение, к которому следует обращаться. Вот надуманный пример:

var arr = [1, 2, 3];
var obj = {multiplier: 42};

var new_arr = arr.map(function(v) {
    return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument

Примечание. То, можете ли вы передать значение для this , обычно упоминается в документации к этой функции / методу. Например, метод $.ajax jQuery [docs] описывает параметр под названием context :

Этот объект станет контекстом всех обратных вызовов Ajax.

Распространенная проблема: использование объектных методов в качестве обратных вызовов / обработчиков событий

Другое распространенное проявление этой проблемы - когда объектный метод используется в качестве обработчика обратного вызова / события. Функции являются первоклассными гражданами в JavaScript, а термин «метод» - это просто разговорный термин для функции, которая является значением свойства объекта. Но эта функция не имеет конкретной ссылки на свой «содержащий» объект.

Рассмотрим следующий пример:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

Функция this.method назначается в качестве обработчика события click, но если щелкнуть this.method значение будет undefined , поскольку внутри обработчика события this относится к document.body , а не к экземпляру Foo .
Как уже упоминалось в начале, то, к чему this относится, зависит от того, как вызывается функция, а не от того, как она определена .
Если бы код был похож на следующий, может быть более очевидно, что функция не имеет неявной ссылки на объект:

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

Решение такое же, как упомянуто выше: если доступно, используйте .bind чтобы явно связать this с определенным значением

document.body.onclick = this.method.bind(this);

или явно вызвать функцию как «метод» объекта, используя анонимную функцию в качестве обработчика обратного вызова / события, и назначить объект ( this ) другой переменной:

var self = this;
document.body.onclick = function() {
    self.method();
};

или используйте функцию стрелки:

document.body.onclick = () => this.method();

В настоящее время возможен другой подход, если в коде используются классы.

С поддержкой полей класса это можно сделать следующим образом:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

Наверняка под капотом все старые добрые функции стрелок связывают контекст, но в этой форме это выглядит намного более ясным, чем явное связывание.

Поскольку это предложение этапа 3, вам понадобится babel и соответствующий плагин babel для его обработки, как сейчас (08/2018).


Во-первых, вам необходимо четко понимать scope и поведение this ключевого слова в контексте области scope .

this & scope :

there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

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

--------------------------------------------------------------------------------
-                                                                              -
-   Global Scope                                                               -
-   ( globally "this" refers to window object)                                 -     
-                                                                              -
-         function outer_function(callback){                                   -
-                                                                              -
-               // outer function scope                                        -
-               // inside outer function"this" keyword refers to window object -                                                                              -
-              callback() // "this" inside callback also refers window object  -

-         }                                                                    -
-                                                                              -
-         function callback_function(){                                        -
-                                                                              -
-                //  function to be passed as callback                         -
-                                                                              -
-                // here "THIS" refers to window object also                   -
-                                                                              -
-         }                                                                    -
-                                                                              -
-         outer_function(callback_function)                                    -
-         // invoke with callback                                              -
--------------------------------------------------------------------------------

Различные способы манипулировать this внутри функций обратного вызова:

Здесь у меня есть функция конструктора Person. У него есть свойство с именем name и четыре метода с именем sayNameVersion1 , sayNameVersion2 , sayNameVersion3 , sayNameVersion4 . У всех четырех из них есть одна конкретная задача. Принять обратный вызов и вызвать его. Обратный вызов имеет специальную задачу, которая заключается в регистрации свойства name экземпляра функции конструктора Person.

function Person(name){

    this.name = name

    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

}

function niceCallback(){

    // function to be used as callback

    var parentObject = this

    console.log(parentObject)

}

Теперь давайте создадим экземпляр из конструктора person и sayNameVersionX различные версии метода sayNameVersionX (X относится к 1,2,3,4) с помощью niceCallback чтобы увидеть, сколько способов мы можем манипулировать обратным вызовом this inside для обращения к экземпляру person .

var p1 = new Person('zami') // create an instance of Person constructor

связать:

Привязка заключается в создании новой функции с установленным значением ключевого слова this .

sayNameVersion1 и sayNameVersion2 используют bind для управления this функцией обратного вызова.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

Первый связывает this с обратным вызовом внутри самого метода. А для второго передается обратный вызов с привязанным к нему объектом.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

вызов :

first argument метода call используется как таковой внутри функции, которая вызывается с прикрепленным к ней call .

sayNameVersion3 использует call для манипулирования this чтобы ссылаться на объект person, который мы создали, вместо объекта window.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

и это называется следующим образом:

p1.sayNameVersion3(niceCallback)

применять :

Как и при call , первый аргумент apply относится к объекту, который будет указан this ключевым словом.

sayNameVersion4 использует apply чтобы манипулировать this чтобы ссылаться на объект person

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

и это называется следующим образом. Просто обратный вызов передается,

p1.sayNameVersion4(niceCallback)

Другим подходом, который является стандартным способом связывания this в DOM2 с помощью прослушивателя событий, позволяющим всегда удалять прослушиватель (среди других преимуществ), является метод handleEvent(evt) из интерфейса EventListener :

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

Подробную информацию об использовании handleEvent можно найти здесь: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38


Мы не можем связать это с setTimeout() , так как он всегда выполняется с глобальным объектом (Window) , если вы хотите получить доступ к this контексту в функции обратного вызова, то с помощью bind() к функции обратного вызова мы можем добиться как:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

Вы должны знать об этом ключевом слове.

На мой взгляд, вы можете реализовать «это» тремя способами (Self / Arrow function / Bind Method)

Ключевое слово this функции ведет себя немного иначе в JavaScript по сравнению с другими языками.

Он также имеет некоторые различия между строгим режимом и нестрогим режимом.

В большинстве случаев значение этого определяется тем, как вызывается функция.

Он не может быть установлен присваиванием во время выполнения, и он может отличаться при каждом вызове функции.

ES5 представил метод bind () для установки значения функции this независимо от того, как она вызывается,

и ES2015 представили функции стрелок, которые не обеспечивают собственную привязку this (она сохраняет значение this лексического контекста).

Метод 1: Self - Self используется для сохранения ссылки на оригинал, даже если контекст меняется. Эта техника часто используется в обработчиках событий (особенно в замыканиях).

Ссылка : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function () {
        alert(self.data);
    });
}

Метод 2 : функция стрелки - выражение функции стрелки является синтаксически компактной альтернативой регулярному выражению функции,

хотя и без привязок к ключевым словам this, arguments, super или new.target.

Выражения функций со стрелками плохо подходят в качестве методов, и их нельзя использовать в качестве конструкторов.

Ссылка : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',()=> {
        alert(this.data);
    });
}

Метод 3 : Bind - метод bind () создает новую функцию, которая,

при вызове имеет ключевое слово this установленное значение,

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

Ссылка: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',(function() {
        alert(this.data);
    }).bind(this);




this