記一次面試題——call、apply、bind模擬實現的更好方式

ZoenLeo發表於2019-01-18

寫在前面

看有人分享的某公司面試題,用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)

相關文章