Как получить доступ к правильному `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);
};
но у него те же проблемы.
Как мне получить доступ к нужному объекту?
Ответы
Что вам следует знать о 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
Используйте стрелочные функции
В ECMAScript 6 появились стрелочные функции , которые можно рассматривать как лямбда-функции. У них нет собственной thisпривязки. Вместо этого thisищется в области видимости как обычная переменная. Это означает, что вам не нужно звонить .bind. Это не единственное их особенное поведение, дополнительную информацию см. В документации MDN.
function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}
Не использовать 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к значению MyConstructors this.
Примечание: при привязке контекста для jQuery используйте вместо этого jQuery.proxy [docs] . Причина в том, что вам не нужно сохранять ссылку на функцию при отмене привязки обратного вызова события. jQuery обрабатывает это внутренне.
Набор thisобратного вызова - часть 2
Некоторые функции / методы, которые принимают обратные вызовы, также принимают значение, на которое thisдолжен ссылаться обратный вызов . Это в основном то же самое, что и привязка самостоятельно, но функция / метод делает это за вас. Array#map [docs] - вот такой метод. Его подпись:
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 [docs]$.ajax описывает параметр, называемый 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();
Вот несколько способов получить доступ к родительскому контексту внутри дочернего контекста -
- Вы можете использовать bind()функцию.
- Сохраните ссылку на context / this внутри другой переменной (см. Пример ниже).
- Используйте стрелочные функции ES6 .
- Измените код / функциональный дизайн / архитектуру - для этого у вас должна быть команда над шаблонами проектирования в 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);
  });
}
Все дело в «волшебном» синтаксисе вызова метода:
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));
Проблема с "контекстом"
Термин «контекст» иногда используется для обозначения объекта, на который ссылается this . Его использование неуместно, потому что он не соответствует ни семантически, ни технически с ECMAScript's this .
«Контекст» означает обстоятельства, окружающие что-то, что добавляет значение, или некоторую предшествующую и последующую информацию, которая придает дополнительный смысл. Термин «контекст» используется в ECMAScript для обозначения контекста выполнения , который представляет собой все параметры, область действия и это в рамках некоторого исполняемого кода.
Это показано в разделе 10.4.2 ECMA-262 :
Установите ThisBinding на то же значение, что и ThisBinding вызывающего контекста выполнения.
что ясно указывает на то, что это часть контекста выполнения.
Контекст выполнения предоставляет окружающую информацию, которая добавляет смысл исполняемому коду. Он включает гораздо больше информации, чем просто thisBinding .
Значит, значение этого не «контекст», это всего лишь одна часть контекста выполнения. По сути, это локальная переменная, которой можно задать любое значение при вызове любого объекта в строгом режиме.
Вы должны знать об "этом" ключевом слове.
На мой взгляд, вы можете реализовать «это» тремя способами (Self / Arrow function / Bind Method)
Ключевое слово this функции в JavaScript ведет себя немного иначе, чем в других языках.
Также есть некоторые различия между строгим режимом и нестрогим режимом.
В большинстве случаев значение этого параметра определяется тем, как вызывается функция.
Его нельзя установить путем присвоения во время выполнения, и он может отличаться при каждом вызове функции.
ES5 представил метод bind () для установки значения функции this независимо от того, как она вызывается,
и ES2015 представили стрелочные функции, которые не обеспечивают своей собственной привязки this (она сохраняет это значение включающего лексического контекста).
Method1: Self - Self используется для сохранения ссылки на оригинал this даже при изменении контекста. Этот метод часто используется в обработчиках событий (особенно при закрытии).
Ссылка : 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);
    });
}
Method3 : 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);
Во-первых, вам необходимо четко понимать 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(X относится к 1,2,3,4), niceCallbackчтобы увидеть, сколькими способами мы можем управлять thisвнутренним обратным вызовом, чтобы ссылаться на personэкземпляр.
var p1 = new Person('zami') // create an instance of Person constructor
Что делает bind, так это создание новой функции с 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метода используется как 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ведет себя иначе, как показано ниже,
- Значение thisобычно определяется контекстом выполнения функции.
- В глобальной области thisотносится к глобальному объекту (windowобъекту).
- Если строгий режим включен для какой-либо функции, то значение thisбудет таким же,undefinedкак и в строгом режиме, глобальный объект ссылается наundefinedвместоwindowобъекта.
- Объект, стоящий перед точкой, - это то, к чему будет привязано это ключевое слово.
- Мы можем установить значение этого явно с call(),bind()иapply()
- Когда используется newключевое слово (конструктор), оно привязано к новому создаваемому объекту.
- Стрелочные функции не привязываются this- вместо этого ониthisсвязаны лексически (т. Е. На основе исходного контекста)
Как подсказывает большинство ответов, мы можем использовать функцию стрелки, bind()метод или собственную переменную. Я бы процитировал пункт о лямбдах (функциях стрелки) из Google JavaScript Style Guide.
Предпочитайте использовать стрелочные функции вместо f.bind (this) и особенно над goog.bind (f, this). Избегайте записи const self = this. Стрелочные функции особенно полезны для обратных вызовов, которые иногда неожиданно передают дополнительные аргументы.
Google явно рекомендует использовать лямбда-выражения вместо привязки или const self = this
Итак, лучшим решением было бы использовать лямбды, как показано ниже,
function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
Ссылки:
В настоящее время возможен другой подход, если классы используются в коде.
При поддержке полей класса можно сделать это следующим образом:
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
this в JS:
Значение thisв JS на 100% определяется тем, как функция вызывается, а не тем, как она определяется. Мы можем достаточно легко найти значение thisсамых «слева от правила точечной» :
- Когда функция создается с использованием ключевого слова function, значением thisявляется объект слева от точки вызываемой функции.
- Если от точки нет объекта, то значение thisвнутри функции часто является глобальным объектом (globalв узле,windowв браузере). Я бы не рекомендовал использоватьthisздесь ключевое слово, потому что оно менее явное, чем использование чего-то вродеwindow!
- Существуют определенные конструкции, такие как стрелочные функции и функции, созданные с использованием 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 (игнорируя первый журнал, начиная со второго):
- thisэто- obj2из-за левой точки правила, мы можем видеть, как- test1называется- obj2.test1();.- obj2находится слева от точки и, следовательно, от- thisзначения.
- Несмотря на то, obj2что осталось от точки,test2связан сobj1помощьюbind()метода. Итак,thisценность естьobj1.
- obj2находится слева от точки из функции , которая называется:- obj2.test3(). Следовательно,- obj2будет стоимость- this.
- В данном случае: obj2.test4()obj2слева от точки. Однако у стрелочных функций нет собственнойthisпривязки. Следовательно, он будет привязан кthisзначению внешней области видимости, которая являетсяmodule.exportsобъектом, который был зарегистрирован в начале.
- Мы также можем указать значение thisс помощьюcallфункции. Здесь мы можем передать желаемоеthisзначение в качестве аргумента, какobj2в данном случае.
Я столкнулся проблемы с 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
 }