如何实现javaScript的 call、apply、bind

135 阅读3分钟

我正在参加「掘金·启航计划」

写在前面

如何优雅的手写call、apply、bind方法?

看这篇文章之前确定自己是否真正理解了js的this指向问题。

友情链接:面试官问:JS的this指向 - 掘金 (juejin.cn)

const oldObject = {
    name: "小蔡",
    age: 18,
    skills(hobby) {
      console.log(`${this.name}今年${this.age}岁,喜欢${hobby}`);
    },
};

oldObject.skills("唱跳");
  
//小蔡今年18岁,喜欢唱跳

那现在如果我想在一个新的对象里面调用这个方法并且保持这个方法内的this指向的是我们这个新的对象,那么这个时候就需要用到js函数的内置方法.call()了。话不多说,直接上代码。

const oldObject = {
    name: "小蔡",
    age: 18,
    skills(hobby) {
      console.log(`${this.name}今年${this.age}岁,喜欢${hobby}`);
    },
};

const newObject = {
    name: "小坤",
    age: 20,
}

oldObject.skills.call(newObject,'篮球')

//小坤今年20岁,喜欢篮球

当然 这和后面我们所说的apply方法并没有太大区别,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。在调用一个存在的函数时,你可以为其指定一个 this 对象。this 指当前对象,也就是正在调用这个函数的对象。使用 apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

如何实现

基本用法我们已经知道了,那么现在来看一下如何去实现这个call方法。

Call

首先先把我们的方法挂载到函数的原型上

Function.prototype.newCall = function newCall(_target = undefined, ..._args) {
    
};

_target是函数运行时使用的 this 值,但有一点要注意的是,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局window对象,原始值会被包装。

Function.prototype.newCall = function newCall(_target = undefined, ..._args) {
    //获取target
    const target = _target ?? window;
    //创建唯一方法
    const symbolFunction = Symbol();
    //this指向当前函数
    target[symbolFunction] = this;
    //执行当前方法
    const value = target[symbolFunction](..._args);
    //执行完成删除创建出的唯一键
    delete target[symbolFunction];
    //最后返回我们获取的数据
    return value;
};

当然这里要注意的是串代码const symbolFunction = Symbol();用的是ES6中的Symbol(),目地只是为了防止我们随意定义的这个方法在传入_target上已经存在了,那就得不偿失了。

其实call方法本质上就是我们去复制一个目标对象内的方法然后借用一下,得到所需的值最后在把这个复制的方法删掉就ok了。那接下来apply就更简单了,apply 使用参数数组而不是一组参数列表。

Apply

Function.prototype.newApply = function newApply(_target = undefined, _args) {
    //获取target
    const target = _target ?? window;
    //创建唯一方法
    const symbolFunction = Symbol();
    //this指向当前函数
    target[symbolFunction] = this;
    //执行当前方法
    const value = target[symbolFunction](..._args);
    //执行完成删除创建出的唯一键
    delete target[symbolFunction];
    //最后返回我们获取的数据
    return value;
};

Bind

bind()  方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。如果看懂了上面call和apply的实现,那么bind方法也难不倒你。

Function.prototype.newBind = function newBind(_target = undefined, ..._args) {
    //获取target
    const target = _target ?? window;
    const that = this
    return function (...tArgs) {
        //创建唯一方法
        const symbolFunction = Symbol();
        //this指向当前函数
        target[symbolFunction] = that;
        //执行当前方法
        const value = target[symbolFunction](..._args, ...tArgs);
        //这里用完删除
        delete target[symbolFunction]
        return value;
    };
};

总结

最后说一下call、apply、bind的共同点和区别:

共同点:都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行。

区别:

  1. call和apply是立即执行的。bind是返回一个方法,需要你手动调用执行。
  2. call和bing从第二个参数开始都可以接受n个参数,并且这些参数都会映射到你所要执行的函数的入参位置上。apply接收的是一个数组,同样也会映射到所要执行的函数入参上。

然后原生实现肯定对于方法内有很多判读、例如判断入参合法性等、在这就不一一举例写出了,只是实现了功能、如果代码错误或理解不到位的地方还请各位看官指出~