实现一个bind函数

目前的打算仍是继续深刻前端基础知识,因此打算从polyfill开始作起。前端

bind函数

bind函数最多见的用法是绑定函数的上下文,好比在setTimeout中的this通常都是指向window,若是咱们想改变上下文,这里可使用bind函数来实现。segmentfault

var a = 10;
var test = function() {
    console.log(this.a);
}
// 若是直接执行test,最终打印的是10.
var bindTest = test.bind({a: "111"})
bindTest(); // 111
复制代码

从上面这个例子能够看出来,bind函数改变了test函数中this的指向。 除此以外,bind函数还有两个特殊的用法,一个是柯里化,一个是绑定构造函数无效。bash

柯里化

bind函数的柯里化实际上是不彻底的,其实只作了一次柯里化,看过MDN的polyfill实现后也就理解了。app

var test = function(b) {
    return this.a + b;
}
// 若是直接执行test,最终打印的是10.
var bindTest1 = test.bind({a: 20});
bindTest1(10); // 30
// 这里的bind是个柯里化的函数
var bindTest2 = test.bind({a: 20}, 10);
bindTest2(); // 30;
复制代码

构造函数无效

其实准确的来讲,bind并非对构造函数无效,只是对new的时候无效,若是直接执行构造函数,那么仍是有效的。函数

var a = 10;
var Test = function(a) {
    console.log(this.a);
}
var bindTest = Test.bind({a: 20});
bindTest(); // 20
// 在new的时候,Test中的this并无指向bind中的对象
new bindTest(); // undefined
复制代码

实现一个bind

咱们能够先实现一个简易版本的bind,再不断完善。因为是在函数上调用bind,因此bind方法确定存在于Function.prototype上面,其次bind函数要有改变上下文的做用,咱们想想,怎么才能改变上下文?没错,就是call和apply方法。ui

而后还要能够柯里化,还好这里只是简单的柯里化,咱们只要在bind中返回一个新的函数,而且将先后两次的参数收集起来就能够作到了。this

Function.prototype.bind = function() {
    var args = arguments;
    // 获取到新的上下文
    var context = args[0];
    // 保存当前的函数
    var func = this;
    // 获取其余的参数
    var thisArgs = Array.prototype.slice.call(args, 1);
    var returnFunc = function() {
        // 将两次获取到的参数合并
        Array.prototype.push.apply(thisArgs, arguments)
        // 使用apply改变上下文
        return func.apply(context, thisArgs);
    }
    return returnFunc;
}
复制代码

这里实现了一个简单的bind函数,能够支持简单的柯里化,也能够改变上下文做用域,可是在new一个构造函数的时候仍是会改变上下文。spa

这里咱们须要考虑一下,怎么作才能让在new的时候无效,而其余时候有效?.net

因此咱们须要在returnFunc里面的apply第一个参数进行判断,若是是用new调用构造函数的时候应该传入函数自己,不然才应该传入context,那么该怎么判断是new调用呢?prototype

关于在new一个构造函数的时候,这中间作了什么,建议参考这个问题:在js里面当new了一个对象时,这中间发生了什么?

因此咱们很容易得出,因为最终返回的是returnFunc,因此最终是new的这个函数,而在new的过程当中,会执行一遍这个函数,因此这个过程当中returnFunc里面的this指向new的时候建立的那个对象,而那个新对象指向returnFunc函数。

可是咱们但愿调用后的结果只是new的func函数,和咱们正常new func同样,因此这里猜测,在returnFunc中,必定会将其this传入func函数中执行,这样才能知足这几个条件。

Function.prototype.bind = function() {
    var args = arguments || [];
    var context = args[0];
    var func = this;
    var thisArgs = Array.prototype.slice.call(args, 1);
  	var returnFunc = function() {
      Array.prototype.push.apply(thisArgs, arguments);
      // 最关键的一步,this是new returnFunc中建立的那个新对象,此时将其传给func函数,其实至关于作了new操做最后一步(执行构造函数)
      return func.apply(this instanceof returnFunc ? this : context, thisArgs);
    }
    return returnFunc
}
function foo(c) {
    this.b = 100;
    console.log(c);
    return this.a;
}

var func =  foo.bind({a:1});
var newFunc = new func() // undefined
复制代码

可是这样仍是不够的,若是foo函数原型上面还有更多的方法和属性,这里的newFunc是无法获取到的,由于foo.prototype不在newFunc的原型链上面。 因此这里咱们须要作一些改动,因为传入apply的是returnFunc的一个实例(this),因此咱们应该让returnFunc继承func函数,最终版是这样的。

Function.prototype.bind = function() {
    var args = arguments || [];
    var context = args[0];
    var func = this;
    var thisArgs = Array.prototype.slice.call(args, 1);
    var returnFunc = function() {
      Array.prototype.push.apply(thisArgs, arguments);
      // 最关键的一步,this是new returnFunc中建立的那个新对象,此时将其传给func函数,其实至关于作了new操做最后一步(执行构造函数)
      return func.apply(this instanceof func ? this : context, thisArgs);
    }
    returnFunc.prototype = new func()
    return returnFunc
}
复制代码

这样咱们就完成了一个bind函数,这与MDN上面的polyfill实现方式大同小异,这里能够参考一下MDN的实现:Function.prototype.bind()

参考连接:

  1. MDN:Function.prototype.bind()

  2. 手写bind()函数,理解MDN上的标准Polyfill

相关文章
相关标签/搜索