【问题标题】:I can't understand how this javascript function works我不明白这个 javascript 函数是如何工作的
【发布时间】:2012-02-07 05:00:33
【问题描述】:

我正在阅读function definition of 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 部分(我无法弄清楚这在这种情况下代表什么)。

有人能解释一下这里发生了什么吗?

【问题讨论】:

标签: javascript logic


【解决方案1】:

嗯,需要设置fBound 的原型的一个原因是,在函数上调用bind 的结果与该函数具有相同的原型。这也是fNop 的用武之地——它允许您使用new fNop() 设置fBound 的原型,而无需调用可能有副作用的原始函数。

apply 的调用允许您在函数中设置this 并指定其他参数。由于bind 允许您将参数“curry”到函数中,因此您必须将绑定函数时传入的参数和调用它的参数结合起来。

【讨论】:

  • 不使用fNop,为什么不能直接说fBound.prototype = this.prototype?我相信这与为什么要进行 instanceof fNop 检查有关,这是我无法理解的部分
  • 重点是,bind 创建了一个函数。所以它的定义只是为了与声明一个函数保持一致,其中一个新对象被分配给该函数的prototype 属性。我想我们可以使用fBound.prototype = Object.create(this.prototype),但这没有多大意义,因为Object.create 可能不存在于不知道bind 的浏览器中。
  • "对函数调用 bind 的结果与该函数具有相同的原型。"实际上,它应该根本没有原型,如defined in ES5
【解决方案2】:

这是为了确保

  • (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 对象,因此使用 polyfill (尤其是数字 2/3 和 4/10)是不可能的,但您可以尝试尽可能多地实现.

有问题的实现试图通过挂钩到原型链来解决第 6 和第 7 问题,但 that's not enough

这里有一个更好的替代实现,但仍然不完美: http://jsfiddle.net/YR6MJ/

【讨论】:

    【解决方案3】:

    来自之前的评论:

    你为什么不能直接说fBound.prototype = this.prototype而不是fNop?

    据我所知,主要区别在于,当绑定函数内的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
    

    换句话说,fn 内的this 的值是调用它的Test 实例。您的建议会将绑定数组提供给bind 内的apply,因此,这样写,相同代码的最后一行将打印:

    3 testing
    

    我并不完全清楚为什么这很重要,但它确实强调了您的建议不会产生相同的结果。

    【讨论】:

    • 整个行为似乎符合标准。一旦绑定,this 就不能被覆盖(无论调用者是谁,instanceof 的来源与否)。所以不应该有不使用oThis 的情况(使条件充其量是不必要的,最坏的情况是不标准的)。
    【解决方案4】:
    // 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;
      };
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多