在面試的過程當中,相信好多朋友都經歷過一些百思不得其姐的題目,或難題,或怪題,或偏題。今天我們一塊兒來看一道相對偏、但其實又很基礎的面試題。這道題是個人一個哥們兒,在半年前去面試字節跳動廣州分公司的時候遇到的。他說當時不會作,回來後和分享的(實際上是請教嘿嘿嘿~)。node
做爲專業的切圖仔,咱們對於 Function.prototype.call
方法確定不陌生(後面簡寫成 Function#call
) ,它的做用是用來指定 this
對象的,或者也能夠通俗一點說,是用來讓一個對象 借用
另一個對象的某個方法的。好比數組有個方法叫作 Array#push
, 而後咱們本身發明了一個假數組(是指有 length
屬性的對象), 咱們想要借用真數組的 push
方法來添加元素,可能會寫出如下代碼:面試
const arrayLike = { length: 0 } [].push.call(arrayLike, 1) console.log(arrayLike); // {0: 1, lenght: 1} 複製代碼
第 4 行中就用到了 Funtion#call
方法。由於它能讓一個不擁有某個方法的對象,去借用其餘對象的方法來調用 ,因此在不少庫和框架源碼中的出鏡率都是很是高的。chrome
OK, Function#call
方法的介紹完畢。下面就來看看今日頭條的面試官小哥當初到底問了個什麼問題,居然讓個人哥們兒百思不得其姐?數組
題目是這樣的:瀏覽器
如下代碼是能夠正常執行的:bash
const arrayLike = { length: 0 } [].push.call(arrayLike, 1); console.log(arrayLike); // {0: 1, lenght: 1} 複製代碼
可是把若是把 [].push.call
方法賦值到變量後再調用 ,在 chrome 卻會報錯:服務器
const arrayLike = { length: 0 } const call = [].push.call; call(arrayLike, 1); console.log(arrayLike); 複製代碼
請問這是什麼緣由呢?markdown
把 Function#call
賦值到變量再調用,以前確實沒有這樣使用過,可是直覺告訴我這段代碼是有些問題,因而立馬在 chrome 的控制檯看看執行結果:app
啊!call is not a function
? 這是在逗我嘛?因而趕忙用 typeof
操做符來驗證一下究竟是不是 function
框架
此時此刻真想罵人了,有木有? 調用 call()
會報錯 call is not a function
, 而使用 typeof
檢測 call
的類型倒是 function
! 你說氣不氣人?
冷靜冷靜!面試遇到不懂的問題必定要冷靜,若是緊張就真的不知道怎麼辦,只能涼拌了。那冷靜又能怎麼辦呢?冷靜以後, 就要嘗試從從實現原理或源碼來尋找思路。
下面就根據 Function#call
基本用法,來本身實現一個叫作 Function#myCall
的方法。
Function.prototype.myCall = function(context, ...args) { context = context || {}; context.fn = this; const res = context.fn(...args); delete context.fn; return res; } 複製代碼
下面來驗證一下 myCall
的功能:
const arrayLike = { length: 0 } [].push.myCall(arrayLike, 1) console.log(arrayLike); // {0: 1, lenght: 1} 複製代碼
OK, 是能夠正常工做了。
而後,若是把 [].push.myCall
賦值到變量再調用會怎樣呢?會不會也跟原生的 call
同樣報錯?以下:
const arrayLike = { length: 0 } const myCall = [].push.myCall; myCall(arrayLike, 1); console.log(arrayLike); 複製代碼
在 chrome 中執行結果以下:
錯誤信息是 context.fn is not a function
, 從源碼中咱們能夠看到 context.fn
實際上就是 this
, 由於源碼中有 context.fn = this
的賦值語句。 那這個 this
究竟是什麼呢?若是在瀏覽器端就是 window
, 若是在 node 端就是 global
,由於他們確實都不是 function
類型。因此調用就會報錯。
好聽,到此你們應該已經明白了爲何 Functio#call
不能先賦值給變量後再調用了。
可是再來看另一個問題,chrome 的錯誤信息 call is not function
, 實際上是很是不友好的錯誤信息,甚至是自相矛盾的。由於他說 call
變量的值不是 function
, 而用 typeof
檢測倒是 function
。因此這道題目的自己其實並不難,而是被 chrome
的極不友好的錯誤信息給誤導了。
下面讓咱們來看看其餘瀏覽器或 node 端的錯誤信息:
IE11 瀏覽器
edge 瀏覽器:
firefox 瀏覽器
node 服務器
對比以上不一樣的瀏覽器,會發現,node 服務器 和 chrome 瀏覽器的錯誤信息是同樣的,都是 call is not a function
, 這是由於他們都用了 v8 解釋器的緣由。而 IE11 和 edge 的錯誤信息也基本同樣,都是 this is not a Function object
。 而 firefox 的錯誤信息是 Function.prototype.call called on incompatible undefined
,這是什麼意思呢??我也不知道,因而悄悄的點擊了錯誤信息右邊的 [詳細瞭解]
,而後打開了 mdn 上關於 X.prototype.y called on incompatible type 的詳情頁面, 裏面有對這種錯誤的詳細介紹,並列舉出了好比 Funtion#call
, Function#apply
, Function#bind
等, 使用不當都會發生這樣的錯誤。
同一個錯誤在不一樣的瀏覽器上卻出現了五花八門的報錯信息,能夠看出優雅的報錯信息也是很難的, 關於 v8 引擎的 issue 6513 就是專門作這件事的。這個 issue 裏有人提議:
var c = Function.prototype.call; c(); Uncaught TypeError: c is not a function 複製代碼
Should be something like:
c is called with undefined as a context which is not a function 複製代碼
或許改爲提議那樣確實會優雅一些。
另外,若是咱們在平時工做中或面試中,遇到一些百思不得其姐的問題,不要憑空去猜想,要透過本質和原理去分析問題的根源。
標題只是開玩笑的啦,若是你會作這道題目不必定能進頭條,哈哈哈。可是若是不會作,就說明基本功不夠紮實,就頗有可能過不了面試啦。