美文网首页
手写bind

手写bind

作者: sweetBoy_9126 | 来源:发表于2020-11-13 17:02 被阅读0次
    知识拓展

    polyfill:抽象理解为把坑填平用到的东西,可以理解为对代码的一种兼容处理(也就是检测一个属性是否存在,然后对其进行处理的行为),比如下面的代码就是polyfill

    if (!Function.prototype.bind) {
      Function.prototype.bind = function () {}
    }
    

    实现bind

    1. 接受this参数

    单元测试代码如下:

    const fn1 = function() {
        return this
    }
    const newFn1 = fn1.bind2({ name: 'lifa' })
    console.assert(newFn1().name === 'lifa')
    

    上面的代码就是我们声明一个fn1函数通过调用bind2传入第一个参数,我们断言它的this就是我们传入的{name:'lifa'}

    思路:我们在bind2里调用fn1然后通过call将我们的第一个参数作为fn1的this,那么怎么能在不作为参数传入的情况下在bind2里调用fn1那?fn1.bind2可以看成是fn1.bind2.call(fn1),所以bind2里的this就是fn1。
    代码实现

    function bind(asThis, p1, p2) {
        const fn = this
        return function() {
            return fn.call(asThis)
        }
    }
    module.exports = bind
    
    2. 接受this. p1, p2参数

    单元测试

    const fn2 = function(p1, p2) {
        return [this, p1, p2]
    }
    const newFn2 = fn2.bind2({ name: 'lifa' }, 123, 456)
    console.assert(newFn2()[0].name === 'lifa')
    console.assert(newFn2()[1] === 123)
    console.assert(newFn2()[2] === 456)
    

    代码实现

    function bind(asThis, ...arguments) {
        const fn = this
        return function() {
            return fn.call(asThis, ...arguments)
        }
    }
    
    3.先传this,再传其他参数

    单元测试代码

    const anotherFn2 = fn2.bind2({ name: 'lifa' })
    console.assert(anotherFn2(222, 333)[0].name === 'lifa')
    console.assert(anotherFn2(222, 333)[1] === 222)
    console.assert(anotherFn2(222, 333)[2] === 333)
    

    代码实现

    function bind(asThis, ...args) {
        const fn = this
        return function(...args2) {
            return fn.call(asThis, ...args, ...args2)
        }
    }
    
    4.问题:上面的代码是es6的如果再ie里可能不兼容,所以我们需要对其兼容

    1). 把const变成var
    2). 如果不是函数调用bind抛出错误
    3).替代扩展运算符

    var slice = Array.prototype.slice
    function bind(asThis) {
        // 得到除第一个参数之外的所有参数
        // 因为arguments不是一个数组,它是一个伪数组,所以不能用slice
        // 可以使用slice.call 从arguments的第1位开始截取
        var args = slice.call(arguments, 1)
        var fn = this
        if (typeof fn !== 'function') {
            throw new Error('bind 必须调用在函数身上')
        }
        return function() {
            var args2 = slice.call(arguments, 0)
            // 这里因为我们拿到的参数是数组没法通过扩展运算拿到每一项
          // 所以将call换成了apply然后把两次参数的数组结合成一个数组
            return fn.apply(asThis, args.concat(args2))
        }
    }
    

    5. 支持new

    单元测试

    function test5(message) {
        console.log(message)
        Function.prototype.bind2 = bind
        const fn = function(p1, p2) {
            this.p1 = p1
            this.p2 = p2
        }
        const fn2 = fn.bind2(undefined, 'x', 'y')
        const object = new fn2()
        console.assert(object.p1 === 'x')
        console.assert(object.p2 === 'y')
    }
    

    如果我们的bind支持new的话那么我们的object.p1一定会等于‘x',object.p2一定会等于'y'
    而且我们需要知道new的过程中都做了什么,它会做以下几步

    var temp = {}
    this = temp
    this.__proto__= fn.prototype
    fn.call(this,'x','y')
    return this
    

    在现在的代码基础上我们的测试报错,打印我们的object发现是一个空对象
    5.1.问题分析

    function _bind(asThis, ...args) {
        const fn = this
        return function(...args2) {
            return fn.call(asThis, ...args, ...args2)
        }
    }
    

    拿我们上面es6版的bind来分析,当我们调用new fn()的时候实际上fn是我们上面的function(...args2) {return fn.call(asThis, ...args, ...args2)},就相当于

    new function(...args2) {
      // 1.var temp = {}
      // 2.this = temp
      // 3.this.p1 = 'x'
      // 4.this.p2 = 'y'
      // 5.return this
      return fn.call(asThis, ...args, ...args2)
    }
    

    上面代码里当我们执行new的时候帮我们执行上面1-4的注释代码,然后默认会执行5帮我们return这个this(默认的意思就是如果我们没写return的情况下new就会帮我们return这个this,如果写了就return我们自己写的里面的this),所以上面代码实际执行完4后就会执行我们的return fn.call(asThis, ...args, ...args2),也就是调用我们的fn,fn里面有this,它的this就是我们的asThis,而我们的asThis传的是undefined,所以我们的p1,p2都放到window里去了
    5.2.解决方法
    我们需要区分new的时候用自己的this,不new的时候用我们传的this,怎么区函数被调用的时候有没有用到new哪?
    因为new的过程中会做this.proto = fn.prototype,所以我们根据这个来判断就可以

    return function resultFn(...args2) {
            return fn.call(this.__proto__ === resultFn.prototype ? this : asThis, ...args, ...args2)
        }
    
    5.3. 支持构造函数.prototype里的属性和方法

    单元测试

    function test6(message) {
        console.log(message)
        Function.prototype.bind2 = bind
        const fn = function(p1, p2) {
            this.p1 = p1
            this.p2 = p2
        }
        fn.prototype.say = function() {
            console.log('say')
        }
        const fn2 = fn.bind2(undefined, 'x', 'y')
        const object = new fn2()
        console.assert(object.p1 === 'x')
        console.assert(object.p2 === 'y')
        console.assert(object.__proto__ === fn.prototype)
        console.assert(typeof object.say === 'function')
    }
    

    基于之前的代码我们的resultFn里面new的时候已经帮我们做了this.proto = resultFn.prototype,而实际上我们是要我们的this.__proto = fn.prototype,所以我们只需要将resultFn.prototype=fn.prototype即可

    function _bind(asThis, ...args) {
        const fn = this
        function resultFn(...args2) {
            // var temp = {}
            // this = temp
            // this.__proto__ = resultFn.prototype
            return fn.call(this.__proto__ === resultFn.prototype ? this : asThis, ...args, ...args2)
        }
        resultFn.prototype = fn.prototype
        return resultFn
    }
    
    5.4.使用instanceof替换proto

    因为proto不是规范属性,所以我们不能直接用,那如果不用proto我们怎么判断一个函数是不是通过new调用的那,使用instanceof,只要是用new调用的那么 实例 instanceof 构造函数

    return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2)
    

    完整代码:

    function _bind(asThis, ...args) {
        const fn = this
        function resultFn(...args2) {
            // var temp = {}
            // this = temp
            // this.__proto__ = resultFn.prototype
            return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2)
        }
        resultFn.prototype = fn.prototype
        return resultFn
    }
    var slice = Array.prototype.slice
    function bind(asThis) {
        var fn = this
        var args = slice.call(arguments, 1)
        if (typeof fn !== 'function') {
            throw new Error('bind的类型必须是函数')
        }
        function resultFn() {
            var args2 = slice.call(arguments, 0)
            return fn.apply(this instanceof resultFn ? this : asThis, args.concat(args2))
        }
        resultFn.prototype = fn.prototype
        return resultFn
    
    }
    

    相关文章

      网友评论

          本文标题:手写bind

          本文链接:https://www.haomeiwen.com/subject/gfcbbktx.html