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

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

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);

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

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

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

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

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

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

16 ответов

Решение

Что вы должны знать о 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

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

Каждая функция имеет метод .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 [документы] вместо. Причина этого заключается в том, что вам не нужно сохранять ссылку на функцию при отмене привязки обратного вызова события. JQuery обрабатывает это внутренне.

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

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

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

Задавать this обратного вызова - часть 2

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

array.map(callback[, thisArg])

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

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 обычно упоминается в документации этой функции / метода. Например, jQuery's $.ajax Метод[docs] описывает опцию под названиемcontext:

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


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

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

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

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

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

Функция this.methodназначается обработчиком события клика, но если document.body нажата, зарегистрированное значение будет 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();

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

  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/

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);
  });
}

Это все в "волшебном" синтаксисе вызова метода:

object.property();

Когда вы получаете свойство от объекта и вызываете его за один раз, объект будет контекстом для метода. Если вы вызываете тот же метод, но в отдельных шагах, вместо этого контекст является глобальной областью (окном):

var f = object.property;
f();

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

this.saveNextLevelData(this.setAll);

Вот где вы бы связали контекст с функцией:

this.saveNextLevelData(this.setAll.bind(this));

Если вы используете JQuery, вы должны использовать $.proxy метод вместо того, как bind не поддерживается во всех браузерах:

this.saveNextLevelData($.proxy(this.setAll, this));

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

На мой взгляд, вы можете реализовать "это" тремя способами (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);

Беда с "контекстом"

Термин "контекст" иногда используется для обозначения объекта, на который ссылается это. Его использование неуместно, потому что он не подходит ни семантически, ни технически к ECMAScript.

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

Это показано в разделе 10.4.2 ECMA-262:

Установите для ThisBinding то же значение, что и для ThisBinding для вызывающего контекста выполнения.

что ясно указывает на то, что это часть контекста исполнения.

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

Таким образом, значение этого не "контекст", это просто одна часть контекста исполнения. По сути, это локальная переменная, которая может быть установлена ​​при вызове любого объекта и в строгом режиме для любого значения вообще.

Во-первых, вам нужно иметь четкое понимание 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 (Х относится к 1,2,3,4) метод с niceCallback чтобы увидеть, сколько способов мы можем манипулировать this внутри обратного вызова, чтобы обратиться к person пример.

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

связать:

Что делать связать, это создать новую функцию с this Ключевое слово установлено на указанное значение.

sayNameVersion1 а также sayNameVersion2 использовать связывание, чтобы манипулировать 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 метод используется как this внутри функции, которая вызывается с call прикреплен к нему.

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

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

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

p1.sayNameVersion3(niceCallback)

применять:

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

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

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

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

p1.sayNameVersion4(niceCallback)

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

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

Вопрос вращается вокруг того, как this Ключевое слово ведет себя в JavaScript. this ведет себя по-другому, как показано ниже,

  1. Значение этого обычно определяется контекстом выполнения функций.
  2. В глобальной области это относится к глобальному объекту (объекту окна).
  3. Если строгий режим включен для какой-либо функции, тогда значение "this" будет "undefined", как и в строгом режиме, глобальный объект ссылается на undefined вместо объекта windows.
  4. Объект, который стоит перед точкой, является тем, к чему будет привязано ключевое слово this.
  5. Мы можем установить значение этого явно с помощью call(), bind() и apply()
  6. Когда используется новое ключевое слово (конструктор), оно привязывается к создаваемому новому объекту.
  7. Функции стрелки не связывают это - вместо этого это связано лексически (то есть основано на оригинальном контексте)

Как показывает большинство ответов, мы можем использовать функцию Arrow или bind() Метод или Self вар. Я бы процитировал пункт о лямбдах (функция стрелки) из руководства по стилю Google JavaScript

Предпочитайте использовать функции стрелок над f.bind(это), и особенно над goog.bind(f, это). Избегайте писать const self = this. Функции стрелок особенно полезны для обратных вызовов, которые иногда передают неожиданные дополнительные аргументы.

Google явно рекомендует использовать лямбды, а не связывать или const self = this

Таким образом, лучшее решение будет использовать лямбды, как показано ниже,

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

Рекомендации:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. стрелка-функции-против-безвыходном

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

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

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

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

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

