• bind()的模拟实现


    上一篇对call和apply的模拟实现做了一个梳理,可参见:模拟实现call、apply,下面将具体研究一下bind啦啦啦

    1. bind和call/apply的差别

    bind方法会创建一个新函数,返回值是一个绑定了上下文的函数

    call和apply是将函数直接执行

    描述:

    bind()函数会创建一个绑定函数(bound function,BF),它包装了原函数对象,调用该绑定函数即执行原函数

    返回值:是一个原函数拷贝,并拥有指定的this值和初始参数

    当一个绑定函数是用来作为构造函数即使用new操作符去构造一个新实例时,原来bind绑定的this将被忽略,this将指向当前新创建的实例。但是提供的参数列表仍然会插入到构造函数调用时的参数列表之前

     

    2. bind的模拟实现

    bind的几个特性:

    1. 指定this
    2. 返回值为一个函数
    3. 可以传参数
    4. 函数柯里化

     第一步:基本实现以上四个特性

        Function.prototype.bind2 = function (context) {
            var self = this;  // 这句实际上是把调用bind2的函数赋给self,console.log(self)得到的是一个函数
            var args = Array.prototype.slice.call(arguments, 1);  // 获取传入的参数,将其变为数组存入args中
    
            return function () {
                var funArgs = Array.prototype.slice.call(arguments);  // 这里的arguments是这个return函数中传入的参数
                return self.apply(context, args.concat(funArgs))  // 将this指向context,将self的参数和return的function的参数拼接起来
            }
        }

    说明:

    1. var self = this;不是常理中我们想象的将this的值保存在self,这里是将整个函数赋给self,其实也等同于将this指向调用者
    2. 因为arguments是类数组对象,所以这里还是使用Array.prototype.slice.call的方式将arguments存储在args数组中
    3. funArgs得到的是返回的function中带的参数,实际上就是实现柯里化的一种参数获取方式!

    第二步:实现bind特性

    当绑定函数使用new操作符创建对象时,会使原this的指向失效,this会指向新的对象实例!

        var value = 2;
        var foo = {
            value: 1
        };
        function bar(name, age) {
            this.habit = 'shopping';
            console.log(this.value);
            console.log(name);
            console.log(age);
        }
    bar.prototype.friend = 'kevin';

    var bindFoo = bar.bind(foo, 'Jack'); var obj = new bindFoo(20); // undefined // Jack // 20

    首先将foo这个对象绑到bar这个函数上,传入参数“Jack”,然后将绑定函数bindFoo()作为构造函数生成新的对象obj,此时bindFoo的this是指向obj的,这里可以看到this.value并没有输出1,而是输出了undefined。因为obj没有value值!

    因此这里将通过修改返回函数的原型实现:

        Function.prototype.bind2 = function (context) {
    
            var self = this;
            var args = Array.prototype.slice.call(arguments, 1);
    
            var fNOP = function () { };  // 1 创建一个空对象
    
            var fBound = function () {
                var funArgs = Array.prototype.slice.call(arguments);
                return self.apply(
                    this instanceof fNOP ? this : context,  // this instanceof fNOP为true时,说明返回的fBound被当做构造函数调用;为false时,this指向context
                    args.concat(funArgs)
                );
            }
    
            fNOP.prototype = this.prototype;  // 2 空对象的原型 指向 绑定函数的原型
            fBound.prototype = new fNOP();  // 3 然后将空对象实例赋给fBound.prototype
    
            return fBound;
        }

    以上已基本实现bind的功能

    • 说明:注释中的123三步是利用空对象作为中介的方式来维护原型关系,实际上完成的功能是:修改返回函数的prototype为绑定函数的prototype —— 完成这三步后,new出来的实例就可以继承绑定函数的原型中的值!拿上题为例,即obj可以获取到bar.prototype的friend值。

    第三步:

    当调用bind调用的不是函数时需要抛出异常!因此加上如下代码就得到bind模拟的最终的完整版:

        Function.prototype.bind2 = function (context) {
            // 如果bind绑定的不是函数,则抛错
            if (typeof this !== "function") {
                throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
            }
    
            var self = this;
            var args = Array.prototype.slice.call(arguments, 1);
    
            var fNOP = function () { }; 
    
            var fBound = function () {
                var funArgs = Array.prototype.slice.call(arguments);
                return self.apply(
                    this instanceof fNOP ? this : context,
                    args.concat(funArgs)
                );
            }
    
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        }
  • 相关阅读:
    win8 64下启动Apache失败:443端口被占用的解决方法
    JavaScript初学者应注意的七个细节
    再说SQL Server数据库优化
    2010.Net程序员年终随笔
    基于Siverlight 3.0的超炫图表工具Visifire 最后一个免费版本,你还等什么?
    苦修六年 终成正果 幸福之路 从此开始
    Asp.net中服务端控件事件是如何触发的(笔记)
    我的缓存实例—工作记录
    坚持观点:决不为了用Linq而用Linq!!
    ASP.NET 之 常用类、方法的超级总结,并包含动态的EXCEL导入导出功能,奉上类库源码
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11793127.html
Copyright © 2020-2023  润新知