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

Nov 29 2013

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

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

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

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

Ответы

1910 FelixKling Nov 29 2013 at 13:13

Что вам следует знать о 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();
225 MohanDere Aug 13 2016 at 17:26

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

  1. Вы можете использовать bind()функцию.
  2. Сохраните ссылку на context / this внутри другой переменной (см. Пример ниже).
  3. Используйте стрелочные функции ES6 .
  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);
  });
}
59 Guffa May 21 2014 at 07:11

Все дело в «волшебном» синтаксисе вызова метода:

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));
34 RobG Jun 01 2014 at 07:44

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

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

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

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

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

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

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

Значит, значение этого не «контекст», это всего лишь одна часть контекста выполнения. По сути, это локальная переменная, которой можно задать любое значение при вызове любого объекта в строгом режиме.

32 Ashish Jan 30 2019 at 18:01

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

На мой взгляд, вы можете реализовать «это» тремя способами (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);
26 AL-zami Aug 19 2017 at 00:58

Во-первых, вам необходимо четко понимать 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)
20 DattaChanewad Nov 17 2017 at 21:32

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

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
13 Code_Mode Feb 18 2019 at 16:10

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

  1. Значение thisобычно определяется контекстом выполнения функции.
  2. В глобальной области thisотносится к глобальному объекту ( windowобъекту).
  3. Если строгий режим включен для какой-либо функции, то значение thisбудет таким же, undefinedкак и в строгом режиме, глобальный объект ссылается на undefinedвместо windowобъекта.
  4. Объект, стоящий перед точкой, - это то, к чему будет привязано это ключевое слово.
  5. Мы можем установить значение этого явно с call(), bind()иapply()
  6. Когда используется newключевое слово (конструктор), оно привязано к новому создаваемому объекту.
  7. Стрелочные функции не привязываются 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);
  });
}

Ссылки:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. стрелки-функции-против-привязки
8 skyboyer Sep 22 2018 at 20:38

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

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

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

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

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

5 AndreaPuddu Aug 28 2018 at 16:10

Другой подход, который является стандартным, начиная с 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

2 WillemvanderVeen May 05 2020 at 15:48

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в данном случае.
1 Md.TazbirUrRahmanBhuiyan Oct 12 2020 at 23:07

Я столкнулся проблемы с 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
 }