Как получить доступ к правильному `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);
Ответы
Ответ 1
Что вы должны знать об 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
on, только то, что 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
this
.
Примечание. При связывании контекста для jQuery вместо этого используйте jQuery.proxy
[docs]. Причина этого заключается в том, что вам не нужно сохранять ссылку на функцию при отмене обратного вызова события. jQuery обрабатывает это внутренне.
В ECMAScript 6 представлены функции стрелок, которые можно рассматривать как лямбда-функции. Они не имеют свои собственные this
связывание. Вместо this
это рассматривается в области видимости как обычная переменная. Это означает, что вам не нужно вызывать .bind
. Это не единственное особое поведение, которое у них есть, для получения дополнительной информации обратитесь к документации MDN.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
Установите 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 $.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();
Ответ 2
Вот несколько способов доступа к родительскому контексту внутри дочернего контекста -
- Вы можете использовать функцию
bind()
. - Храните ссылку на контекст/внутри внутри другой переменной (см. Пример ниже).
- Используйте функции ES6 Arrow.
- Alter Code/function design/architecture - для этого вы должны иметь команду над шаблонами проектирования в javascript.
1. Используйте функцию bind()
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', ( function () {
alert(this.data);
}).bind(this) );
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
Если вы используете underscore.js
- http://underscorejs.org/#bind
transport.on('data', _.bind(function () {
alert(this.data);
}, this));
2 Сохраните ссылку на контекст/внутри внутри другой переменной
function MyConstructor(data, transport) {
var self = this;
this.data = data;
transport.on('data', function() {
alert(self.data);
});
}
3 Функция стрелки
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
Ответ 3
Все это в "волшебном" синтаксисе вызова метода:
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));
Ответ 4
Беда с "контекстом"
Термин "контекст" иногда используется для обозначения объекта, на который ссылается это. Его использование неуместно, потому что это не соответствует ни семантически, ни технически с ECMAScript this.
"Контекст" означает обстоятельства, окружающие что-то, что добавляет значение, или некоторую предшествующую и следующую информацию, которая придает дополнительный смысл. Термин "контекст" используется в ECMAScript для обозначения контекста исполнения, который представляет собой все параметры, область действия и это в рамках некоторого исполняемого кода.
Это показано в разделе ECMA-262 10.4.2:
Установите для ThisBinding то же значение, что и для ThisBinding вызывающего контекста выполнения.
который ясно указывает на то, что это является частью контекста исполнения.
Контекст выполнения предоставляет окружающую информацию, которая добавляет смысл к исполняемому коду. Он включает в себя гораздо больше информации, чем просто thisBinding.
Таким образом, значение этого не "контекст", это просто одна часть контекста исполнения. По сути, это локальная переменная, которая может быть установлена при вызове любого объекта и в строгом режиме для любого значения вообще.
Ответ 5
Во-первых, вам нужно иметь четкое представление о scope
и поведении this
ключевого слова в контексте области scope
.
this
& scope
:
there are two types of scope in javascript. They are :
1) Global Scope
2) Function Scope
Короче говоря, глобальная область действия относится к объекту window.Variables, объявленные в глобальной области, доступны из любого места. С другой стороны, область функций находится внутри функции. переменная, объявленная внутри функции, не может быть доступна из внешнего мира. this
ключевое слово в глобальной области видимости относится к объекту окна. this
внутренняя функция также относится к объекту window.So, 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, а не на объект окна.
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)
Ответ 6
Мы не можем привязать это к setTimeout()
, поскольку он всегда выполняется с глобальным объектом (Window), если вы хотите получить доступ к this
контексту в функции обратного вызова, а затем используя функцию bind()
для функции обратного вызова, которую мы можем достичь как:
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
Ответ 7
Вы должны знать об этом ключевом слове.
На мой взгляд, вы можете реализовать "это" тремя способами (Self/Arrow function/Bind Method)
Функция это ключевое слово ведет себя немного иначе в 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);
Ответ 8
В настоящее время существует другой подход, если классы используются в коде.
С поддержкой полей классов можно сделать следующий путь:
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // this refers to correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
Конечно, под капотом все старые функции доброй стрелки, которые связывают контекст, но в этой форме выглядит намного яснее, чем явное связывание.
Поскольку это предложение Stage 3 вам понадобится babel и соответствующий плагин babel для его обработки как сейчас (08/2018).
Ответ 9
Другой подход, который является стандартным способом, поскольку 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
Ответ 10
Вопрос вращается вокруг того, как this
ключевое слово ведет себя в JavaScript. this
ведет себя иначе, как показано ниже,
- Значение
this
обычно определяется контекстом выполнения функций. - В глобальной области
this
относится к глобальному объекту (объекту window
). - Если строгий режим включен для любой функции, то значение
this
будет undefined
как в строгом режиме, глобальный объект ссылается на undefined
вместо объекта window
. - Объект, который стоит перед точкой, является тем, к чему будет привязано ключевое слово this.
- Мы можем установить значение этого явно с помощью
call()
, bind()
и apply()
- Когда используется
new
ключевое слово (конструктор), оно привязывается к создаваемому новому объекту. - Функции стрелки не связывают
this
- вместо this
это связано лексически (то есть основано на оригинальном контексте)
Как подсказывает большинство ответов, мы можем использовать функцию Arrow или метод bind()
или Self var. Я бы процитировал пункт о лямбдах (функция стрелки) из руководства по стилю 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);
});
}
Рекомендации:
- https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
- стрелка-функции-против-безвыходном