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

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

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

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

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

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

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


不少難以想象的事在非嚴格模式函數中都被限制了。嚴格模式下函數經過 AddRestrictedFunctionProperties 定義 "caller" 的訪問器,當訪問該屬性的時候會拋出一個類型錯誤。github

對於非嚴格模式的函數,目前 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. 不然的話,咱們則返回上級調用的閉包。

這裏給出了一個它們如何工做的簡單例子:ide

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


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

相關文章
相關標籤/搜索