call、apply、bind的手写实现( ES5 )

122 阅读1分钟

1.apply

  1. 判断this是否传入,没有则指向全局
  2. 通过将调用的函数绑定在传入的context上的方式,context.fn()调用,将fn的this指向context
  3. 判断参数数组是否传入,没有则直接调用得到结果,反之使用eval函数结算结果
  4. 最终删除context上的fn属性,恢复context的初始状态,将结果返回
Function.prototype.apply = function(context, arr){
    context = context || (typeof window === 'undefined') ? global : window;
    let res;
    context.fn = this;
    if(!arr || arr.length === 0){
        res = context.fn();
    }else{
        let args = [];
        for(let i = 0; i < arr.length; i++){
            args.push("arr[" + i + "]");
        }
        res = eval("context.fn(" + args + ")");
        // res = context.fn(...arr);  ES6
    }
    delete context.fn;
    return res;
}

2.call

  1. 判断this是否传入,没有则指向全局
  2. 通过将调用的函数绑定在传入的context上的方式,context.fn()调用,将fn的this指向context
  3. 使用eval函数结算结果
  4. 最终删除context上的fn属性,恢复context的初始状态,将结果返回
Function.prototype.call = function(context){
    context = context || (typeof window === 'undefined') ? global : window;
    let res;
    context.fn = this;
    let args = [];
    for(let i = 1; i < arguments.length; i++){
        args.push("arguments[" + i + "]");
    }
    res = eval("context.fn(" + args + ")");
    delete context.fn;
    return res;
}

3.bind

  1. 取出this的引用存入变量中
  2. 创建一个干净的函数用于新函数的原型继承
  3. bind中传入的参数会插入新函数调用时的实参的前面
  4. bind返回的新函数支持new调用,判断是否是new调用来决定this的指向
  5. 通过圣杯模式采用的原型继承方式继承原型对象
Function.prototype.bind = function(obj){
    let fn = this;
    // 干净的函数
    let _fn = function(){};
    let args = Array.prototype.slice.call(arguments, 1);
    function bound(){
        // 支持柯里化
        let params = Array.prototype.slice.call(arguments);
        // 支持new调用,判断是否是new调用来决定this指向
        return fn.apply((this instanceof fn_) ? this : obj || window, args.concat(params));
    }
    // 圣杯模式的原型对象继承方式
    fn_.prototype = fn.prototype;
    bound.prototype = new fn_();
    return bound;
}

手撕代码系列