zoukankan      html  css  js  c++  java
  • javascript 原生bind方法实现

    bind方法可以用来给一个方法绑定上下文环境对象,以及重新给方法传参数。
    bind的另一个简单使用是使一个函数拥有预设的初始参数。我们称为偏函数

    function list() {
      return Array.prototype.slice.call(arguments);
    }
    
    var list1 = list(1, 2, 3); // [1, 2, 3]
    
    // Create a function with a preset leading argument
    var leadingThirtysevenList = list.bind(undefined, 37);
    
    var list2 = leadingThirtysevenList(); // [37]
    var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
    

    由于bind方法在并不是在所有的浏览器上都支持,因此我们考虑自己实现bind方法。
    首先我们可以给目标函数指定作用域来简单实现bind

    Function.prototype.bind = function(context){
        self = this;
        return function(){
            return self.apply(context, arguments);
        }
    }
    

    这样实现的bind方法就只能接受一个上下文环境变量的参数,不能同时接受参数。因此我们修改一下。

    Function.prototype.bind = function(context){
        var slice = Array.prototype.slice,
            _args = slice.call(arguments,1),
            self = this;
        return function(){
            var _inargs = slice.call(arguments);
            return self.apply(context, _args.concat(_inargs));
        }
    }
    

    现在bind可以绑定对象,同时也能在绑定对象时传递参数。
    但是bind还有一个特点:

    一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
    并看不懂什么意思 = = 其实就是bind返回的函数还能用做构造函数。bind 时指定的 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, 'daisy');
    
    var obj = new bindFoo('18');
    // undefined
    // daisy
    // 18
    console.log(obj.habit);
    console.log(obj.friend);
    // shopping
    // kevin
    

    我们可以通过修改函数的返回原型来实现,代码如下:

    Function.prototype.bind = function(context){
        var slice = Array.prototype.slice,
            _args = slice.call(arguments,1),
            self = this,
            fBound = function(){
                var _inargs = slice.call(arguments);
                // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
                // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
                // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
                return self.apply((this instanceof fBound ? this : context), _args.concat(_inargs));
            };
            // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
            fBound.prototype = self.prototype;
            return fBound;
    }
    

    bound.prototype = this.prototype这么写的话,修改返回函数原型对象(bound.prototype)的同时把绑定函数的原型对象(this.prototype也同时修改了。因此用匿名函数做中转,this.protptype 就安全了。
    还有几个小问题的解决:

    • 调用bind不是函数
    • bind兼容性的问题
      最终的完整代码如下:
    fakeBind = function (context) {
        if (typeof this !== "function") {
            throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }
        var slice = Array.prototype.slice,
            _args = slice.call(arguments, 1),
            self = this,
            F = function () {},
            bound = function () {
                var _inargs = slice.call(arguments);
                return self.apply((this instanceof F ? this : context), _args.concat(_inargs));
            };
        F.prototype = self.prototype;
        bound.prototype = new F();
        return bound;
    }
    
    Function.prototype.bind = Function.prototype.bind || fakeBind;
    
    ES6版实现
    Function.prototype.fakeBindES6 = function(context, ...rest) {
      if (typeof this !== "function") {
        throw new Error("Bind must be called on a function");
      }
      var self = this;
      return function inner(...args) {
        if (this instanceof inner) {
          // 当返回的内层函数作为构造函数使用,bind 时绑定的 this 失效。
          // 即此处直接执行绑定函数,而不使用 apply 对 this 进行绑定 
          return new self(...rest, ...args);
        }
        // 当作为普通函数调用,this 指向传入的对象
        return self.apply(context, rest.concat(args));
      };
    };

    Github: https://github.com/Vxee/articles/issues/7
  • 相关阅读:
    Objective--C三大特性:封装,继承,多态(零碎笔记)
    零碎的知识点
    Objective--C之《文件操作》练习代码
    Objective--C的Foundation frame之NSMutableDictionary代码
    Objective--C的Foundation frame之NSMutableArray代码
    Objective--C随笔2016年8月7日
    关于OC中的委托delegate
    javascript 绝对路径工具类
    IE 弹出框处理经验
    InputStream和OutputStream 何时使用
  • 原文地址:https://www.cnblogs.com/fjl-vxee/p/9346613.html
Copyright © 2011-2022 走看看