javascript SpiderMonkey中的函數序列化如何進行_基礎知識
在Javascript中,函數可以很容易的被序列化(字串化),也就是得到函數的源碼.但其實這個操作的內部實現(引擎實現)並不是你想像的那麼簡單.SpiderMonkey中一共使用過兩種函數序列化的技術:一種是利用反編譯器(decompiler)將函數編譯後的字節碼反編譯成源碼字串,另一種是在將函數編譯成字節碼之前就把函數源碼壓縮並且儲存下來,用到的時候再解壓縮還原.
如何進行函數序列化
在SpiderMonkey中,能將函數序列化的方法或函數有三個:Function. prototype.toString,Function.prototype.toSource,uneval.只有toString方法是標準的,也就是各引擎通用的.但是ES標準中關於Function.prototype.toString方法的規定(ES5 15.3.4.2)只有寥寥數語,也就是說,基本上沒有標準,引擎自己決定該如何實現.
函數序列化的作用
函數序列化最主要的作用應該是利用序列化生成的函數原始碼重新定義這個函數.
function(
...
alert("a")
...
}
a() //執行時可能會彈出"a"
a = eval("(" a.toString().replace('alert("a")', 'alert("b")') ")")
a() //執行時可能會彈出"b"
你也許會想:"我寫了這麼多年Javascript,怎麼沒有遇到這種需求".的確,如果是自己的網站,自己完全控制的js文件,不需要以這種打補丁的方式來修改函數,直接修改就可以了.但是如果源文件不是你能控制的了的話,就很有可能要這樣做了.比如常用的地方有greasemonkey腳本:你可能需要停用或修改某個網站中的某個函數.還有就是Firefox擴充:你需要修改Firefox自身的某個函數(可以說Firefox是用JS寫的).舉個我自己寫的 Firefox腳本的範例:
location == " /browser/content/browser.xul" && eval("gURLBar.handleCommand=" gURLBar.handleCommand.toString().replace(/^s*(load. );/gm, "/^javascript:/.test(url )||(content.location=='about:blank'||content.location=='about:newtab')?$1:gBrowser.loadOneTab(url,{postData:postData,inBackground:false, allowThirdPartyFixup: true}) ;"))
這個代碼的作用是:在地址列上回車時,讓Firefox在新標籤中打開頁面,而不是佔用當前標籤.實現方式就是用toString方法讀取到gURLBar. handleCommand函數的原始碼,然後用正規替換後傳給eval,重新定義了這個函數.
為什麼不用直接定義的方式,也就是直接重寫函數呢:
gURLBar.handleCommand = function(){...//將原本的函數更改了一個小地方}
不能這麼做的原因是因為我們得考慮兼容性,我們應該盡可能小的更改這個函數的源碼.如果這麼寫的話,Firefox的gURLBar.handleCommand源碼一旦發生變化,這個腳本就失效了.比如Firefox3和Firefox4中都有這個函數,但函數內容差別非常大,可是如果用正則替換部分關鍵字的話,只要這個被替換的這個關鍵字沒有改變的話,就不會出現不相容的現象.
反編譯字節碼
在SpiderMonkey中,函數在被解析之後會被編譯成字節碼(bytecode),也就是說,內存中存儲著並不是原始的函數源碼.SpiderMonkey中存在一個反編譯器,它的主要作用就是把函數的字節碼反編譯成函數源碼的形式.
alert(function () {
"字串";
//註解
return 1 2 3
}.toString())
傳回的字串是
function () {
return 6;
輸出和其他的瀏覽器完全不同:
1.沒有意義的原始值字面量在編譯的時候會被刪除,這個例子中就是"字符串".
你也許會覺得:"似乎沒什麼問題,反正這些值對於函數的運行來說並沒有什麼意義".等等,你是不是忘了個東西,表示嚴格模式的字串"use strict"怎麼辦呢?
在不支援嚴格模式的版本中,比如Firefox3.6,這個"use strict"和其他字串沒什麼區別,編譯的時候會被刪除.在SpiderMonkey實現了嚴格模式之後,雖然編譯的時候同樣會忽略掉這個字符串"use strict",但在反編譯的時候會進行判斷,如果這個函數處於嚴格模式中,則會在函數體的第一行新增上"use strict",下面是對應的引擎原始碼.
static JSBool
複製程式碼
複製程式碼
複製程式碼
程式碼如下:
DecompileBody(JSPrinter *jp, JSScript *script, jsbytecode *pc)
{
/* Print a strict mode code directive, if needed. */
* if (script->strictModeCode && !jp->strict) {
if (jp->fun && (jp->fun->flags & JSFUN_EXPR_CLOSURE)) {
/*
* We have no syntax for strict function expressions;
*/ js_printf(jp, "t/* use strict */ n"); } else {
js_printf(>js_printf(>js_printf(>js_printf( jp, "t"use strict";n");
}
jp->strict = true;
這個貌似沒太大影響,不過有些人願意利用函數註解來實現多行字串,這個方法在Firefox 17之前的版本中是不可用的.
複製程式碼
function hereDoc(f) {
return f.toString().replace(/^. s { /,"").replace(/. $/,""); } var string = hereDoc(function () {/*
我
你
他

console.log(string)
我
你
反編譯的弊端
由於新技術的出現(例如嚴格模式)以及在修改其他相關bug的時候,反編譯器這部分的實現經常需要更改,更改就有可能產生新的bug,我自己就親身遇到過一個bug.大概是在Firefox10左右的時候,具體問題記不大清了,反正是關於反編譯時小括號是否要保留的問題,大概是這樣的:
複製程式碼
在反編譯時,(a b)中的小括號被省略了,由於加法結合律從左到右,所以這沒關係.但我遇到的bug是這樣的:
這就不行了,a b c不等於a (b c),比如在a=1,b=2,c="3"的情況下,a b c等於"33",而a (b c)等於"123 ".
關於反編譯器,Mozilla工程師Luke Wagner指出,反編譯器對他們實現一些新功能的阻礙很大,而且經常會出現一些bug:
Not to pile on , but I too have felt an immense drag from the decompiler in the last year. Testing coverage is also poor and any non-trivial change inevitably produces fuzz bugs.The sooner we remove this this the starter fuzz bugs.The sooner we remove this this the starter we 也, 零, this. I think now is a much better time to remove it than after doing significant frontend/bytecode hacking for new language features.
Brendan Eich也表示,反編譯器的確有很多不理想I have no love for the decompiler, it has been hacked over for 17 years. 存儲函數源碼
從Firefox17之後,SpiderMonkey改成了第二種實現方法,其他瀏覽器也應該是這樣實現的吧.函數序列化得到的字串完全和源碼一致,包括空白符,註解等等.這樣的話,大部分問題就應該沒有了吧.不過,貌似我又想到個問題.還是關於嚴格模式的.
如:
(function A() {
(function A() {
(function A() {
複製程式碼
程式碼如下:
但如果是這樣呢:
程式碼如下:
(function A() {
上面說了,Firefox17之前Firefox4之後的版本是通過判斷當前函數是否處於嚴格模式來決定輸出不輸出"use strict"的,函數B繼承了函數A的嚴格模式,所以會有"use strict".
同時函數源碼是縮進嚴格的,因為在反編譯的時候,SpiderMonkey會給反編譯出的源碼進行格式化,即使之前的源碼完全沒有縮進也沒關係:
複製程式碼
" use strict";
alert("B");
}
Firefox17之後的版本會不會帶有"use strict"呢?因為是直接把函數源碼保存下來的,而且函數B中的確沒有"use strict"字樣.試驗結果是:會添加上"use strict",只是縮進有點問題,因為沒有格式化這一步了.
複製程式碼
程式碼如下:
function B() {
// 我們也會在這個內部函數的函數體內插入"use strict".
// 這就確保了,如果這個函數的toString方法的回傳值被重新求值時,
// 重新產生的函數會和原函數有著相同的語意.
而不同的是,其他瀏覽器都是不帶" use strict"的: 複製程式碼 程式碼如下: function B() >alert("B") } 雖然這不會有什麼太大影響,但我覺的Firefox的實現是更合理的.

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

本文討論了在瀏覽器中優化JavaScript性能的策略,重點是減少執行時間並最大程度地減少對頁面負載速度的影響。

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

本文討論了使用瀏覽器開發人員工具的有效JavaScript調試,專注於設置斷點,使用控制台和分析性能。

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

本文說明瞭如何使用源地圖通過將其映射回原始代碼來調試JAVASCRIPT。它討論了啟用源地圖,設置斷點以及使用Chrome DevTools和WebPack之類的工具。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。
