嚴(yán)格模式和非嚴(yán)格模式下的Function.caller

最近一個(gè)活動(dòng)項(xiàng)目中纫溃,在IOS的瀏覽器中會(huì)必現(xiàn)一個(gè)bug父能, 這個(gè)bug的起因是臣镣,我們?cè)谝粋€(gè)vue開(kāi)發(fā)的項(xiàng)目中,通過(guò)script方式引入了一個(gè)歷史有點(diǎn)久的動(dòng)畫(huà)庫(kù)谈山,通過(guò)eruda定位到問(wèn)題俄删,調(diào)用棧指向的就是這個(gè)動(dòng)畫(huà)庫(kù),具體報(bào)錯(cuò)信息即Function.caller used to retrieve strict caller。但是畴椰,為什么在PC上的chrome模擬器沒(méi)有這個(gè)bug臊诊,為什么不同瀏覽器的對(duì)于Function.caller這個(gè)API實(shí)現(xiàn)的差異這么大呢?基于此迅矛,我總結(jié)了一些經(jīng)驗(yàn)妨猩,如果你不幸也遇到這個(gè)問(wèn)題,希望可以參考這篇文章并獲得一些幫助秽褒。

Function.caller的表現(xiàn)跟嚴(yán)格模式和非嚴(yán)格模式是有區(qū)別的壶硅,在MDN可以看到定義:它會(huì)返回調(diào)用指定函數(shù)的函數(shù),在嚴(yán)格模式中禁止使用主要是因?yàn)槲舱{(diào)用優(yōu)化销斟。并且有一段警告:

Non-standard
This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

你可以在不同瀏覽器執(zhí)行這段代碼:

  function fun1() {
    console.log(arguments.callee.caller);
  }

  function fun2() {
    fun1();
  }

  function fun3() {
    'use strict';
    fun1();
  }

  fun2();
  fun3();

然后你會(huì)發(fā)現(xiàn)庐椒,正如 MDN 的警告所言,不同的引擎實(shí)現(xiàn)的差異非常大蚂踊,fun2都能正常執(zhí)行约谈,但是執(zhí)行fun3函數(shù)的時(shí)候,這是我的測(cè)試結(jié)果:

Safari12/JavaScriptCore:

    TypeError: Function.caller used to retrieve strict caller

Firefox63/SpiderMonkey:

    TypeError: access to strict mode caller function is censored
    
IE10/Trident:

    Accessing the 'caller' property of a function or arguments object is not allowed in strict mode

Chrome70/v8:
    不會(huì)報(bào)錯(cuò)犁钟,返回null

對(duì)于非嚴(yán)格模式的函數(shù)棱诱,EcmaScript對(duì)于caller的實(shí)現(xiàn)的定義非常模糊,Forbidden Extensions中涝动,有一段是這樣說(shuō)的:

If an implementation extends non-strict or built-in function objects with an own property named “caller” the value of that property, as observed using [[Get]] or [[GetOwnProperty]], must not be a strict function object. If it is an accessor property, the function that is the value of the property’s [[Get]] attribute must never return a strict function when called.

總結(jié)來(lái)說(shuō)迈勋,非嚴(yán)格模式函數(shù)的“caller”屬性唯一的限制是,如果它要產(chǎn)生一個(gè)值醋粟,那么該值不能是嚴(yán)格模式函數(shù)靡菇。這一點(diǎn)大部分js引擎實(shí)現(xiàn)的都還不錯(cuò)。

V8引擎嚴(yán)格模式為什么不報(bào)錯(cuò)米愿,而是返回NULL厦凤?

比較有趣的是,2017年還有人在V8項(xiàng)目下提過(guò)一個(gè)issue育苟,function.caller differs in behavior from other browsers (但這個(gè)其實(shí)不算issue)较鼓。

V8引擎開(kāi)發(fā)者之一 Benedikt Meurer 寫(xiě)過(guò)一篇文章
caller-considered-harmful,他有解釋當(dāng)你調(diào)用 foo.caller時(shí)宙搬, 在Chrome和Node.js中的工作原理:

(在理解下面這段話之前笨腥,最好先了解下什么是活動(dòng)對(duì)象,可以參考這篇文章)

  1. 我們?cè)噲D找到函數(shù)foo的最新活動(dòng)對(duì)象勇垛,即最后一次調(diào)用foo的且未return的調(diào)用者。
  2. 如果foo沒(méi)有當(dāng)前活動(dòng)對(duì)象士鸥,我們立即返回null闲孤。
  3. 如果有活動(dòng),我們會(huì)用一些奇技淫巧來(lái)查找到父活動(dòng)對(duì)象,一直會(huì)查詢(xún)到最頂級(jí)的非用戶(hù)JavaScript活動(dòng)對(duì)象的代碼讼积。
  4. 如果根據(jù)這些規(guī)則沒(méi)有父活動(dòng)肥照,我們返回null。
  5. 此外勤众,如果有父活動(dòng)對(duì)象舆绎,但它是嚴(yán)格模式函數(shù)或我們無(wú)法訪問(wèn)它,那么我們也返回null们颜。
  6. 其他情況吕朵,我們從父活動(dòng)對(duì)象中返回閉包。

