call、apply以及bind的模拟实现

127 阅读2分钟

这三个函数的使用方式很像,细微之处有所区别。但是都可以划分为三部分,调用者绑定this以及绑定参数

1.call

用法回顾: 以下面的例子为例,调用者为fn,绑定this为obj,绑定值为1,2。

const obj = {
    id: 19
}
const fn = function () {
    const args = [...arguments]
    console.log(args)
    console.log(this.id)
}
fn.call(obj, 1, 2) // [1,2]  19

模拟实现:

Function.prototype.call = function (context) {
    // 1.获取绑定this
    var context = context || window
    // 2.获取调用者 this永远指向调用者
    var fn = this
    // 3.获取绑定参数
    var args = [...arguments].slice(1)
    // 4.绑定this执行调用者函数,并将结果返回
    context.f = fn
    var result = context.f(...args)
    delete context.f
    return result
}

这种方式需要说明的一点是conext.f = fn以及delete context.f。 context是绑定this, fn是调用者。所以在context上新增一个属性,其值是fn函数。执行这个属性就可以获取到想要的结果。然后再把这个属性删除即可。 但是这种方式的弊端就在此处。假如context本身就有一个名为f的属性,调用call方法后,会导致该属性被删除。有一种解决方式是使用es6的sympol。但是es3的api使用es6...... 目前大多数模拟实现都是这种思路,弊端的解决,我还没有看原生是如何处理实现的。暂且记下~

2.apply

apply与call的唯一区别只是参数的不同。

Function.prototype.apply = function (context) {
    // 1.获取绑定this
    var context = context || window
    // 2.获取调用者 (this永远指向调用者)
    var fn = this
    // 3.获取绑定参数 (如果参数存在且为数组)
    if (arguments[1] && arguments[1] instanceof(Array)) {
        var args = arguments[1]
        // 4.绑定this执行调用者函数,并将结果返回
        context.f = fn
        var result = context.f(...args)
        delete context.f
        return result
    } else {
        throw new Error('Error!')
    }
}

3.bind

bind与call、apply不同的一点在于,bind只是返回一个绑定了this的新函数。

Function.prototype.bind = function (context) {
    // 1.获取绑定的this
    var context = context || window
    // 2.获取调用者
    var fn = this
    // 3.获取绑定参数
    var args = [...arguments].slice(1)
    return function () {
        // 4.将此函数中arguments与外层获取的args连接,得到最终参数
        const arr = args.push(...arguments)
        // 5.调用
        return fn.call(context, ...arr)
    }
}

4.总结

心血来潮,复习了下call、apply以及bind的原理模拟实现。主要原因是在es6-shim遇见了Function.prototype.call.bind(Array.prototype.push)这句语法。具体文章链接可见下方。

Function.prototype.call.bind