Я не могу понять, как работает эта функция JavaScript - PullRequest
26 голосов
/ 10 января 2012

Я читал определение функции для bind , но я не могу на 100% понять код как написано:

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP
                                       ? this 
                                       : oThis || window,
                                     aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

В частности, я не понимаю целиfNOP, и я не понимаю, почему прототип fBound должен быть установлен.Я также зациклен на части fToBind.apply (я не могу понять, что это представляет в этом контексте).

Может кто-нибудь объяснить, что здесь происходит?

Ответы [ 4 ]

6 голосов
/ 11 января 2012

Хорошо, одной из причин, по которой необходимо установить прототип fBound, является то, что результат вызова bind для функции имеет тот же прототип, что и эта функция. Здесь также появляется fNop - это позволяет установить прототип fBound с использованием new fNop() без вызова исходной функции, которая может иметь побочные эффекты.

Вызов apply позволяет вам одновременно установить this в функции и указать дополнительные аргументы. Поскольку bind позволяет вам «каррировать» аргументы функции, вы должны объединить как аргументы, переданные при связывании функции, так и аргументы, с которыми она вызывается.

1 голос
/ 11 января 2012

Необходимо убедиться, что

  • (1) связанная функция может использоваться в качестве конструктора, игнорируя привязку.(следовательно, проверка instanceof)
  • (2) В то же время вы хотите убедиться, что new g() наследуется от цепочки прототипов f.(отсюда .prototype = new fNop часть)

Пример:

function f() {
    this.foo = 'bar';
}
f.prototype = {
    baz: 'yay!'
};

var g = f.bind({});
var o = new g();
console.log(o.foo); // 'bar' - (1)
console.log(o.baz); // 'yay!' - (2)

В тот момент, когда вы вызываете new g(), функция fBound вызывается как производитель с брендомновый объектный объект (this), который является экземпляром fNop.


Редактировать:

Стандарт ECMAScript5 определяет сложный алгоритм для функций привязки.Среди прочего, следующие утверждения должны выполняться:

var DateJan2042 = Date.bind(null, 2042, 0);

 /*1*/ console.assert(Function.prototype.bind.length == 1, 'bind should have a length of 1');
 /*2*/ console.assert(typeof DateJan2042 == 'function', 'bind() should return a function');
 /*3*/ console.assert(!DateJan2042.hasOwnProperty('prototype'), 'Bound function must not have a prototype');
 /*4*/ console.assert(DateJan2042.length == Math.max(Date.length - 2, 0), 'Bound function should have a proper length');
 /*5*/ console.assert(typeof DateJan2042() == 'string', 'Function call should return a string');
 /*6*/ console.assert({}.toString.call(new DateJan2042()).indexOf('Date') != -1, 'Constructor call should return a new Date object');
 /*7*/ console.assert(new DateJan2042() instanceof DateJan2042, 'Instanceof check should pass for constructor\'s return value');
 /*8*/ console.assert((new DateJan2042()).getMonth() == 0, 'Constructor should be called with bound arguments');
 /*9*/ console.assert((new DateJan2042(1)).getDate() == 1, 'Constructor should take additional arguments');
/*10*/ console.assert(!/^function *\( *[^ )]/.test(Function.prototype.toString.call(DateJan2042)), 'Bound function should have no formal arguments');

Поскольку правильно связанная функция не является реальным Function объектом, невозможно получить все правильно, используя полифилл (в частности, числа 2/3,и 4/10), но вы можете попытаться реализовать как можно больше.

Реализация, о которой идет речь, пытается решить номер 6 и номер 7, подключившись к цепочке прототипов, но этого недостаточно.

Вот альтернативная реализация, которая работает немного лучше, но все еще не совершенна: http://jsfiddle.net/YR6MJ/

0 голосов
/ 16 июня 2018
// check to see if the native implementation of bind already
// exists in this version of JavaScript. We only define the
// polyfill if it doesn't yet exist.