V8的項(xiàng)目可以在github上找到窥突,FindCaller這個(gè)函數(shù)就是其caller的核心代碼努溃。根據(jù)這幾條規(guī)則我們已經(jīng)可以知道,在最開(kāi)始的例子中阻问,我們命中的是第5條規(guī)則梧税,父活動(dòng)對(duì)象是嚴(yán)格模式函數(shù),所以得到的結(jié)果是null称近。

MaybeHandle<JSFunction> FindCaller(Isolate* isolate,
                                   Handle<JSFunction> function) {
  FrameFunctionIterator it(isolate);
  if (function->shared()->native()) {
    return MaybeHandle<JSFunction>();
  }
  // Find the function from the frames. Return null in case no frame
  // corresponding to the given function was found.
  if (!it.Find(function)) {
    return MaybeHandle<JSFunction>();
  }
  // Find previously called non-toplevel function.
  if (!it.FindNextNonTopLevel()) {
    return MaybeHandle<JSFunction>();
  }
  // Find the first user-land JavaScript function (or the entry point into
  // native JavaScript builtins in case such a builtin was the caller).
  if (!it.FindFirstNativeOrUserJavaScript()) {
    return MaybeHandle<JSFunction>();
  }

  // Materialize the function that the iterator is currently sitting on. Note
  // that this might trigger deoptimization in case the function was actually
  // materialized. Identity of the function must be preserved because we are
  // going to return it to JavaScript after this point.
  Handle<JSFunction> caller = it.MaterializeFunction();

  // Censor if the caller is not a sloppy mode function.
  // Change from ES5, which used to throw, see:
  // https://bugs.ecmascript.org/show_bug.cgi?id=310
  if (is_strict(caller->shared()->language_mode())) {
    return MaybeHandle<JSFunction>();
  }
  // Don't return caller from another security context.
  if (!AllowAccessToFunction(isolate->context(), *caller)) {
    return MaybeHandle<JSFunction>();
  }
  return caller;
}

其他引擎拋出異常的解決方案

  1. 移除嚴(yán)格模式(不推薦)

用一些插件移除編譯之后的"use strict"第队,比如這個(gè)remove-strict-webpack-plugin,原理非常簡(jiǎn)單刨秆,就是替換掉"use strict"凳谦,但這種方式無(wú)疑是舍本逐末的方式。除非是歷史包袱太嚴(yán)重的項(xiàng)目坛善, 否則最好不要這么做晾蜘。

因?yàn)閲?yán)格模式有助于防止一些bug,并且它也有助于安全的使用 JS 眠屎。在 ES5 中, 嚴(yán)格模式是可選項(xiàng)剔交,但是在 ES6 中,許多特性要求必須使用嚴(yán)格模式改衩。 因此大多數(shù)開(kāi)發(fā)者和 babel 之類(lèi)的工具默認(rèn)添加 use strict 到 JS 文件的頭部岖常,以確保整個(gè) JS 文件的代碼都采用嚴(yán)格模式,這個(gè)習(xí)慣有助于我們寫(xiě)更好的 JS 葫督。

  1. 模擬caller方法(不推薦)
