Как получить доступ к правильному `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
к значению MyConstructor
s 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
}