【JavaScript】还记得如何手写实现call、apply、bind吗

1,610 阅读2分钟

写在前面

callapplybind是强制改变this指向的几种方法。大家应该看过了很多关于手写callapplybind的文章,并且自己手动尝试,已经实现手写源码~所以我写这篇文章的目的是怕大家忘了,及时出来帮大家复习一下🐶

本篇文章将列举三种方法之间的区别和手写三种方法。下面是一张思维导图:

三种方法的区别

  • callapply两个方法第一个参数都是要改变为的this指向,在非严格模式下,如果传递nullundefined,则指向window。第二个参数有所区别,call是将参数一个一个传递,而apply是将参数整体放到数组中传递。两个方法都是立即执行函数,call的性能要好于apply

  • bind方法预先改变this指向、传递参数,不立即执行函数。

手写三种方法

我们将方法直接挂到函数原型上

call

~ function anonymous(proto){
    // call
    function call(context = window, ...args) {
        // 判断context是否为null,为null时让其指向window
        context === null ? context = window : null;
        // 保证context是引用类型值
        var 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;
        // 执行函数赋值给result,删除传入函数,最后将结果返回
        var result = context.$fn(...args);
        delete context.$fn;
        return result;
        
    }
    
    proto.call = call;
}(Function.prototype)

上面就是实现的源码,现在测试一下它是否好用

var obj = {
    name: '小红',
    fn: function() {
        return this.name;
    }
}

var obj1 = {
    name: '小明'
}

console.log(obj.fn());             // 小红
console.log(obj.fn.call(null));    // 小白
console.log(obj.fn.call(obj1));    // 小明
let func = obj.fn;
console.log(func());               // 小白
console.log(func.call(obj1));      // 小明

apply

上面说了,callapply只是传参形式不一样,所以,我们只需要改一小部分

~ function anonymous(proto){
    // apply
    function apply(context = window, args) {
        // 判断context是否为null,为null时让其指向window
        context === null ? context = window : null;
        // 保证context是引用类型值
        var 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;
        // 执行函数赋值给result,删除传入函数,最后将结果返回
        var result = context.$fn(...args);
        delete context.$fn;
        return result;
        
    }
    
    proto.apply = apply;
}(Function.prototype)

测试用例

var obj = {
    name: '小红',
    fn: function(...args) {
        return [this.name, ...args];
    }
}

var obj1 = {
    name: '小明'
}

console.log(obj.fn());                            // ["小红"]
console.log(obj.fn.apply(null, [1, 2, 3, 5]));    // ["小白", 1, 2, 3, 5]
console.log(obj.fn.apply(obj1, [2, 3, 4, 6]));    // ["小明", 2, 3, 4, 6]
let func = obj.fn;
console.log(func());                              // ["小白"]
console.log(func.apply(obj1, [7, 5, 3]));         // ["小明", 7, 5, 3]

bind

接下来实现bind,之前的文章也有手写实现内置bind,在高级技巧应用的文章里:【JavaScript】几个必须要会的高级编程技巧,它是柯里化的应用

~ function anonymous(proto) {
    // es5
    function bind(context) {
        context = context || window;
        // 截取参数
        var outArgs = Array.prototype.slice.call(arguments, 1);
        var _this = this;
        return function() {
            var innerArgs = Array.prototype.slice.call(arguments);
            var args = innerArgs.concat(outArgs);
            return _this.apply(context, args);
        }
    }
    
    // es6
    function bind(context = window, ...args) {
        return (...innerArgs) =>  this.call(context, ...args.concat(innerArgs));
    }
    
    proto.bind = bind;
}(Function.prototype)

测试用例:

var obj = {
    name: '小红',
    fn: function(...args) {
        return [this.name, ...args];
    }
}

var obj1 = {
    name: '小明'
}

console.log(obj.fn());                            // ["小红"]
console.log(obj.fn.bind(null, 1, 2, 3, 5)());     // ["小白", 1, 2, 3, 5]
console.log(obj.fn.bind(obj1, 2, 3, 4, 6)());     // ["小明", 2, 3, 4, 6]
let func = obj.fn;
console.log(func());                              // ["小白"]
console.log(func.bind(obj1, 7, 5, 3)());          // ["小明", 7, 5, 3]

一道面试题

function fn1() {
    console.log(1);
}
function fn2() {
    console.log(2);
}
fn1.call(fn2);                      // 1
fn1.call.call(fn2);                 // 2
Function.prototype.call(fn1);       // 1
Function.prototype.call.call(fn1);  // undefined

这道题就不写解答过程了,欢迎大家积极讨论~

最后

综上为callapplybind的手写源码,如果觉得帮你复习的还不错🐶,就给本篇文章点个赞吧~如果有有瑕疵的地方,还请大家指出来,我们共同学习,一起进步~

最后,如果你想更加快速的收到文章推送,欢迎关注我的公众号「web前端日记」~