手动实现call,apply和bind

  1 /***************************************实现call************************************************/
  2 //目标:将this指向传入第一个对象,参数不定,且立即执行
  3 Function.prototype.myCall = function (obj){
  4     var args = Array.prototype.apply(arguments,[1]);
  5     obj.fn = this;
  6     obj.fn(...args);
  7     delete obj.fn;
  8 }
  9 
 10 //我们使用不带apply和bind的写法
 11 var foo ={
 12     value:1
 13   }
 14   function bar(){
 15     console.log(this.value)
 16   }
 17   bar.call(foo);//1
 18  
 19 /*
 20  * 我们现在为了改变this指向,通过上面的例子可以发现, 只需要把当前对象bar放到foo里面就可以改变指向了
 21  * 但是这样会增加额外的属性,所以最后我们再删除即可
 22  * 因此我们大致的模拟步骤为
 23  * 1. foo.fn = bar;     //添加属性
 24  * 2. foo.fn();         //执行
 25  * 3. delete foo.fn();  //删除该属性
 26  */
 27 
 28  //第一版实现this指向
 29  Function.prototype.myCall1 = function(context){
 30     context.fn = this;      //获取调用myCall的函数,用this
 31     context.fn();
 32     delete context.fn;
 33  }
 34 
 35  //call还可以传入参数,但是传入参数不确定咋办。因此我们可以利用arguments,从中取值,
 36  //取出第二个到最后一个参数,放到一个数组里面
 37  //第二版
 38 Function.prototype.myCall2 = function(context){
 39     context.fn = this;
 40     var args = [];
 41     for(let i = 1 , len = arguments.length ; i < len ; i++ ){
 42         args.push('arguments[' + i + ']');
 43     }
 44 
 45     eval('context.fn(' + i + ')');      //把这个参数数组放到要执行的函数的参数里面
 46 
 47     delete context.fn;
 48 }
 49 
 50 //还有两个问题,this可以传null,当为null时,视为window
 51 //另一个问题是函数是有返回值的
 52 //第三版
 53 Function.prototype.myCall3 = function(context){
 54     var context = context || window;
 55     context.fn = this;
 56 
 57     var args = [];
 58     for(let i = 1 , len = arguments.length ; i < len ; i++){
 59         args.push('arguments[' + i + ']');
 60     } 
 61 
 62     var result = eval('context.fn(' + i + ')');
 63 
 64     delete context.fn;
 65     return result;
 66 }
 67 
 68 //到此就算完了,我们最后优化一下代码,使用ES6的写法
 69 //前面的主要是用来学习思想,我们主要记下面的就可以了
 70 //最终版
 71 Function.prototype.myCall = function(context = window , ...args){
 72     context.fn = this;
 73     let result = context.fn(...args);
 74     delete context.fn;
 75     return result;
 76 }
 77 
 78 /***************************************实现apply************************************************/
 79 //apply和call的区别主要就是传参的问题,apply把call里面第二个以后的参数作为一个数组传入
 80 //因此同样的思想我们实现一下apply
 81 Function.prototype.myApply = function(context = window , arr){
 82     context.fn = this;
 83     let result ;
 84     if(!arr){
 85         result = context.fn;
 86     }else{
 87         result = obj.fn(...arr);
 88     }
 89     //上面的if语句可以用三目运算符代替
 90     //let result = !arr ? context.fn : obj.fn(...arr);
 91     
 92     delete context.fn;
 93     return result;
 94 }
 95 
 96 /***************************************实现bind************************************************/
 97 /* 同样基于call来实现bind函数,但是bind不是立即调用,所以需要返回一个函数
 98  * 但是要注意:bind 函数有个特点,就是在绑定的时候可以传参,返回的函数还可以继续传参
 99  * 如下情况:
100  * var person = {
101         name: 'jayChou'
102     };
103 
104     var say = function(p1, p2) {
105         console.log(this.name, p1, p2);
106     }
107 
108     var foo = say.myBind(person, 18);
109     foo(20);  // jayChou 18 20
110  * 
111  * 因此我们只需要把两次传参合在一起,并让结果返回一个函数即可
112  */
113 
114 Function.prototype.myBind = function(context = window , ...arg1){
115     context.fn = this;
116     return function(...arg2){
117         let args = [...arg1].concat(...arg2);       //concat用于把两个数组合并
118         let result = context.fn(...args);
119         delete context.fn;
120         return result;
121     }
122 }

猜你喜欢

转载自www.cnblogs.com/liuxinfuchen/p/12289169.html
今日推荐