1

在阅读本文之前,您应该先首先对call或者applynew操作符的原理、柯里化以及原型链机制有一定了解。

1.bind方法的原理

fn(p1,p2,p3)调用bind方法时,fn.bind(obj),会返回一个this指向obj的函数bindFn,bindFnfn函数体相同,而且bind可以只接收原函数的一部分参数,让它返回的函数再去处理其他的参数,即函数柯里化。

2.bind的第一次简单实现

因为bind返回值是一个函数,所以我们就需要考虑函数调用的方式了,如果该函数是作为构造函数,通过new操作符调用的话和直接调用结果应该是相同的吗?在实现bind的过程中需不需要判断函数的调用方式呢?我对此感到疑惑,然后我就首先实现了一个不判断调用方式的简单版本:

    //不考虑构造函数
        Function.prototype.myBind = function (thisValue, ...firstArgs) {
            //thisValue是返回值函数要绑定的this firstArgs是第一次传入的参数数组
            const currentFn = this;
            //在执行myBind之后 得到的是一个函数体和调用myBind的函数体相同的函数 只是返回值函数中 绑定了this 
            return function (...secondArgs) {
                //secondArgs是第二次传入的参数数组
                return currentFn.apply(thisValue, [...firstArgs, ...secondArgs])
            }
        }
        function add(a, b, c) {
            console.log(this);
            return a + b + c
        }
        console.log(add(1, 2, 3));
        console.log(add.bind(null, 1)(2, 3));
        console.log(add.bind(obj, 1)(2, 3));
        console.log(add.myBind(null, 1)(2, 3));
        console.log(add.myBind(obj, 1)(2, 3));

但是如果是构造函数的话,结果就会有所不同

    function Person(n, a, s) {
            console.log(this);
            this.name = n
            this.age = a
            this.sex = s
        }
        Person('小明', 18, '男')//this 指向window
        const p = new Person('小花', 19, '男')//this 指向p
        const bindPerson = Person.bind(obj)
        const myBindPerson = Person.myBind(obj)
        const p2 = new bindPerson('xiaogou', 17, 'male')//指向p2
        const p3 = new myBindPerson('xiaogou', 17, 'male')//指向obj 

这是因为虽然new myBindPerson()时,new操作符将myBindPerson内部的this指向了当前实例p3,但是myBindPerson在执行到return语句currentFn.apply(thisValue,[])一句时,又将this强制绑定成为了obj,所以最终输出的this是obj,显然,在此时我们应当判断是否是由new操作符调用的函数。
根据new操作符的原理,我们可以采用instanceof的方法来判断。

3.bind实现

    Function.prototype.myBind = function (thisValue, ...firstArgs) {
            //thisValue是返回值函数要绑定的this firstArgs是第一次传入的参数数组
            const currentFn = this;
            //创建一个空的中转函数 用于检测是否使用了new
            const tempFn = function () { }
            //如果currentFn是一个构造函数 他的prototye存在 将中转函数的prototype指向当前函数的prototype 维护原型链关系
            if (currentFn.prototype) {
                tempFn.prototype = currentFn.prototype
            }
            //此时 将tempFn创建的实例 作为返回值函数BindFn的prototype 
            //以后通过new bindFn() 创建的实例的__proto__就指向了 new Temp()
            //new TempFn() 就被添加到了该原型链之上 就可以通过instanceof 判断对bindFn的调用有没有使用new
            bindFn.prototype = new tempFn();//将来能够使用instanceof判断的关键语句
            function bindFn(...secondArgs) {
                //如果调用时使用了new 即 const p=new bindFn()形式 此时this指向p 又因为tempFn的实例是p的原型对象
                //所以 p instanceof tempFn 应当为true 此时我们不应改变this指向
                //如果是直接调用bindFn() this应该指向window 这时候我们需要绑定this
                return currentFn.apply(this instanceof tempFn ? this : thisValue, [...firstArgs, ...secondArgs])

            }
            return bindFn
        }
        Person('小明', 18, '男')//this 指向window
        const p = new Person('小花', 19, '男')//this 指向p
        const bindPerson = Person.bind(obj)
        const myBindPerson = Person.myBind(obj)
        const p2 = new bindPerson('xiaogou', 17, 'male')//指向p2
        const p3 = new myBindPerson('xiaogou', 17, 'male')//指向p3 bindFn的实例

4.bind的mdn实现

我参考了mdn,mdn实现是:

if(!Function.prototype.bind){
  Function.prototype.bind = function(oThis){
    if(typeof this !== 'function'){
      throw new TypeError('被绑定的对象需要是函数')
    }
    var self = this
    var args = [].slice.call(arguments, 1)
    var func = function(){}
    fBound = function(){
      return self.apply(this instanceof func ? this : oThis, args.concat([].slice.call(arguments)))
    }
    if(this.prototype){
      func.prototype = this.prototype
    }
    fBound.prototype = new func()
    return fBound
  }
}

我利用声明提升机制,将

    var func = function(){}
    if(this.prototype){
      func.prototype = this.prototype
    }
    fBound.prototype = new func()

放在了一起,为的是便于理解中转函数func的作用。

仅为个人理解,如有错谬,欢迎指正。
如果您觉得对您有帮助,请您点个赞吧!

forceddd
271 声望912 粉丝

一名前端爱好者。