Подтвердить что ты не робот

Цепочка вызовов .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"}

4b9b3361

Ответ 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() весьма опасна. Повторное связывание будет вдвойне опасным. Поэтому вам лучше всего стараться избегать этого, если это возможно.