js方法call、apply、bind实现原理

720 阅读3分钟

1.首先整理一下大致的实现流程

首先要理解call、apply、bind的最终目的是改变this的指向, 而this的指向不去复杂的的理解,可简单理解为this指向调用函数的对象,当我们直接调用一个全局定义的或对象内定义的方法时,其实函数内部this是指向window或我们定义方法的那个对象,例如在全局作用域下定义let showtime = function() {} ,此方法在声明时,已经被挂在了window下,调用此方法的本质是window.showTime(),所以此函数内部this指向window,那么实现call、apply、bind等方法,我们只需要在方法内将this指向我们传入的context即可,如果传入的context不存在, 就默认让this指向window, 思路大致是这样, 下面我们就去动手实现。

2.方法的简单实现

首先我们需要将call、apply、bind先绑定到Function的原型上, 这样每一个函数都能调用这三个方法(因为对象本身没有的属性和方法,回去原型链中查找)

Function.prototype.selfCall = function(context) {
    //做些事
    console.log(this)
}

定义一个方法先检验一下我们的selfcall方法, 看一下我们selfcall中的this是不是指向调用selfCall的函数

let getTime = function() {
    console.log(1)
}

输出结果ƒ () { console.log(1) }, 所以与我们前面提到的一样,this指向了调用selfCall的对象,即getTime,那么我们在selfCall方法中的this已经明确, 就是 我们想要执行的函数, 即例如我们的示例中是getTime

接下来我们缓存一下传入的context, 如果context有值就用传入的值, 如果无值就设置为window

context = context || window;

context.fn = this 因为this是我们需要的函数, 我们把此函数挂在context的属性下, 当调用函数时用context.fn(), 那么我们想调用的函数内部的this就已经转变为我们传入的context了, 下面只需要将传入的参数传给函数即可, 下面是call方法的所有实现代码

Function.prototype.selfCall = function(context) {
    // 为防止不是函数调用增加一个类型判断, 只有是函数才能调用, 否则抛出错误
    if ( typeof this !== "function") {
        throw new TypeError('not function');
    }
    context = context || window;
    const fn = Symbol("fn");
    context[fn] = this;
    let arg = [...arguments].slice(1);  // 因为selfCall传递的第一个参数肯定是context, 所以把剩下的参数传给函数即可
    let res = context[fn](arg);
    delete context[fn];
    return res       // 传递出res原因是将结果传递出去
}

apply 实现和call并无太大差别, 只是传参方式进行相应的改变

Function.prototype.selfApply = function(context) {
    // 为防止不是函数调用增加一个类型判断, 只有是函数才能调用, 否则抛出错误
    if ( typeof this !== "function") {
        throw new TypeError('not function');
    }
    context = context || window;
    const fn = Symbol("fn");
    context[fn] = this;
    let res;
    if (arguments[1]) { /// 因为apply 传参是一个context 和一个数组, 所以通过arguments 取出数组传给函数即可,没有就不传参数
        res = context[fn](...arguments[1]);
    } else {
        res = context[fn]();
    }
    delete context[fn];
    return res       // 传递出res原因是将结果传递出去
}

bind绑定上下文环境和call, apply稍微有些不同,因为bind只绑定上线文环境并不立即执行,所以bind中需要一个闭包来完成只绑定context不执行的函数

 Function.prototype.selfBind = function(context) {
    // 为防止不是函数调用增加一个类型判断, 只有是函数才能调用, 否则抛出错误
    if ( typeof this !== "function") {
        throw new TypeError('not function');
    }
    let _this = this;  // 缓存this对象在闭包中使用
    let arg = [...arguments].slice(1);   // 取出context的参数

    return function F() {
        if ( this instanceof F) {// 有人可能会用new调用, 就需要此步骤来处理
            return _this(...arg, ...arguments);
        } else {
            // 此处用我们自己实现的myApply来处理,并将selfBind中的参数和F中的参数合并传递给要执行的函数
            return _this.myApply(context, ag.concat(...arguments));
        }
    }    
}