apply call bind的用法与实现

概念

apply call 和bind 容许为不一样的对象分配和调用属于一个对象的函数/方法。同时它们能够改变函数内 this 的指向。前端

区别

  • apply 和 call 接收的参数形式不一样git

  • apply 和 call 都是直接调用函数并获得函数执行结果,而 bind 会返回待执行函数,须要再次调用github

用法演示

咱们先建立一个对象 parentapp

const parent = {
    name: 'parent',
    sayPerson (age, addr) {
        return {
            name: this.name,
            age,
            addr
        }
    }
}

显然它具备 name 属性,及方法 sayPerson,咱们如今能够经过 parent.sayPerson() 来输出该对象信息。框架

const person = parent.sayPerson(60, 'shenzhen');
// {name: "parent", age: 60, addr: "shenzhen"}

如今咱们再建立一个对象 son函数

const son = {
    name: 'son'
}

咱们如今也想获得 son 的信息,可是 son 对象没有 sayPerson 函数怎么办?借助已有的 parent 对象和 call 方法,咱们能够这样写学习

const person = parent.sayPerson.call(son, 26, 'shenzhen');
// {name: "son", age: 26, addr: "shenzhen"}

能够看出,经过调用 call 函数,咱们为 son 对象分配了 sayPerson 方法并进行调用。实现了一个对象能够调用不属于它本身的方法,而且函数内的 this 指向该对象。apply 方法的用法其实同样,只是传参有些区别测试

const person = parent.sayPerson.call(son, [26, 'shenzhen']);
// {name: "son", age: 26, addr: "shenzhen"}

bind 函数则不直接调用函数,而是返回待调用函数this

const sayPersonFn = parent.sayPerson.bind(son, 26, 'shenzhen');

const person = sayPersonFn();
// {name: "son", age: 26, addr: "shenzhen"}

以上就是三者的使用方法和区别,下面咱们来看看它们是如何实现的prototype

实现

call的实现

实现原理就是为对象 obj 添加须要调用的方法,接着调用该方法(此时 this 指向 obj),调用事后再删除该方法

简单版

Object.prototype.callFn = function (...args) {
    // 第一个参数为目标对象
    const context = args[0];

    args.shift();

    // 为对象赋值须要调用的方法
    context.fn = this;

    // 调用该方法
    context.fn(...args);

    // 删除方法
    delete context.fn;
}

加上返回值

Object.prototype.callFn = function (...args) {
    // 第一个参数为目标对象
    const context = args[0];

    args.shift();

    // 为对象赋值须要调用的方法
    context.fn = this;

    // 调用该方法
    const result = context.fn(...args);

    // 删除方法
    delete context.fn;

    return result;
}

在测试中发现,咱们调用 call,若是第一个参数是 null 或者 undefined,那么 call 将以全局 window 来调用方法,此时 this 也指向 window。若是第一个参数不是对象类型,则以空对象 {} 来调用方法。

Object.prototype.callFn = function (...args) {
    // 第一个参数为目标对象
    let context = args[0];

    // undefined 和 null 指向 window
    if (context === null || context === undefined) {
        context = window;
    }

    // 不是对象类型则建立空对象
    if (typeof context !== 'object') {
        context = {};
    }

    args.shift();

    // 为对象赋值须要调用的方法
    context.fn = this;

    // 调用该方法
    const result = context.fn(...args);

    // 删除方法
    delete context.fn;
    
    return result;
}

至此,咱们实现了一个完整的 call 方法。

apply的实现

既然和 call 只存在传参的区别,那咱们只须要简单修改下已实现的 call 方法便可。

Object.prototype.applyFn = function (...args) {
    let context = args[0];

    if (context === null || context === undefined) {
        context = window;
    }

    if (typeof context !== 'object') {
        context = {};
    }

    args.shift();

    context.fn = this;

    // 和 call 存在差别的地方
    const result = context.fn(...args[0]);

    delete context.fn;

    return result;
}

bind的实现

在实现了 apply 和 call 的前提下,bind 的实现也比较简单。

Object.prototype.bindFn = function (...args) {
    // 实际就是多包了层待执行函数
    return () => {
        return this.applyFn(args[0], (args || []).slice(1));
    }
}

至于以 bind 方法返回的函数做为构造函数来建立对象会存在的问题请参考JavaScript深刻之bind的模拟实现

总结

call apply bind 在工做中其实是比较常见的函数,特别是在一些框架或库的源码中,可是常常有人会混淆它们的用法。但愿你们经过此篇文章能够完全弄清它们的做用与区别,而且知道其实现原理,知其然知其因此然。

参考


欢迎到前端学习打卡群一块儿学习~516913974

相关文章
相关标签/搜索