記一次面試題——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)

相關文章
相關標籤/搜索