bind函数源码解析

bind函数

ES5中原生bind函数是用于绑定this指向的

   var obj = {
      x: 1
   }
   function show(){
       console.log(this.x);
   }
   var newShow = show.bind(obj);
   newShow();    // 1

bind方法会创建一个新函数。当这个新函数被调用时,bind的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。
bind返回的绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数

初级实现

我们来实现一个初级的bind函数Polyfill:

   Function.prototype.bind = function(context){
      //that为调用bind函数的对象
      var that = this;
      //bind函数可能入其他参数,先将参数类数组转化为数组
      var argsArray = Array.prototype.slice.call(arguments);
      return function(){
          // slice只传一个参数,返回从参数开始到数组末尾的元素
          // slice(1)之后为其他参数
          return that.call(context, argsArray.slice(1))
      }
   }

译者注:如果对这个函数有所不理解,我们可以先看一下不改变this的改造

   function a(){ console.log(1) };
   Function.prototype.bind = function(){
      return this;
   }
   var b = a.bind();
   b();   // 1

进行嗅探增加程序健壮性

其实是一个典型的“Monkey patching(猴子补丁)”,即“给内置对象扩展方法”。所以,如果能进行一下“嗅探”,进行兼容处理,就是锦上添花了。

   Function.prototype.bind = Function.prototype.bind || function(context){
      //...
   }

柯里化(curring)实现

我们返回的参数列表里包含:atgsArray.slice(1),他的问题在于存在函数的参数丢失的现象。

   Function.prototype.bind = Function.prototype.bind || function (context) {
       var that = this;
       //通过bind预先传入的参数
       var args = Array.prototype.slice.call(arguments, 1);
       return function () {
           //函数调用时传入的参数
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return that.apply(context, finalArgs);
        }
   }

但是,我们注意在上边bind方法介绍的第三条提到:bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”

构造函数场景下的兼容

直接上代码

    Function.prototype.bind = Function.prototype.bind || function (context) {
        var that = this;
        var args = Array.prototype.slice.call(arguments, 1);
        var F = function () {};
        F.prototype = this.prototype;
        var bound = function () {
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return that.apply(this instanceof F ? this : context || this, finalArgs);
        }
        //把新函数的原型清空
        bound.prototype = new F();
        return bound;
    }

更严谨的做法

我们需要调用bind方法的一定要是一个函数,所以可以在函数体内做一个判断:

    if (typeof this !== "function") {
      //使用类型错误
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

一切还没完

es5-shim源码如下所示

   bind: function bind(that) {
        var target = this;
        if (!isCallable(target)) {
            throw new TypeError('Function.prototype.bind called on incompatible ' + target);
        }
        var args = array_slice.call(arguments, 1);
        var bound;
        var binder = function () {
            if (this instanceof bound) {
                var result = target.apply(
                    this,
                    array_concat.call(args, array_slice.call(arguments))
                );
                if ($Object(result) === result) {
                    return result;
                }
                return this;
            } else {
                return target.apply(
                    that,
                    array_concat.call(args, array_slice.call(arguments))
                );
            }
        };
        var boundLength = max(0, target.length - args.length);
        var boundArgs = [];
        for (var i = 0; i < boundLength; i++) {
            array_push.call(boundArgs, '$' + i);
        }
        bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);

        if (target.prototype) {
            Empty.prototype = target.prototype;
            bound.prototype = new Empty();
            Empty.prototype = null;
        }
        return bound;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值