Другой подход, который является стандартным способом связывания DOM2 this в слушателе событий, который позволяет вам всегда удалять слушателя (среди других преимуществ), является 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

Я столкнулся с проблемой Ngx линейный график xAxisTickFormatting функция, которая вызывалась из HTML следующим образом: [xAxisTickFormatting]="xFormat". Мне не удалось получить доступ к переменной моего компонента из объявленной функции. Это решение помогло мне решить проблему, чтобы найти правильный ответ. Надеюсь, это поможет Ngx линейный график, пользователи.

вместо использования такой функции:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result 
}

Использовать этот:

 xFormat = (value) => {
   // console.log(this);
   // now you have access to your component variables
   return value + this.oneComponentVariable
 }

this в JS:

Значение thisв JS на 100% определяется тем, как функция вызывается, а не тем, как она определяется. Мы можем относительно легко найти значениеthisпо "правилу слева от точки":

  1. Когда функция создается с использованием ключевого слова function, значение this это объект слева от точки функции, которая вызывается
  2. Если от точки не осталось объекта, то значение this внутри функции часто находится глобальный объект (global в узле, windowв браузере). Я бы не рекомендовал использоватьthis ключевое слово здесь, потому что оно менее явное, чем использование чего-то вроде window!
  3. Существуют определенные конструкции, такие как стрелочные функции и функции, созданные с использованием Function.prototype.bind() функция, которая может фиксировать значение this. Это исключения из правила, но они действительно помогают исправить значениеthis.

Пример в nodeJS

module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);

const obj1 = {
    data: "obj1 data",
    met1: function () {
        console.log(this.data);
    },
    met2: () => {
        console.log(this.data);
    },
};

const obj2 = {
    data: "obj2 data",
    test1: function () {
        console.log(this.data);
    },
    test2: function () {
        console.log(this.data);
    }.bind(obj1),
    test3: obj1.met1,
    test4: obj1.met2,
};

obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);

Выход:

Позвольте мне провести вас по выходам 1 на 1 (игнорируя первый журнал, начиная со второго):

  1. this является obj2 из-за левой точки правила мы можем видеть, как test1 называется obj2.test1();. obj2 находится слева от точки, поэтому this ценность.
  2. Даже если obj2 слева от точки, test2 связан с obj1 через bind()метод. Так чтоthis ценность obj1.
  3. obj2 находится слева от точки вызываемой функции: obj2.test3(). Следовательноobj2 будет стоимость this.
  4. В этом случае: obj2.test4() obj2слева от точки. Однако стрелочные функции не имеют собственныхthisпривязка. Поэтому он будет привязан кthis значение внешней области, которая является module.exports объект, который был зарегистрирован в начале.
  5. Мы также можем указать значение this используя callфункция. Здесь мы можем перейти к желаемомуthis значение в качестве аргумента, которое obj2 в этом случае.

некоторые другие люди коснулись того, как использовать метод .bind(), но, в частности, вот как вы можете использовать его с .then (), если у кого-то возникают проблемы с их совместной работой

      someFunction()
.then(function(response) {
    //'this' wasn't accessible here before but now it is            
}.bind(this))

РЕДАКТИРОВАТЬ: как упоминалось в комментариях, альтернативой было бы использование функции стрелки, у которой нет собственного значения 'this'

      someFunction()
.then((response)=>{
    //'this' was always accessible here         
})

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

      const functionToTest = (dataToSet , transport) => {
  this.dataToSet = dataToSet ;
  transport.on('dataToSet ', () => {
    console.log(this.dataToSet);
  });
}

Вот как я решил проблему

Нам нужно инициализировать состояние в конструкторе. Используйте this.state.students вместо this.students. Попробуйте этот код -

        import React from 'react'
  import './App.css';
  import {db} from "./Firebase";

  class App extends React.Component {
  constructor(props) {
  super(props);
  this.state = {
     students: null
   }
  }


componentDidMount() {
    db.ref("/posts/").once("value").then(function (snapshot) {
        const students = []
        snapshot.forEach(function (childSnapshot) {
            const childData = childSnapshot.val();
            students.push(childData);
        });
        this.setState({students: students});
        console.log(this.state.students);
    });
   }
}

  export default App;
Другие вопросы по тегам