2020-02-17(手写实现call,apply,bind)

/***************************************实现call************************************************/
//目标:将this指向传入第一个对象,参数不定,且立即执行
Function.prototype.myCall = function (obj){
    var args = Array.prototype.apply(arguments,[1]);
    obj.fn = this;
    obj.fn(...args);
    delete obj.fn;
}

//我们使用不带apply和bind的写法
var foo ={
    value:1
  }
  function bar(){
    console.log(this.value)
  }
  bar.call(foo);//1
 
/*
 * 我们现在为了改变this指向,通过上面的例子可以发现, 只需要把当前对象bar放到foo里面就可以改变指向了
 * 但是这样会增加额外的属性,所以最后我们再删除即可
 * 因此我们大致的模拟步骤为
 * 1. foo.fn = bar;     //添加属性
 * 2. foo.fn();         //执行
 * 3. delete foo.fn();  //删除该属性
 */

 //第一版实现this指向
 Function.prototype.myCall1 = function(context){
    context.fn = this;      //获取调用myCall的函数,用this
    context.fn();
    delete context.fn;
 }

 //call还可以传入参数,但是传入参数不确定咋办。因此我们可以利用arguments,从中取值,
 //取出第二个到最后一个参数,放到一个数组里面
 //第二版
Function.prototype.myCall2 = function(context){
    context.fn = this;
    var args = [];
    for(let i = 1 , len = arguments.length ; i < len ; i++ ){
        args.push('arguments[' + i + ']');
    }

    eval('context.fn(' + i + ')');      //把这个参数数组放到要执行的函数的参数里面

    delete context.fn;
}

//还有两个问题,this可以传null,当为null时,视为window
//另一个问题是函数是有返回值的
//第三版
Function.prototype.myCall3 = function(context){
    var context = context || window;
    context.fn = this;

    var args = [];
    for(let i = 1 , len = arguments.length ; i < len ; i++){
        args.push('arguments[' + i + ']');
    } 

    var result = eval('context.fn(' + i + ')');

    delete context.fn;
    return result;
}

//到此就算完了,我们最后优化一下代码,使用ES6的写法
//前面的主要是用来学习思想,我们主要记下面的就可以了
//最终版
Function.prototype.myCall = function(context = window , ...args){
    context.fn = this;
    let result = context.fn(...args);
    delete context.fn;
    return result;
}

/***************************************实现apply************************************************/
//apply和call的区别主要就是传参的问题,apply把call里面第二个以后的参数作为一个数组传入
//因此同样的思想我们实现一下apply
Function.prototype.myApply = function(context = window , arr){
    context.fn = this;
    let result ;
    if(!arr){
        result = context.fn;
    }else{
        result = context.fn(...arr);
    }
    //上面的if语句可以用三目运算符代替
    //let result = !arr ? context.fn : obj.fn(...arr);
    
    delete context.fn;
    return result;
}

/***************************************实现bind************************************************/
/* 同样基于call来实现bind函数,但是bind不是立即调用,所以需要返回一个函数
 * 但是要注意:bind 函数有个特点,就是在绑定的时候可以传参,返回的函数还可以继续传参
 * 如下情况:
 * var person = {
        name: 'jayChou'
    };

    var say = function(p1, p2) {
        console.log(this.name, p1, p2);
    }

    var foo = say.myBind(person, 18);
    foo(20);  // jayChou 18 20
 * 
 * 因此我们只需要把两次传参合在一起,并让结果返回一个函数即可
 */

Function.prototype.myBind = function(context = window , ...arg){
    let _this = this;
    let bound = function(){
        return _this.apply(this instanceof _this ? this : context , arg.concat([...arguments]));
    }

    bound.prototype = new this();
    return bound;
}

/*
 * 如果我们的bind调用的时候指向了一个新对象,上面的办法显然就不行了,所以我们实现了一下继承
 */

Function.prototype.myBind = function(context = window , ...arg1){
    context.fn = this;
    let bound = function(){
        let args = [...arg1].concat(...arguments);       //concat用于把多个数组合并.

        context.fn = this instanceof context.fn ? this : context; //调用bind的时候可能会发生this改变
        let result = context.fn(...args);
        delete context.fn;
        return result;
    }

    bound.prototype = new this();       //实现继承,因为调用的时候可能会用new创建一个新对象
    return bound;
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。