寫在前面
看有人分享的某公司面試題,用js閉包實現call
、apply
、bind
。
這是我沒有考慮過的面試題,很好,它成功引起了我的興趣。
很多人表示不理解,為什麼面試題總出這些看上去毫無作用的實現。
面試題的意義
上次我寫了一個promise
模擬實現應該注意的點,也有人吐槽重複造輪子意義何在。
其實這就是理念的問題了,在一家重視基礎的公司裡,輪子不是會用就行,甚至你可以不需要會輪子,但是理念以及基礎一定要過關,不然你寫出來的隱患程式碼往往會給公司帶來未知的風險。
(想起了上月某男裝店網頁上的bug,應該被不少人薅了羊毛 ?。)
分享的兄弟寫出了自己的答案,解法中用了eval
、arguments
,忘記return
結果等等一些嚴重的問題。說實話,他給的答案我只能給5分(10分制),甚至讓我看著有點難受。
醒醒吧,兄弟們,都9012年了,我們是否可以用js的新特性實現呢?
實現思考
方法容錯:
一個合格的程式設計師,在寫方法的時候第一點應該想到的是做好容錯——當call
、apply
、bind
傳入第一個引數不是一個引用型別的時候,你應該要知道它的表現。
剩下的apply
、bind
測試結果一樣。
所以第一步:
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指向
終於可以進入主題了,無論我們用call
、apply
還是bind
,唯一的目的就是修改函式內部的this
指向。
仔細想想,在call
、apply
方法中我們拿到的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
,否則將在呼叫的時候報錯。
所幸的是我們已經在上文中實現了call
、 apply
函式,在這裡用上相得益彰,也表現出了你的封裝思想。
那麼修改一下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)