#javascript #binding #prototype
#javascript #привязка #прототип
Вопрос:
У меня есть объект «me» с определенными для него функциями.
me.prototype.f = function(args){
console.log("f called");
};
Мне нужно предоставить f в качестве обратного вызова в асинхронной функции.
me.prototype.operation = function (){
var self = this;
var client; // an io client
//client.on("data", self.f); --a
//client.on("data", self.f.bind(self)) --b
}
Есть ли какая-либо разница между a amp; b выше в этом случае и есть ли какая-либо ситуация, когда a) может завершиться неудачей?
В каких сценариях мы получаем проблему без привязки? Как правило, это когда значения могут меняться в зависимости от контекста. (например. закрытие с использованием значения индекса цикла)
Итак, каковы могут быть разные сценарии, в которых значения могут действовать по-разному.
Из ответа я пришел к следующим выводам. Пожалуйста, подтвердите то же самое :
a) Область видимости будет меняться по мере передачи функции в качестве ссылки и будет получать локальную ссылку, если контекст не привязан с помощью bind, call или apply .
б) Особенно в обратных вызовах / асинхронных функциях, если контекст изменен (например, setTimeout, контекст изменится, следовательно, значение self изменится во время вызова.)
c) Это не имеет никакого отношения к закрытию, поскольку при закрытии лексическая среда проверяется на предмет оценки значений, однако здесь оценивается контекст во время выполнения
Комментарии:
1.У вас возникнут проблемы, если вы используете
this
в своейf
функции. Вот почему выbind
this
должны функционировать.2. Простой пример
var a = {f: function(){console.log(this)}}; a.f(); var f = a.f; f();
. вы потеряли своеthis
во втором выполнении так же, как потеряли его сself.f
3. @Maxx Если я вызову функцию as
client.on("data", self.f);
, в этом случае такжеthis
будет сохранен мой, поскольку контекст self будет связан с вызовом функции (поскольку это функция-член). Тогда есть ли какая-либо разница в a) и b)4. ваш
self
var абсолютно бесполезен в вашем примере, он ничего не делает. в a) вы передаете клиенту некоторую функциюf
, в момент, когда вашиclient
вызовыf
f
ничего не знают о ее контексте. в b) вы привязываете свой контекст кf
5.
self
здесь имеется в виду экземплярme
и, следовательно, предоставление связанных с ним функций. Прокомментированы два случая, которые я хочу обсудить.
Ответ №1:
self.f
является ссылкой на f
функцию.
self.f.bind(self)
это новая функция, которая при вызове вызывается f
с self
помощью as this
.
Есть ли какая-либо разница между a amp; b выше в этом случае и есть ли какая-либо ситуация, когда a) может выйти из строя?
ДА. Поведение зависит от того, как вы выполняете вызов:
function me() {}
me.prototype.f = function() {
console.log(this);
};
me.prototype.operation = function() {
client.on("this.f", this.f);
client.on("this.f.bind(this)", this.f.bind(this));
};
var client = {
on: function(whatever, callback) {
var self = this;
setTimeout(function() {
console.log("=== " whatever " ===");
self.f = callback;
// `this` not specified
// `this.f` logs the global object, or `undefined` in strict mode
// `this.f.bind(this)` logs `me` instance
callback();
// `this` belongs to current function (passed to setTimeout)
// `this.f` logs the global object, or `undefined` in strict mode
// `this.f.bind(this)` logs `me` instance
callback.call(this);
// `self` is a reference to the client object
// `this.f` logs `client`
// `this.f.bind(this)` logs `me` instance
callback.call(self);
// `this` is determined by the calling object (client again)
// `this.f` logs `client`
// `this.f.bind(this)` logs `me` instance
self.f();
}, 1000);
}
};
(new me()).operation();
Обновить
Я решил добавить больше к ответу, поскольку вы добавили больше к вопросу. Мне жаль, что это получилось так долго.
Как this
работает
Значение this
определяется при вызове функции, а не при определении функции.
-
При вызове функции, которая не является свойством объекта
this
, будет находитьсяundefined
в строгом режиме или ссылаться на глобальный объект в нестрогом режиме.function f1() { console.log(this === window); } function f2() { 'use strict'; console.log(this === undefined); } f1(); // true f2(); // true
-
При вызове функции, которая является свойством объекта,
this
будет ссылаться на этот объект.var object = { f: function() { console.log(this === object); } }; object.f(); // true
-
При вызове функции с
new
префиксомthis
будет ссылаться на
вновь созданный объект.var backup; function Example() { backup = this; this.value = 0; } var example = new Example(); console.log(example === backup); // true console.log(example.value); // 0
-
При вызове функции, которая не является свойством объекта, но
является частью цепочки прототипов объекта,this
будет ссылаться на
вызывающий объект.function Example() {} Example.prototype.f = function() { console.log(this === example); }; var example = new Example(); example.f(); // true
Изменение this
Упомянутое поведение часто не то, что мы хотим. Рассмотрим следующую ситуацию.
function Counter() {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval(update, 1000);
function update() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
}
};
var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...
this
получает свое значение после вызова функции, а не после ее определения. Интервальная функция будет вызвана позже и this
будет ссылаться на глобальный объект. В большинстве случаев это не то, что нужно.
Изменение this
неявно
Исторический способ решения setInterval
проблемы — назначить ссылку this
на переменную, видимую проблемной функцией, и использовать вместо нее эту переменную.
function Counter() {}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
var self = this;
function update() {
console.log(self.count);
if ( self.count > self.limit)
clearInterval(self.interval);
}
this.interval = setInterval(update, 1000);
};
var counter = new Counter();
counter.countTo(5);
То же самое с использованием замыкания, чтобы предотвратить загрязнение области действия функции.
function Counter() {}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval((function(self) {
return function() {
console.log(self.count);
if ( self.count > self.limit)
clearInterval(self.interval);
};
}(this)), 1000);
};
var counter = new Counter();
counter.countTo(5);
Создание новой update
функции всякий Counter
раз, когда создается a, не самый эффективный способ сделать это. Допустим, мы хотим переместить его в прототип.
function Counter() {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval(this.update, 1000);
};
Counter.prototype.update = function() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
};
var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...
Теперь проблема заключается в том update
, чтобы узнать о self
s, поскольку они больше не определены в одной области видимости, и update
для всех s существует только одна функция Counter
.
Мы можем создать функцию-посредник, которая будет заключать значение (копировать ссылку) this
и использовать его для выполнения вызова.
function Counter() {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
var update = (function(self) {
return function() {
self.update();
};
}(this));
this.interval = setInterval(update, 1000);
};
Counter.prototype.update = function() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
};
var counter = new Counter();
counter.countTo(5);
Мы можем использовать функции со стрелками, которые связываются this
лексически, то есть их значение определяется при определении функции, независимо от того, как и когда она вызывается.
function Counter() {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval(() => this.update(), 1000); // neat.
};
Counter.prototype.update = function() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
};
var counter = new Counter();
counter.countTo(5);
Изменение this
явно
Допустим, наша update
функция определена в другом месте, и мы хотим использовать ее вместо нашей функции-прототипа.
function Counter(to) {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval(update, 1000);
};
function update() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...
Используя ранее упомянутые подходы, мы могли бы прикрепить ссылку к update
объекту или его прототипу и использовать, скажем, функцию со стрелкой для привязки this
. Если нам не нравятся такого рода накладные расходы, в нашем распоряжении есть три мощные функции для явной настройки this
:
Вызов bind
функции приводит к созданию новой функции, для которой this
установлен первый параметр.
function Counter(to) {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
this.interval = setInterval(update.bind(this), 1000); // not bad.
};
function update() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5);
Использование call
или apply
для функции вызовет вызов с this
установленным первым параметром.
function Counter(to) {
this.count;
this.limit;
this.interval;
}
Counter.prototype.countTo = function(number) {
this.count = 1;
this.limit = number;
var self = this;
this.interval = setInterval(function() {
update.call(self);
}, 1000);
};
function update() {
console.log(this.count);
if ( this.count > this.limit)
clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5);
Комментарии:
1. Спасибо, я много экспериментировал с вышеизложенным. Возникли следующие наблюдения. a) Область видимости будет меняться по мере передачи функции в качестве ссылки и будет получать локальную ссылку, если контекст не привязан с помощью bind, call или apply . б) Особенно в обратных вызовах / асинхронных функциях, если контекст изменен (например, setTimeout , контекст изменится, следовательно, значение self изменится во время вызова.) в) Это не имеет никакого отношения к закрытию, поскольку при закрытии лексическая среда проверяется на предмет оценки значений, однако здесьоценивается контекст во время выполнения
2. Ваши наблюдения верны. Дело в том, что
this
определяется при вызове функции , а не при определении функции .3. Во многих ситуациях это не то, что вы хотите, и чтобы обойти это, вы явно устанавливаете
this
значение, используяbind
,call
илиapply
, или простую функцию, используя сохраненную ссылкуthis
из ее среды.4. Одна вещь, которую я забыл упомянуть, — это функции со стрелками ES6 , которые связываются
this
лексически, то есть определяются определением функции .5. Интересны последние два пункта. В
simple function using the stored reference of this
— вы имеете в виду вызов функции с сохраненной ссылкой в качестве одного из параметров.