看有人分享的某公司面試題,用js閉包實現call
、apply
、bind
。程序員
這是我沒有考慮過的面試題,很好,它成功引發了個人興趣。面試
不少人表示不理解,爲何面試題總出這些看上去毫無做用的實現。編程
上次我寫了一個promise
模擬實現應該注意的點,也有人吐槽重複造輪子意義何在。數組
其實這就是理念的問題了,在一家重視基礎的公司裏,輪子不是會用就行,甚至你能夠不須要會輪子,可是理念以及基礎必定要過關,否則你寫出來的隱患代碼每每會給公司帶來未知的風險。promise
(想起了上月某男裝店網頁上的bug,應該被很多人薅了羊毛 🐑。)閉包
分享的兄弟寫出了本身的答案,解法中用了eval
、arguments
,忘記return
結果等等一些嚴重的問題。說實話,他給的答案我只能給5分(10分制),甚至讓我看着有點難受。app
醒醒吧,兄弟們,都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)
}
複製代碼
終於能夠進入主題了,不管咱們用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)