Имам функция конструктор, която регистрира обработчик на събитие:
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
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
на обратната връзка към стойността на this
на MyConstructor
's.
Забележка: Когато свързвате контекст за jQuery, вместо това използвайте [jQuery.proxy
[docs]][proxy]. Причината за това е, че не е необходимо да съхранявате референцията към функцията, когато развързвате обратна връзка на събитие. 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
или не, обикновено се споменава в документацията на тази функция/метод. Например, методът $.ajax
на jQuery'[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
, записаната стойност ще бъде неопределена
, защото вътре в обработчика на събитието 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();
Всичко е в "магическия" синтаксис на извикване на метод:
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 за обозначаване на контекст на изпълнение, който представлява всички параметри, обхват и this в рамките на обхвата на някакъв изпълняващ се код.
Това е показано в ECMA-262, раздел 10.4.2:
Задайте ThisBinding на същата стойност като ThisBinding на извикващия контекст на изпълнение
което ясно показва, че this е част от контекста на изпълнение.
Контекстът на изпълнение предоставя заобикалящата информация, която добавя смисъл към кода, който се изпълнява. Той включва много повече информация, отколкото само thisBinding.
Така че стойността на this не е "контекст", а само една част от контекста на изпълнение. По същество това е локална променлива, която може да бъде зададена от извикването на всеки обект, а в строг режим - на всякаква стойност изобщо.