function getCallerName (){
    var callerName;
    try { throw new Error(); }
    catch (e) { 
        var re = /(\w+)@|at (\w+) \(/g, st = e.stack, m;
        re.exec(st), m = re.exec(st);
        callerName = m[1] || m[2];
    }
    return callerName;
}

用這種 hack 方式可以獲取到 caller的函數(shù)名竭鞍,但是跟原來(lái)的 caller 的語(yǔ)義是不同的,原來(lái)的 caller返回的是函數(shù)的引用橄镜。而且這個(gè)方法畢竟只是一個(gè) hack偎快,可能會(huì)隨著引擎的升級(jí)而失效。

  1. 禁用 caller

本來(lái)該屬性就不是ECMA-262第3版標(biāo)準(zhǔn)的一部分洽胶,只是大部分瀏覽器實(shí)現(xiàn)了它晒夹,但是大部分的實(shí)現(xiàn)又有各自的問(wèn)題,比如IE10中的in strict mode報(bào)錯(cuò)信息是錯(cuò)誤的。

所以丐怯,最好的解決方式就是不要去使用它喷好,如果之前的項(xiàng)目有用到,那就去改造它读跷,總會(huì)有不使用Function.caller也可以實(shí)現(xiàn)的方式梗搅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市效览,隨后出現(xiàn)的幾起案子无切,更是在濱河造成了極大的恐慌,老刑警劉巖钦铺,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件订雾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡矛洞,警方通過(guò)查閱死者的電腦和手機(jī)洼哎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沼本,“玉大人噩峦,你說(shuō)我怎么就攤上這事〕檎祝” “怎么了识补?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辫红。 經(jīng)常有香客問(wèn)我凭涂,道長(zhǎng),這世上最難降的妖魔是什么贴妻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任切油,我火速辦了婚禮,結(jié)果婚禮上名惩,老公的妹妹穿的比我還像新娘澎胡。我一直安慰自己,他們只是感情好娩鹉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布攻谁。 她就那樣靜靜地躺著,像睡著了一般弯予。 火紅的嫁衣襯著肌膚如雪戚宦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天锈嫩,我揣著相機(jī)與錄音阁苞,去河邊找鬼困檩。 笑死祠挫,一個(gè)胖子當(dāng)著我的面吹牛那槽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播等舔,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼骚灸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了慌植?” 一聲冷哼從身側(cè)響起甚牲,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝶柿,沒(méi)想到半個(gè)月后丈钙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡交汤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年雏赦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芙扎。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡星岗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戒洼,到底是詐尸還是另有隱情俏橘,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布圈浇,位于F島的核電站寥掐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏磷蜀。R本人自食惡果不足惜召耘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蠕搜。 院中可真熱鬧怎茫,春花似錦、人聲如沸妓灌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)虫埂。三九已至祥山,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掉伏,已是汗流浹背缝呕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工澳窑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人供常。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓摊聋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親栈暇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子麻裁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 小時(shí)候羨慕會(huì)畫(huà)畫(huà)的人煎源,但是一直沒(méi)有機(jī)會(huì)去學(xué),最近閑來(lái)無(wú)事臨摹了幾張圖香缺,原來(lái)自己也可以畫(huà)畫(huà)手销,畫(huà)的不好,大家勿噴图张!
    嚴(yán)雪琴閱讀 267評(píng)論 0 1
  • (對(duì)號(hào)入座一下哦锋拖!) 宮寒是女性衰老的“頭號(hào)殺手” 女人溫度你要懂,寒則老埂淮,溫才美姑隅! 道家最高...
    2121彤閱讀 172評(píng)論 0 0
  • 1 昨天早上出門(mén)取車(chē)讲仰,發(fā)現(xiàn)被另一車(chē)堵了,氣不打一處來(lái)痪蝇。十分鐘左右物管才通知車(chē)主挪車(chē)鄙陡。 一大早上,心情就不爽躏啰,十五分...
    杜痕遠(yuǎn)閱讀 589評(píng)論 7 3
  • 一晃四個(gè)月過(guò)去了趁矾,父親離開(kāi)我們已四月有余。每當(dāng)路上看到相似年紀(jì)的叔叔伯伯给僵,目光常忍不住追隨他們走遠(yuǎn)毫捣,真的真的...
    C勇敢做自己閱讀 254評(píng)論 0 0