欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

call, apply, bind的模拟实现

程序员文章站 2022-07-14 14:27:52
...

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方法,也可以参考该博文

相关标签: call apply bind