Как получить доступ к правильному `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();
Вот несколько способов получить доступ к родительскому контексту внутри дочернего контекста:
- Ты можешь использовать
bind()
функция. - Сохраните ссылку на context / this внутри другой переменной (см. Пример ниже).
- Используйте функции ES6 Arrow.
- Изменить код / дизайн функции / архитектуру - для этого вы должны иметь команду над шаблонами проектирования в 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
ведет себя по-другому, как показано ниже,
- Значение этого обычно определяется контекстом выполнения функций.
- В глобальной области это относится к глобальному объекту (объекту окна).
- Если строгий режим включен для какой-либо функции, тогда значение "this" будет "undefined", как и в строгом режиме, глобальный объект ссылается на undefined вместо объекта windows.
- Объект, который стоит перед точкой, является тем, к чему будет привязано ключевое слово this.
- Мы можем установить значение этого явно с помощью call(), bind() и apply()
- Когда используется новое ключевое слово (конструктор), оно привязывается к создаваемому новому объекту.
- Функции стрелки не связывают это - вместо этого это связано лексически (то есть основано на оригинальном контексте)
Как показывает большинство ответов, мы можем использовать функцию 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);
});
}
Рекомендации:
В настоящее время возможен другой подход, если в коде используются классы.
С поддержкой полей класса это можно сделать следующим образом:
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
по "правилу слева от точки":
- Когда функция создается с использованием ключевого слова 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
в этом случае.
некоторые другие люди коснулись того, как использовать метод .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;