模拟实现js的bind方法

var obj = {};
console.log(obj);
console.log(typeof Function.prototype.bind); // function
console.log(typeof Function.prototype.bind());  // function
console.log(Function.prototype.bind.name);  // bind
console.log(Function.prototype.bind().name);  // bound

bind是什么

  • a. bindFunction原型链中Function.prototype的一个函数,每个函数都可以调用它。

  • b. bind本身是一个函数名为bind的函数,返回值也是函数,函数名是bound。(console出来为

    bound 加上一个空格)。

let obj = {
    name: "yato"
}

function original(a,b){
    console.log(this.name)
    console.log([a,b])
    return false
}

let bound = original.bind(obj, 1)
let boundInvoke = bound(2)                    // 'yato', Array(2)[1,2]

console.log(boundInvoke)                       // false
console.log(original.bind.name)                // bind
console.log(original.bind.length)              // 1
console.log(original.bind().length)            // 2 返回original函数形参个数
console.log(bound.name)                        // 'bound original'
console.log((function(){}).bind().name)        // 'bound '
console.log((function(){}).bind().length)      // 0

进一步理解bind

  • a. 调用bind的函数中的this指向bind()函数的第一个参数。

  • b. 函数bind()时传递的参数被bind接受处理,bind()完毕之后,程序调用返回的函数(bound)时,传递的参数也接收处理了,也就是在bind()内部合并处理了。

  • c. 并且bind()后的函数的name为bound+空格+调用bind的函数名。如果调用函数为匿名函数,则名字为bound+空格

  • d. bind后的返回值函数,执行后返回值时原函数(original)的返回值(上例中的false)

  • e. bind函数的形参(即函数的length)是1bind后返回的bound函数形参不定,根据绑定的函数原函数(original)形参个数决定。

根据上面的两个例子,模拟实现一个简单版的bindFn

Function.prototype.bindFn = function bindFake(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + 'must be a function')
    }

    // 存储函数本身
    let self  = this
    
    // 去除thisArg的其他参数,转成数组
    let args = [].slice.call(arguments, 1)
    let bound = function(){
        // bind 返回的函数,也就是bound,在程序中被调用时传递的参数转成数组
        let boundArg = [].slice.call(arguments);

        // apply修改this指向,把两个函数的参数合并传给self函数,返回执行结果
        return self.apply(thisArg, args.concat(boundArg))
    }

    return bound
}

// Test
let obj = {
    name: 'yato'
}

function original(a, b){
    console.log(this.name)
    console.log([a,b])
}

let bound = original.bindFn(obj, 1)
bound(2);  // 'yato', [1,2]

但是函数是可以使用new来实例化的。

 let obj = {name : 'yato'}

 function original(a, b){
     console.log('this : ', this)
     console.log('typeof this : ', typeof this)
     this.name = b
     console.log('name: ', this.name)
     console.log('this: ', this)
     console.log([a,b])
 }

 let bound = original.bind(obj, 1)
 let newBoundInvoke = new bound(2)
 console.log('newBoundInvoke: ', newBoundInvoke)
// this :  original {}
// typeof this :  object
// name:  2
// this:  original { name: 2 }
// [ 1, 2 ]
// newBoundInvoke:  original { name: 2 }

分析例子可以得出结论

  • a. 从例子中可以看出this指向了new bound()生成的对象

  • b. new bound() 的返回值是以original原函数构造器生成的新对象。original原函数的this指向的就是这个新对象。

  • c.简要剖析下new做了什么

    1. 创建一个全新的空对象
    2. 对这个对象指向原型链接(instance.__proto__ = Class.prototype ),其实Class.prototype就是constructor
    3. 生成的新对象会绑定到函数调用的this
    4. 通过new创建的每个对象最终被[[prototype]]链接这个函数的prototype上(参考2)
    5. 如果函数没有返回对象类型Object(包含Function, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象
所以相当于在new调用时,bind的返回值函数bound内部要实现new的操作
// 第二版 实现new调用
Function.prototype.bindFn = function bindFake(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' must be a function')
    }

    // 存储调用bind的函数本身的引用
    let self = this

    // 去除thisArg参数,其他转成数组
    let args = [].slice.call(arguments, 1)
    let bound = function(){
        let boundArgs = [].slice.call(arguments)
        let finalArgs = args.concat(boundArgs)

        // new 调用时,其实this instanceof bound 判断不是很准确。es6
        // new.target就是解决这一问题的
        if(this instanceof bound){
            // 这里是实现上文描述的 new 的第 1, 2, 4 步
            // 1.创建一个全新的对象
            // 2.并且执行[[Prototype]]链接
            // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
            // self可能是ES6的箭头函数,没有prototype,所以就没必要再指向做prototype操作。
            if(self.prototype){
                function Empty(){}
                Empty.prototype = self.prototype
                bound.prototype = new Empty()
            }

            // 这里实现的时上文描述的第三步
            // 3.生成的新对象会绑定到函数调用的this
            let result = self.apply(this, finalArgs);

            // 这里是实现上文描述的 new 的第 5 步
            // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`,               //   `Error`),
            // 那么`new`表达式中的函数调用会自动返回这个新的对象。
            let isObject = typeof result === 'object' && result !== null
            let isFunction = typeof result === 'function'

            if(isObject || isFunction)
                return result

            return this
        }else{
            // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
            return self.apply(thisArg, finalArgs)
        }
    }
    return bound
}

// Test
let obj = {name : 'yato'}

function original(a, b){
    console.log('this : ', this)
    console.log('typeof this : ', typeof this)
    this.name = b
    console.log('name: ', this.name)
    console.log('this: ', this)
    console.log([a,b])
}

let bound = original.bindFn(obj, 1)
let newBoundInvoke = new bound(2)
console.log('newBoundInvoke: ', newBoundInvoke)
// this :  bound {}
// typeof this :  object
// name:  2
// this:  bound { name: 2 }
// [ 1, 2 ]
// newBoundInvoke:  bound { name: 2 }

总结

    1. bindFunction原型链中Function.prototype的一个属性,它是一个函数,修改this指向,合并参数传递给原函数,返回值是一个新的函数
    1. bind返回的函数可以通过new调用,这是提供的this参数被忽略,指向了new生成的全新对象。bind()内部模拟实现了new操作符
posted @ 2020-08-11 14:59  yatolk  阅读(184)  评论(0编辑  收藏  举报