手动实现call() , apply() , bind()

这篇文章简单的介绍了实现call() , apply() , bind()的思路

实现call(obj,arg,arg....)

将目标函数的this指向传入的第一个对象,参数为不定长,且立即执行

实现思路

  • 改变this指向:可以将目标函数作为这个对象的属性
  • 利用arguments类数组对象实现参数不定长
  • 不能增加对象的属性,所以在结尾需要delete

那么怎么将不定长的参数传递给函数呢?有三种办法:eval,apply,ES6的解构语法

eval('obj.fn('+args+'));
obj.fn.apply(obj,args);
obj.fn(...args);
Function.prototype.mycall = function(obj){
         var args = Array.prototype.slice.apply(arguments,[1]);
         obj.fn = this;
         obj.fn(...args);//es6的解构语法,也可以使用obj.fn.apply(obj,args);
         delete  obj.fn;
}

使用eval时,eval会将一个字符串解析为变量,所以如果传入的参数为字符串,会报 xxx is not defined,解决办法如下:

Function.prototype.mycall = function(obj){
        obj = obj||window;
         var args = [];
        for(var i = 1 ; i < arguments.length; i++) {
                  args.push('arguments[' + i + ']');
         }

         obj.fn = this;
         eval('obj.fn('+args+'));
         delete  obj.fn;
}

实现apply()

与call()只有一个区别,apply第二个参数为数组

Function.prototype.myapply = function(obj,arr){
        obj.fn = this;
         if(!arr){
             obj.fn();
        }else{
             var args = []; 
       for(var i = 0; i < arr.length; i++) {
             args.push('arr[' + i + ']');
         }
         
         eval('obj.fn('+args+')');
 }
       
         delete  obj.fn;

}

实现bind()

返回一个与被调函数具有相同函数体的新函数,且这个新函数也能使用new操作符。

实现思路

  1. 返回一个新函数,可以使用闭包
  2. dind()传入的参数长度不定,使用函数内置的arguments对象数组,可利用Array.prototype.slice.call(arguments,1 )将其转化为数组
  3. 返回的新函数中,使用apply改变被调用函数的this指向,将arguments转化成的数组作为apply的第二个参数
  4. 因为返回的新函数也可以使用new操作符,所以在新函数内部需要判断是否使用了new操作符(为什么需要判断,后面会讲解到),如果使用则将apply的第一个参数设置为新创建的对象,如果没有则设置为在调用bind()时所传入的对象(不传的话默认为window)。

需要注意的是怎么去判断是否使用了new操作符呢?在解决这个问题之前,我们先看使用new操作符时具体干了些什么,下面是new操作符的简单实现过程:

//简洁版的new操作符实现过程

function newFunc(constructor){
      //第一步:创建一个空对象obj 
        var obj = {};
       //第二步:将构造函数 constructor的原型对象赋给obj的原型
        obj.__proto__ = constructor.prototype;
      //第三步:将构造函数 constructor中的this指向obj,并立即执行构造函数内部的操作
        constructor.apply(obj);
      //第四步:返回这个对象
        return obj;
}

new操作符的一个过程相当于继承,新创建的构造函数的实例可以访问构造函数的原型链

在new操作符实现过程的第三步中,会将构造函数 constructor中的this指向obj,并立即执行构造函数内部的操作,那么,当在执行函数内部的操作时,如果不进行判断是否使用了new,就会导致 " 将构造函数 constructor中的this指向obj " 这一过程失效,具体原因请看下面的模仿实现bind()的代码:

Function.prototype.testBind = function(object){

          var that = this,
              args = Array.prototype.slice.call(arguments,1),
              bound = function(){
                    return that.apply(this instanceof fNOP?this:object||window,
                         args.concat.apply(Array.from(arguments)));
          };

        //创建一个中转函数fNOP,让bound间接继承目标函数的原型
          var fNOP =  function(){};
          fNOP.prototype= that.prototype;   
          bound.prototype= new fNOP();  
  
          return bound;
}

重点:创建一个中转函数fNOP,让bound间接继承目标函数的原型,一开始我想为什么不直接让 bound.prototype = that.prototype ,后来才发现直接赋值后,bound.prototype和that.prototype指向同一块内容,如果改变bound.prototype就会直接影响that.prototype,使用一个中转函数, bound.prototype= new fNOP()将bound.prototype的__poro__指向fNOP.prototype,然后fNOP.prototype = that.prototype,所以此时改变bound.prototype并不会影响that.prototype。

另外:上面实现bind()的代码中使用apply的地方可以换成原生实现的代码

猜你喜欢

转载自blog.csdn.net/qq_36367995/article/details/81319852
今日推荐