js重写内置的call、apply、bind

更新日期: 2020-01-27阅读: 1.5k标签: apply
首先看call和apply,第一个参数就是改变的this指向,写谁就是谁,如果是非严格模式下,传递null或undefined指向的也是window,二者唯一的区别是执行函数时,传递的参数方式不同,call是一个个的传递,apply是把需要传递的参数放到数组中整体传递。
·func.call([context], x, y)
·func.apply([context], [x, y])
再看bind,它和call和apply都是改变this并且传递一些参数,不同于call和apply在改变this的同时直接把函数就执行了,bind不会立即执行函数。
let obj = {
    fn(x, y) {
        console.log(this, x, y);
    }
};
        
obj.fn.call({}, 10, 20); // {}, 10, 20
obj.fn.apply(window, [10, 20]); //window, 10, 20

setTimeout(obj.fn.bind(30, 10, 20), 1000); //Number(30), 10, 20

先试着重写一下bind:

从参数看,首先是传递一个this指向并需要做一下处理,后续还有若干个参数

function bind(context) {
    //context可能是null或undefined,需要处理一下
    if (context == undefined) {
        context = window;
    }
    //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合
    var args = [].slice.call(arguments, 1);
}

bind函数中的this是指最终要执行的函数,而且执行bind的时候会返回一个新的匿名函数,并且在这个新的函数中执行最终要执行的函数也就是this,并且改变其this指向:

function bind(context) {
    //context可能是null或undefined,需要处理一下
    if (context == undefined) {
        context = window;
    }
    //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合
    var args = [].slice.call(arguments, 1);
    //需要最终执行的函数
    var _this = this;
    return function anonymous() {
        _this.apply(context, args);
    };
}    

这个bind函数大体算是写完,但还是有些问题,比如给元素进行事件绑定,div.onclick = obj.fn.bind(window, 10, 20),元素进行点击的时候,会有ev事件对象,相当于在执行bind函数返回的那个匿名函数中也需要传递参数,而且参数个数不确定,当然,最后还需要改写一下原型上的方法:

function bind(context) {
    //context可能是null或undefined,需要处理一下
    if (context == undefined) {
        context = window;
    }
    //借用数组的slice方法结合arguments获取传递的指定this之后的参数集合
    var args = [].slice.call(arguments, 1);
    //需要最终执行的函数
    var _this = this;
    return function anonymous() {
        var amArg = [].slice.call(arguments, 0);
        _this.apply(context, args.concat(amArg));
    };
} 
Function.prototype.bind = bind;

这样,这个bind函数的重写算是完成了。

我们用es6改写一下:

function bind(context = window, ...args) {
    return (...amArg) => this.call(context, ...args.concat(amArg));
}

代码看上去确实精简不少,当然也可以用apply,但经测试,性能不如call。


接下来看看重写call:

 它会有若干个参数,第一个是要指向的this,直接用es6写法:

function call(context = window, ...args) {

}

函数中的this就是要调用call方法的函数,想让该函数执行并且其内部this指向传递进来的context,那么形如context.函数可以做到:

function call(context = window, ...args) {
    //给context增加一个$fn属性,把当前函数赋给这个属性      
    context.$fn = this;
    //让context.$fn这个方法执行,就是之前this函数执行,并且this指向的是context
    let result = context.$fn(...args);
    //增加完方法应该删除 
    delete context.$fn;
    return result;
}
Function.prototype.call = call;

 

apply也就出来了:

function apply(context = window, args) {
    context.$fn = this;
    let result = context.$fn(...args);
    delete context.$fn;
    return result;
}
Function.prototype.apply = apply;

这里边还是有两个问题,一是$fn属性没有删除,目前还没想到解决办法,一个就是传进来的context必须是引用类型,但其实可以是基础类型:

function call(context = window, ...args) {
    context === null ? context = window : null;
    let type = typeof context;
    if (type !== "object" && type !== "function" && type !== "symbol") {
        //=>基本类型值
        switch (type) {
            case 'number':
                context = new Number(context);
                break;
            case 'string':
                context = new String(context);
                break;
            case 'boolean':
                context = new Boolean(context);
                break;
        }
    }
    context.$fn = this;
    let result = context.$fn(...args);
    delete context.$fn;
    return result;
}

apply的判断就不写了,但基本都实现了重写,当然,这几个方法毕竟是js的内置写法,我们只是想大致实现它们的实现原理。

链接: https://fly63.com/article/detial/7955

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!