【譯】function.caller 被認為是有害的

鈞嘢嘢發表於2018-03-08

今天我收到來自微軟的 Patrick Kettner 提的這個問題,然而我發現這個問題是我已經回答過的,只不每次的問題稍有不同而已。

Snipaste_2018-03-05_14-09-37.png

最終我發現是自己在第一次看到這個問題的時候理解錯了這個問題,並且當別人在 Twitter 上回應的時候我也沒有足夠重視這個問題。

Snipaste_2018-03-05_14-10-35.png

最後 Patrick 又提醒我一次,我才發現引起他興趣的並不是 arguments.caller,而是函式物件的 "caller" 這個神祕的屬性 ——— 準確來說是非嚴格模式下的函式物件。

JavaScript 在歷史上曾提供了一個有魔力的 foo.caller 屬性,它可以返回撥用 foo 函式的引用。使用該屬性存在著眾多問題,例如它可能會因跨域呼叫產生安全問題、它在複雜的 JavaScript 引擎中實現的不夠充分、它難以維護和測試、諸如對閉包的內聯插入,逃逸分析和標量替換的優化都變得不可行,甚至在呼叫 "caller" 的屬性訪問器時,這些優化在返回的呼叫函式中也無法實現。


很多不可思議的事在非嚴格模式函式中都被限制了。嚴格模式下函式通過 AddRestrictedFunctionProperties 定義 "caller" 的訪問器,當訪問該屬性的時候會丟擲一個型別錯誤。

【譯】function.caller 被認為是有害的

對於非嚴格模式的函式,目前 EcmaScript 規格中的定義也是非常模糊的,基本上對它沒有做任何的規範限制。在章節 16.2 禁止擴充套件中說到:

如果擴充套件非嚴格模式或內建函式物件的時候,將物件自己的屬性命名為 "caller" ,並且它的值通過 [[Get]] 或者 [[GetOwnProperty]] 定義的話,這種情況下必須保證不是嚴格模式。如果它是作為一個訪問器屬性,通過 [[Get]] 屬性獲取它的值將會返回撥用它的函式,那麼這個時候不會返回嚴格模式下的函式。

所以在非嚴格模式函式下的 "caller" 屬性,或多或少完全實現了既定的行為。唯一的限制是如果有 yield 一個變數,那麼這個變數一定不是嚴格模式下的函式。所以在非嚴格模式下,給 "caller" 賦一個預設值 42 是一個合理做法。顯然實現中並沒有這麼做 —— 儘管有把這個新增到 V8 中的想法,同時現在也極不建議大家使用 foo.caller。


這是我們目前如何在 V8 中實現這些(有誤導性的)特性 —— 也正是如何在 Chrome 和 Node.js 中執行的。"caller" 這個屬性在非嚴格模式函式中是一個特殊的訪問器,其實現方法 FunctionCallerGetter 在 accessors.cc 原始碼檔案中實現,同時在該檔案實現的還有核心的邏輯方法 FindCaller。要理解下面這些規則可以說是比較困難的,但這就是當你在非嚴格模式下訪問 foo.caller時我們底層程式碼所做的事:

  1. 首先找到函式 foo 的最近一次的呼叫,例如 foo 的最後一次還沒返回給呼叫方的呼叫。
  2. 如果當前 foo 不存在被呼叫的情況,則立即返回 null。
  3. 如果處於正被呼叫的情況,我們通過檢視非使用者層的 JavaScript 程式碼的呼叫情況,找到它的上級調。
  4. 如果通過上述規則沒有找到上級呼叫,我們直接返回 null。
  5. 如果能找到上級呼叫,如果它是嚴格模式的函式或者是我們不需要訪問的 —— 例如來自不同域的函式 —— 這種情況下我們也返回 null。
  6. 否則的話,我們則返回上級呼叫的閉包。

這裡給出了一個它們如何工作的簡單例子:

【譯】function.caller 被認為是有害的

現在你對 foo.caller 是怎麼工作已經有了一個基本的瞭解,這裡我強烈建議你不要再使用它。正如上述所說的,它基本上是一個不能保證完全實現的特性。我們目前仍然會提供支援,但對於 arguments.caller,正如在 crbug.com/691710 提到的一樣,我們可能在某個時間會移除它 —— 因為我們希望能夠對閉包做逃逸分析和標量替換 —— 所以不要依賴它 —— 同時顯然其他 JavaScript 引擎或許根本不支援這種特性。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章