if (!Function.prototype.bind) {

  // creating the bind function for all Function instances by 
  // assigning it to the `Function.prototype` object. Normally 
  // you would avoid assigning to builtin prototypes because you
  // may cause a conflict with new features, but here this is a
  // known feature that is already in the spec that we're adding
  // to a JavaScript runtime that is not up to spec, so its ok

  Function.prototype.bind = function (oThis) {

    // if you attempt to call this function from a non-function object
    // for example if you assign this bind function to a normal object
    // or use `call`/`apply` to change the context of this function call to
    // a non function value (e.g. `Function.prototype.bind.call({})`), we
    // throw an error because bind can only work on functions, and we
    // require that `this` in this call is a function

    if (typeof this !== "function") {
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    // bind does two things, it binds a context (`this` value) to a
    // function for when its called, and it provides a way to bake in
    // some pre-defined arguments that are automatically passed into 
    // that function when called. Those arguments can be passed into
    // the bind call and get picked up here as `aArgs` pulling them
    // from `arguments` making sure to lop off the `oThis` value

    var aArgs = Array.prototype.slice.call(arguments, 1),

      fToBind = this, // this is the function we're binding
      fNOP = function () {}, // a constructor used for `new` usage (see below)

      // `fBound` is the bound function - the result that bind is going to
      // return to represent the current function (`this` or `fToBind`) with
      // a new context.  The idea behind this function is that it will simply
      // take the original function and call it through `apply` with the
      // new context specified.

      fBound = function () {

        // call the original function with a new context using `apply`.
        // however if the function is called with `new`, it needs to be called
        // with the context of, and return, a new object instance and not the
        // bound version of this.  In that case, binding gets ignored in favor
        // of using the `this` of the new instance rather than the `oThis` binding.

        // new object instances inherit from the prototype of their constructors.
        // Our `fBound` function is supposed to mimic the original with the
        // exception of a change in context.  So if new objects are created with
        // it, they should behave as though they were created from the original.
        // But at the same time, we can't simply carry over the prototype of the
        // original into `fBound` because it is a separate function and needs its
        // own prototype, just one that also inherits from the original. To
        // accommodate this, the `fNOP` function (constructor) above is used as
        // an intermediary for creating `fBound`'s prototype while allowing it to
        // be unique but also inherit the original.  And because that becomes part
        // of the bound function's prototype chain, it can be used to determine
        // whether `this` in `fBound` is an instance created by `new` or not since
        // `instanceof` works through a prototype chain lookup.

        return fToBind.apply(this instanceof fNOP
               ? this
               : oThis,

               // call the function with arguments that include the added 
               // arguments specified from the original bind call plus
               // the arguments this function was called with

               aArgs.concat(Array.prototype.slice.call(arguments)));
      };

    // `fNOP`'s use to provide an intermediary prototype between `fBound` and
    // the current function instance mimics `Object.create`. But we're assuming
    // if you don't have `bind`, you probably don't have `create` either, so do
    // it the old fashioned way with a constructor.  This works by setting the
    // constructor's prototype to the to-inherit-from constructor's (this)
    // prototype. A check is needed to prevent assinging that prototype to null
    // if it doesn't exist on this function (Function.prototype is technically
    // a valid target for `bind()` because it is a function but one that does not
    // have its own prototype).

    if (this.prototype) {
      fNOP.prototype = this.prototype;
    }

    // here the inheritance is made.  As a new function, `fBound` has no existing
    // inheritance chain to worry about, so we can easily replace it with a new
    // one - that of a new instance `fNOP`.  Since `fNOP`'s prototype is the original
    // function's prototype, `fBound` has a prototype which directly inherits from
    // that, one level between new instances and the original prototype. So
    // `fBound.prototype.__proto__ === this.prototype` and new instances of `fBound`
    // created with `new fBound()` will inherit from `fBound.prototype` as well as
    // the original function's prototype.

    fBound.prototype = new fNOP();

    // return the bound version of the function as
    // the result of the bind call

    return fBound;
  };
}
0 голосов
/ 11 января 2012

Из предыдущего комментария:

вместо использования fNop, почему вы не можете просто сказать fBound.prototype = this.prototype?

Насколько я могу судить, основное отличие состоит в том, что когда значение this внутри связанной функции является экземпляром исходной функции, для которой был вызван bind, тогда значение для привязки первый аргумент, первоначально переданный bind - игнорируется.

Например, этот код:

function Test(blah) {
    console.log(this.length, blah);
}

Test.prototype.length = 77;
Test.prototype.fn = Test.bind(['a', 'b', 'c'], "testing");
new Test().fn()

... заставляет fn печатать:

77 testing

Другими словами, значение this внутри fn является экземпляром Test, для которого он был вызван. Ваше предложение предоставит связанный массив для apply внутри bind, так что, написанная таким образом, последняя строка того же кода напечатает:

3 testing

Мне не совсем понятно почему это важно, но оно подчеркивает, что ваше предложение не даст эквивалентных результатов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...