Цепочка вызовов .bind() в JavaScript. Неожиданный результат?
Из MDN:
Метод bind() создает новую функцию, которая при вызове имеет это ключевое слово, установленное на предоставленное значение
И я могу с радостью увидеть, как он работает в этом примере:
(function () {
console.log(this);
}).bind({foo:"bar"})();
который регистрирует Object { foo="bar"}
.
Но если я связываю другой вызов вызова или даже вызов вызова, я все равно получаю функцию, вызванную с помощью "this", назначенной объекту, переданному первому bind. Примеры:
(function () {
console.log(this);
}).bind({foo:"bar"}).bind({oof:"rab"})();
&
(function () {
console.log(this);
}).bind({foo:"bar"}).call({oof:"rab"});
Оба log Object { foo="bar"}
вместо того, что я ожидал бы: Object { oof="rab"}
.
Независимо от того, сколько цепочек вызовов я связываю, кажется, что только первый из них имеет эффект.
Почему?
Это может помочь. Я только что узнал, что версия jQuery ведет себя одинаково!: О
jQuery.proxy(
jQuery.proxy(function() {
console.log(this);
},{foo:"bar"})
,{oof:"rab"})();
logs Object { foo="bar"}
Ответы
Ответ 1
Заманчиво думать о bind
как о некоей модификации функции для использования нового this
. В этой (неправильной) интерпретации люди думают о bind
как добавлении какого-то волшебного флага в функцию, сообщающую ему использовать другой this
при следующем вызове. Если это так, тогда должно быть возможно "переопределить" и изменить волшебный флаг. И тогда спросите, в чем причина произвольного ограничения возможности сделать это?
Но на самом деле, что не, как это работает. bind
создает и возвращает новую функцию, которая при вызове вызывает первую функцию с определенным this
. Поведение этой вновь созданной функции, чтобы использовать указанную this
для вызова исходной функции, записывается в при создании функции. Он не может быть изменен больше, чем внутренние функции любой другой функции, возвращаемой функцией, могут быть изменены после факта.
Это может помочь рассмотреть реальную простую реализацию bind
:
// NOT the real bind; just an example
Function.prototype.bind = function(ctxt) {
var fn = this;
return function bound_fn() {
return fn.apply(ctxt, arguments);
};
}
my_bound_fn = original_fn.bind(obj);
Как вы можете видеть, нигде в bound_fn
функция, возвращаемая из bind
, ссылается на this
, с которой была вызвана связанная функция. Он игнорируется, так что
my_bound_fn.call(999, arg) // 999 is ignored
или
obj = { fn: function () { console.log(this); } };
obj.fn = obj.fn.bind(other_obj);
obj.fn(); // outputs other_obj; obj is ignored
Таким образом, я могу связать возвращаемую функцию из bind
"снова", но это не, восстанавливающая исходную функцию; он просто связывает внешнюю функцию, которая не влияет на внутреннюю функцию, поскольку она уже настроена для вызова базовой функции с контекстом (this
value), переданным в bind
. Я могу связывать снова и снова, но все, что я делаю, это создание более внешних функций, которые могут быть связаны чем-то, но все же в конечном итоге вызывают самую внутреннюю функцию, возвращенную с первого bind
.
Поэтому несколько неверно утверждать, что bind
"нельзя переопределить".
Если я хочу "перестроить" функцию, тогда я могу просто выполнить новое привязку к исходной функции. Поэтому, если я связал его один раз:
function orig() { }
my_bound_fn = orig.bind(my_obj);
а затем я хочу, чтобы моя оригинальная функция вызывалась с каким-то другим this
, тогда я не переустанавливаю связанную функцию:
my_bound_fn = my_bound_fn.bind(my_other_obj); // No effect
Вместо этого я просто создаю новую функцию, связанную с исходной:
my_other_bound_fn = orig.bind(my_other_obj);
Ответ 2
Я нашел эту строку в MDN:
Функция bind() создает новую функцию (связанную функцию) с то же тело функции (внутреннее свойство вызова в терминах ECMAScript 5), как вызываемой функции (целевой функции границы функция) с этим значением, связанным с первым аргументом bind(), , который нельзя переопределить.
поэтому, возможно, он не может быть переопределен после его установки.
Ответ 3
Торазабуро отличный ответ дал мне идею. Было бы возможно, чтобы функция, подобная связыванию, вместо того, чтобы выпекать приемник (этот) в вызове внутри замыкания, чтобы поместить его как свойство в объект функции, а затем использовать его при вызове. Это позволит повторной попытке обновить свойство до того, как будет выполнен вызов, эффективно предоставив ожидаемые результаты повторной обработки.
Например,
function original_fn() {
document.writeln(JSON.stringify(this));
}
Function.prototype.rebind = function(obj) {
var fn = this;
var bound = function func() {
fn.call(func.receiver, arguments);
};
bound.receiver = obj;
bound.rebind = function(obj) {
this.receiver = obj;
return this;
};
return bound;
}
var bound_fn = original_fn.rebind({foo: 'bar'});
bound_fn();
var rebound_fn = bound_fn.rebind({fred: 'barney'});
rebound_fn();
Ответ 4
Хорошо, это будет главным образом предположение, но я попытаюсь объяснить это.
В спецификации ECMAScript (которая в настоящее время недоступна) указано следующее для функции bind
(выделение мое собственное):
15.3.4.5 Function.prototype.bind(thisArg [, arg1 [, arg2,...]])
Метод bind принимает один или несколько аргументов, thisArg и (необязательно) arg1, arg2 и т.д., и возвращает новый объект функции, выполняя следующие шаги:
- Пусть Target - это значение.
- Если IsCallable (Target) имеет значение false, введите исключение TypeError.
- Пусть A - новый (возможно, пустой) внутренний список всех значений аргументов, предоставленных после этогоArg (arg1, arg2 и т.д.), в порядке.
- Пусть F - новый собственный объект ECMAScript.
- Задайте все внутренние методы, кроме [[Get]], из F, как указано в 8.12.
- Установите внутреннее свойство [[Get]] для F, как указано в 15.3.5.4.
- Установить внутреннее свойство [[TargetFunction]] для F в Target.
- Задайте внутреннее свойство [[BoundThis]] F для значения thisArg.
- Установите внутреннее свойство [[BoundArgs]] для F в A.
- Установите внутреннее свойство [[Class]] для F в "Function".
- Установите внутреннее свойство [[Prototype]] F для стандартного встроенного объекта прототипа функции, как указано в 15.3.3.1.
- Задайте внутреннее свойство [[Call]] для F, как описано в 15.3.4.5.1.
- Задайте внутреннее свойство [[Construct]] для F, как описано в 15.3.4.5.2.
- Задайте внутреннее свойство [[HasInstance]] для F, как описано в 15.3.4.5.3.
- Если внутреннее свойство [[Class]] Target является "Function", то a. Пусть L - свойство длины Target минус длина A. b. Задайте собственное свойство длины F равным 0 или L, в зависимости от того, что больше.
- Else задайте собственное свойство длины F равным 0.
- Задайте атрибуты собственного свойства длины F для значений, указанных в 15.3.5.1.
- Установите внутреннее свойство [[Расширяемое]] для F в значение true.
- Пусть метатель - это функция [[ThrowTypeError]] Object (13.2.3).
- Вызвать внутренний метод F [[DefineOwnProperty]] с аргументами "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false} и false.
- Вызвать внутренний метод F [[DefineOwnProperty]] с аргументами "аргументы", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false} и false.
- Возврат F
И когда вы вызываете function
в свой объект, который был создан с помощью bind
:
15.3.4.5.1 [[Вызов]]
Когда внутренний метод [[Call]] объекта функции, F, который был создан с использованием функции bind, вызывается с помощью это значение и список аргументов ExtraArgs, следующие шаги: Предлагаемое решение:
- Пусть boundArgs является значением внутреннего свойства Fs [[BoundArgs]].
- Пусть boundTh является значением внутреннего свойства Fs [[BoundThis]].
- Пусть target является значением внутреннего свойства Fs [[TargetFunction]].
- Пусть args - новый список, содержащий те же значения, что и список boundArgs в том же порядке, за которым следуют те же значения, что и список ExtraArgs в том же порядке.
- Возвращает результат вызова внутреннего метода [[Call]] цели, предоставляющего boundThis как это значение и предоставляющий args как Аргументы
Вызов указывает, как вызывается каждая функция. И несколько напоминает JavaScript call
:
someFunction.[[call]](thisValue, arguments) {
}
Однако, когда [[call]]
используется для связанной функции, thisValue
переопределяется значением [[BoundThis]]
. В случае вызова bind
во второй раз thisValue
, который вы пытаетесь переопределить первым, заменяется на [[BoundThis]]
, по существу не влияя на значение thisValue
:
boundFunction.[[call]](thisValue, arguments) {
thisValue = boundFunction.[[BoundThis]];
}
Вы заметите, что если вы попытаетесь использовать call
или apply
, то они также не будут иметь никакого эффекта, потому что их попытка переопределить свойство thisValue
будет отменена, когда [[call]]
вызывает следующую функцию.
Ответ 5
Эти упрощенные примеры того, как bind()
работают, объясняют это лучше.
Вот как выглядит функция, связанная однажды:
function bound_function() {
function original_function() {
console.log(self);
}
var self = 1;
original_function();
}
bound_function()
Вот что происходит, если мы дважды обертываем оригинальную функцию:
function bound_function2() {
function bound_function1() {
function original_function() {
console.log(self);
}
var self = 1;
original_function();
}
var self = 2;
bound_function1();
}
bound_function2()
Ответ 6
Я думаю, что думать об этом можно: Когда вы вызываете bind(), первый раз значение 'this' внутри функции, возвращаемой вызовом bind(), является FIXED, к данному значению. Это возможно ПОТОМУ ЧТО оно не было исправлено раньше, оно было несвязано. Но как только он исправлен, он не может быть привязан ни к чему другому, потому что он больше не является незафиксированным, он больше не является "переменной".
В теории может существовать противоположная операция для связывания вызываемого "unbind", которую вы могли бы назвать следующим:
<Предварительно > <код > myFunk.bind(что-то) .unbind();//- > имеет такое же поведение, что и оригинальный myFunk
Код > Имя "bind" указывает на то, что (псевдо-) переменная 'this' имеет значение BOUND для чего-то, это не просто НАЗНАЧЕН значение, которое затем может быть назначено снова и снова.
Когда что-то "связано", оно имеет значение и это значение не может быть заменено - поскольку оно "связано". Поэтому вам понадобится операция unbind(), чтобы сделать это возможным. Но так как вы предположительно используете оригинальную функцию где-то
нет необходимости "отвязывать" на самом деле.
Я согласен, что это поведение, возможно, удивительно и неожиданно и, возможно, подвержено ошибкам, потому что, если вы получаете функцию как аргумент, кажется, нет способа определить, имеет ли ваш bind() на нем какой-либо эффект или нет.
ОЧЕНЬ, если вы не знаете много о таком аргументе функции, также невозможно будет узнать, какое значение вы можете привязать к нему, не нарушая при этом вызовы, которые он делает с 'this' внутри него.
SO сама операция bind() весьма опасна. Повторное связывание будет вдвойне опасным. Поэтому вам лучше всего стараться избегать этого, если это возможно.