美文网首页前端架构系列
bind的实现和原理

bind的实现和原理

作者: 羽晞yose | 来源:发表于2020-06-11 18:01 被阅读0次

    照旧,先分析bind的特点,再进行重现。只有清晰的了解一个方法的特点,才能完整重现并理解

    bind的特点:

    1. bind方法可以绑定 this 指向
    2. bind方法会返回一个绑定了this的函数(返回一个函数,所以这是个高阶函数)
    3. bind支持柯里化(也就是调用时可以传入一部分参数,返回的函数可以继续传入其他参数)
    4. this一经绑定无法再被更改(call,apply也无效)
    5. 如果绑定的函数被new了,当前函数的this就是当前的实例
    6. 原函数的原型链上的属性,new出来的结果也可以找到

    由于特点较多,所以需要一步步实现,先实现前四个特点

    Function.prototype.bindModel = function (context) {
        // 保留原函数,返回的函数被调用的时候才执行
        let originFn = this;
    
        // 保存的传入参数(柯里化实现)
        let args = [].slice.call(arguments, 1);
    
        // 返回绑定了this的函数
        return function (parames) {
            originFn.apply(context, args.concat(parames));
        }
    }
    
    let obj = {
        name: '二哈',
        age: '1'
    }
    
    let obj2 = {
        name: '折耳',
        age: '2'
    }
    
    function fn1 (country, city) {
        console.log('我养了一只' + this.name + ', 它今年' + this.age + '岁了,我们一起生活在' + country + '的' + city);
    }
    
    const boundFn1 = fn1.bind(obj, '中国');
    boundFn1('深圳');
    boundFn1.call(obj2, '深圳'); // 我养了一只二哈, 它今年1岁了,我们一起生活在中国的深圳
    
    const boundFn2 = fn1.bindModel(obj, '俄罗斯');
    boundFn2.call('莫斯科'); // 我养了一只二哈, 它今年1岁了,我们一起生活在中国的深圳
    

    上面代码originFn.apply(context, args.concat(parames));也可以看出,因为我们传入的context,其实一开始就被保存了,后面即使改变了返回函数的this指向,但是执行的时候apply的是context而不是this,所以再怎么call或apply也无法改变this指向
    当然这个特点其实严谨的说也是不对的,其实返回函数的this指向是被更改了,只是它运行的时候会重新apply(context),所以更改无效。
    因此正确的说法应该是:虽然返回函数的this被更改了,但是执行的时候this依然会重新指向回最初绑定的对象上


    实现第5个特点,如果返回的函数被new,那么this将不再指向传入的context,而是指向该实例。
    因为我们返回的函数现在是个匿名函数,那么去new这个匿名函数将无法判断,所以需要将其变成具名函数,再使用instanceof来判断this的指向

    Function.prototype.bindModel = function (context) {
        // 保留原函数,返回的函数被调用的时候才执行,如果这里不保存,调用的时候this会变成window
        let originFn = this;
    
        // 去掉第一个参数,这样arguments则剩下应该被保存的参数(柯里化实现)
        let bindArgs = [].slice.call(arguments, 1);
    
        function fBound (parames) {
            // this instanceof fBound 如果是new出来的,那么this将会是fBound,否则将会是window
            // 参考上一节new的实现,在Animal函数里打印this,打印出来是Animal {}
            originFn.apply(this instanceof fBound ? this : context, bindArgs.concat(parames));
        }
    
        return fBound
    }
    
    const boundFn1 = fn1.bind(obj, '中国');
    boundFn1('深圳');
    let instance = new boundFn1('深圳');
    
    const boundFn2 = fn1.bindModel(obj, '俄罗斯');
    boundFn2('莫斯科');
    let instance2 = new boundFn2('莫斯科'); // 如果是之前的实现,这里依然会指向obj,所以this.name和this。age还是能正常取到
    

    最后实现第6个特点,原函数原型链上的属性会被继承,因此将fbound.prototype = this.prototype即可。当然这么写会导致原型链共享,所以还需要使用一个中间件来实现继承

    Function.prototype.bindModel = function (context) {
        // 保留原函数,返回的函数被调用的时候才执行,如果这里不保存,调用的时候this会变成window
        let originFn = this;
    
        // 去掉第一个参数,这样arguments则剩下应该被保存的参数(柯里化实现)
        let bindArgs = [].slice.call(arguments, 1);
    
        // Object.create()原理,创建一个空白函数来做中间件
        function Fn() {};
    
        function fBound (parames) {
            // 改变this指向传入的上下文环境
            // 或者直接使用arguments,这里的arguments已经是返回函数的参数了
            // this instanceof fBound 如果是new出来的,那么this将会是fBound,否则将会是window
            // 参考上一节new的实现,在Animal函数里打印this,打印出来是Animal {}
            originFn.apply(this instanceof fBound ? this : context, bindArgs.concat(parames));
        }
    
        Fn.prototype = this.prototype;
        // 通过原型链找到原函数中的属性
        fBound.prototype = new Fn();
    
        // 返回一个函数,第三步
        return fBound
    }
    
    let obj = {
        name: '二哈',
        age: '1'
    }
    
    let obj2 = {
        name: '二哈哈',
        age: '2'
    }
    
    function fn1 (country, city) {
        console.log('我养了一只' + this.name + ', 它今年' + this.age + '岁了,我们一起生活在' + country + '的' + city);
    }
    
    fn1.prototype = {
         name: '折耳',
         age: '0.5'
    }
    
    const boundFn1 = fn1.bind(obj, '中国');
    boundFn1('深圳');
    let instance = new boundFn1('深圳');
    
    
    const boundFn2 = fn1.bindModel(obj, '俄罗斯');
    boundFn2( '莫斯科');
    // 会通过原型链去寻找(fn1.prototype),所以name和age不再是undefined
    let instance2 = new boundFn2('莫斯科'); // 我养了一只折耳, 它今年0.5岁了,我们一起生活在俄罗斯的莫斯科
    

    更加详细的文章:js 手动实现bind方法,超详细思路分析!
    这篇文章分得比我细很多,但我不是一个喜欢啰里啰嗦的人,点到即可,看不懂我的可以看看这位作者的

    相关文章

      网友评论

        本文标题:bind的实现和原理

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