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

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

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

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

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

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

но у него те же проблемы.

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





Answer 1 Felix Kling


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

Этот объект будет сделан контекстом всех связанных с Аяксом обратных вызовов.


Общая проблема:Использование объектных методов в качестве обработчиков обратного вызова

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

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

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

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

Функция this.method назначается в качестве обработчика события click, но если щелкнуть 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();



Answer 2 Mohan Dere


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

  1. Вы можете использовать функцию bind() .
  2. Храните ссылку на контекст внутри другой переменной (см.пример ниже).
  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 Хранить ссылку на контекст-это внутри другой переменной.

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



Answer 3 Guffa


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

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



Answer 4 RobG


Проблема с "контекстом"

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

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

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

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

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

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

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




Answer 5 Ashish


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

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

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

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

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

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

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

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

Метод 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 : функция стрелки - выражение функции стрелки является синтаксически компактной альтернативой регулярному выражению функции,

хотя и без собственных привязок к этому,аргументы,супер-или 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 () создает новую функцию, которая

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

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

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