call, apply, bind的模拟实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/strongbill/article/details/86152501

call,apply,bind都是用于改变this的指向的,call和apply的区别在于,他们传递的参数不一样,后者是用数组传参,bind和call, apply两者的区别是,bind返回的是一个函数。
在模拟他们实现之前,我们需要先做些兼容处理,如下

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

call的实现

call的模拟实现的方法是

  1. 把函数挂载在对象上
  2. 然后执行该函数
  3. 删除该函数
Function.prototype.call = Function.prototype.call || function(context){
    // 当call的第一个参数是null的时候,this的默认指向是window
    var context = context || window;

    // 把该函数挂载到对象上
    context.fn = this;

    // 用于存储call后面的参数
    var args = [];
    var len = arguments.length;

    // 这里是为了将一个函数的参数传入到另外一个函数执行
    for(var i = 1; i < len; i++){
        args.push('arguments[' + i + ']');
    }

    // 这里的args默认是会调用Array.toString()方法的
    var result = eval('context.fn(' + args + ')');

    // 删除挂载在对象里的该函数
    delete context.fn;

    // 因为函数可能有放回值,所以把结果也返回出去给他们
    return result;
}

var data = {
    name: 'mr3x'
}

function Person(age){
    console.log(this.name, age);
    return {
        name: this.name,
        age: age,
    };
}

Person.call(data, 21);  //mr3x 21

apply的实现

apply的模拟实现和call的模拟实现大同小异

Function.prototype.apply = Function.prototype.apply || function(context, arr){
    context.fn = this;
    var result;
    var args = [];
    var arr = arr || [];
    var len = arr.length;
    if(!Array.isArray(arr)) throw new Error('apply的第二个参数必须是数组')
    for(var i = 0; i < len; i++){
        args.push('arr[' + i +']');
    }
    result = eval('context.fn('+ args + ')');
    delete context.fn;
    return result;
}

var data = {
    name: 'mr3x'
}

function Person(age){
    console.log(this.name, age);
    return {
        name: this.name,
        age: age,
    };
}

Person.apply(data, [21]);  //mr3x 21

bind的实现

bind的实现有两个难点,第一个难点是bind的参数问题,因为bind返回的是一个函数(也叫绑定函数),它不仅可以在bind里面带参数,还可以在绑定函数里面带参数,如下

var data = {
    name: 'mr3x'
}
function Person(name, age){
    console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
bindPerson(21);  //mr3x bill 21

第二个难点是返回的绑定函数中可以使用new来创建对象(本质是把原函数当成是构造函数),但是这使后的修改的this的指向就失效了,因为用new来创建对象,本质是调用了apply,apply也是会修改this的指向的,所以之前bind绑定的就失效了,如下

var data = {
    name: 'mr3x'
}
function Person(name, age){
    console.log(this.name, name, age);
}
var bindPerson = Person.bind(data, 'bill');
var man = new bindPerson(21);   //undefined bill 21

代码实现

Function.prototype.bind = Function.prototype.bind || function(context){
    var self = this;
    // 用于解决调用的对象非函数
    if(typeof self !== 'function'){
        throw new Error('非函数无法调用bind');
    }

    // 外部参数的处理
    var args = Array.prototype.slice.call(arguments, 1);

    // 用于解决引用类型的传值问题
    var index = function(){}

    var fBound = function(){
        // 用于处理绑定函数里的参数问题
        var bindArgs = Array.prototype.slice.call(arguments);
        // 当是普通函数的时候,this指向window,
        //但是构造函数的时候,this指向实例,
        return self.apply(this instanceof index ? this : context, args.concat(bindArgs));
    }

    index.prototype = this.prototype;
    fBound.prototype = new index();
    return fBound;
}

上述代码有一个问题,它只是勉强算是模拟了bind的实现,有一个缺陷就是,原生的bind所返回的绑定函数是没有prototype属性的,最高级版本就去看ES5-shim的源码是如何实现bind方法,也可以参考该博文

猜你喜欢

转载自blog.csdn.net/strongbill/article/details/86152501