记一次面试题——call、apply、bind模拟实现的更好方式

1,335 阅读4分钟

写在前面

看有人分享的某公司面试题,用js闭包实现callapplybind

这是我没有考虑过的面试题,很好,它成功引起了我的兴趣。

很多人表示不理解,为什么面试题总出这些看上去毫无作用的实现。

面试题的意义

上次我写了一个promise模拟实现应该注意的点,也有人吐槽重复造轮子意义何在。

其实这就是理念的问题了,在一家重视基础的公司里,轮子不是会用就行,甚至你可以不需要会轮子,但是理念以及基础一定要过关,不然你写出来的隐患代码往往会给公司带来未知的风险。

(想起了上月某男装店网页上的bug,应该被不少人薅了羊毛 🐑。)

分享的兄弟写出了自己的答案,解法中用了evalarguments,忘记return结果等等一些严重的问题。说实话,他给的答案我只能给5分(10分制),甚至让我看着有点难受。

醒醒吧,兄弟们,都9012年了,我们是否可以用js的新特性实现呢?

实现思考

方法容错:

一个合格的程序员,在写方法的时候第一点应该想到的是做好容错——当callapplybind传入第一个参数不是一个引用类型的时候,你应该要知道它的表现。

容错测试

剩下的applybind测试结果一样。

所以第一步:

    Function.prototype.call = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.apply = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.bind = function(context) {
        context = context === undefined || context === null ? window : Object(context)
    }

获取参数

接下来是获取参数,call多个参数用逗号分隔,apply参数作为数组传入,bind是逗号分隔,那么取未知个参数,你一定想到了arguments.

但是相比arguments获取参数,我们是否应该想想,在新特性中能够怎么做?

没错,就是rest参数

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
    }

修改this指向

终于可以进入主题了,无论我们用callapply还是bind,唯一的目的就是修改函数内部的this指向。

仔细想想,在callapply方法中我们拿到的this应该就是原函数,所以把this赋值给context然后调用就改变了原函数的this指向。

当然不要直接简单的使用对象赋值,为了外部取不到这个属性,这里使用Symbol是合理的。

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        context[fn](...args)
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
          const fn = Symbol('fn')
        context[fn] = this
        context[fn](...args)
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        return function(...args) {
            context[fn](...bindArgs, ...args)
        }
    }

最后一步

很明显,这里还有个问题就是修改了原对象上下文context以及函数没有返回值,所以我们应该return 结果以及调用完毕后删除多余的原函数引用,除了bind

bind作为一个特例,它的方法会返回一个新函数,如果这时候把原函数放到context上,我们不能删除它的原函数引用context._$fn,否则将在调用的时候报错。

所幸的是我们已经在上文中实现了callapply函数,在这里用上相得益彰,也表现出了你的封装思想。

那么修改一下bind方法(注意bind传入的参数在新函数传入的参数之前):

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        return (...args) => this.apply(context, [...bindArgs, ...args])
    }

完整的实现

    Function.prototype.call = function(context, ...args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.apply = function(context, args) {
        context = context === undefined || context === null ? window : Object(context)
        const fn = Symbol('fn')
        context[fn] = this
        const result = context[fn](...args)
        delete context[fn]
        return result
    }
    
    Function.prototype.bind = function(context, ...bindArgs) {
        context = context === undefined || context === null ? window : Object(context)
        return (...args) => this.apply(context, [...bindArgs, ...args])
    }

写在最后

面试造轮子? 不,这不叫造轮子,一个重复的轮子公司是否有需要你来写的必要? 而且放着社区这么多优秀作品不用,难道相信我们临时写的轮子么,公司也怕翻车。

包括我这里写的方法肯定有所遗漏(希望大家及时指正),这里真的只是为了考查你的js基础与编程思想。

所以大部分公司的面试题自有道理,希望大家少点吐槽,多学习下基础吧。(无力吐槽脸.jpg)