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

写在前面

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

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

不少人表示不理解,为何面试题总出这些看上去毫无做用的实现。编程

面试题的意义

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

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

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

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

醒醒吧,兄弟们,都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)

相关文章
相关标签/搜索