你可能從未用過的 JavaScript 功能,可安全運行不可信的 JavaScript 代碼
最近我在調一個多 iframe 的妖孽應用時,撞見了一個被很多人忽略的 JavaScript 特性。它直接改寫了我對“代碼隔離”的全部認知。不是玄學,是工程學。
大多數開發者都知道:運行不可信的 JavaScript,很危險。然而,理解了這個特性之后我才發現——這些年我們在“瞎修”,用各種土辦法茍著過關。
你可能經歷過:為了跑第三方代碼做了個小功能,結果某個“熱心網友”把 Array.prototype.push 重寫了。——轟!你的應用全線崩壞。
這,就是 JavaScript Realm(領域) 要解決的問題。
接下來我想用最不廢話的方式告訴你:Realm 到底是什么、為什么比你想的還重要、以及你如何用它把應用變得更安全、更可預期。
當 JavaScript 環境彼此“串味兒”
想象一下,你在做一個插件系統,用戶可以寫自定義 JS 插件。聽起來多自由、多強大。
直到有一天,一個插件把 Array.prototype.push 覆蓋了。然后呢?你的整個世界觀(以及頁面)一起倒下。
再舉個例子:你在做模塊測試,每個用例都應該“白紙一張”,可上一個測試的狀態偏偏泄漏進來了。怎么都洗不干凈。
根子在這兒:所有代碼共享同一個全局環境。同一個 window、同一套內置原型、同一切。
然而,JavaScript 里有個很酷的機制,能把這鍋徹底端掉。
什么是 Realm:一間“新裝修的房子”,地板墻面全是新的
Realm 是 JavaScript 的“隔離執行環境”。
說人話:它給你一整套全新的內置對象與全局作用域。你創建一個 Realm,就會拿到“剛出廠”的 Array、Object、Error……所有內置對象,全部獨立、互不污染。
// 主 Realm(你的瀏覽器窗口)
console.log(Array); // [Function: Array]
// 創建一個 iframe(它會創建一個新的 Realm)
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
// iframe 里有它自己的 Array 構造器
const iframeArray = iframe.contentWindow.Array;
console.log(Array === iframeArray); // false
// 兩者都是 Array 構造器,但不是同一個對象第一次看到這個對比,真的會“哇——”。
主窗口里的 Array 與 iframe 里的 Array,長得一樣、能耐一樣,但就是兩個不同的對象,住在兩個不同的 Realm。
Realm 的底層構成:一套“整屋家電+軟裝”的拷貝
每當 JavaScript 執行時,它都得依賴一套內置對象與全局。Realm 就是這份“依賴套餐”。
一個 Realm 包含:
- 全局對象:瀏覽器里是
window,Node.js 里是global。 - 內置對象:
Array、Object、Function、Error、Map、Set、Promise等等。 - 內置函數:
parseInt、setTimeout、fetch等。 - 固有對象(Intrinsic):例如
Array.prototype、Object.prototype這些原型鏈上的基石。
因此,你寫代碼,永遠是在某個 Realm 里跑的。 而當你創建 iframe、Web Worker,或使用即將到來的 ShadowRealm API時,你就是在創建新的 Realm。
現代的 JS 沙箱:ShadowRealm,上桌了
ShadowRealm API 是 TC39 的一個提案(目前 Stage 3),它把“創建 Realm”這件事,第一次做成了標準化的一等公民。
與 iframe 不同,ShadowRealm 更輕量、跨平臺、不綁 DOM,專注做 JavaScript 代碼的隔離。
// 創建一個新的隔離 Realm
const realm = new ShadowRealm();
// 在隔離 Realm 中執行代碼
const result = realm.evaluate('2 + 2');
console.log(result); // 4
// 代碼完全隔離
realm.evaluate('const secretData = "hidden"');
console.log(typeof secretData); // undefined(主 Realm 訪問不到)Realm 內的代碼拿不到主 Realm 的全局對象。沒有 DOM、沒有 window,是實打實的“絕緣”。 因此,上面的插件污染問題,迎刃而解。
現在就能用嗎?能,但要分場景
1.ShadowRealm(實驗/探索)
ShadowRealm 還在 Stage 3,原生支持有限。你可以用類似 shadowrealm-api 的 polyfill 先玩起來:
import ShadowRealm from 'shadowrealm-api';
const realm = new ShadowRealm();
const result = realm.evaluate('2 + 2');
console.log(result); // 42.生產環境(立刻可用)
今天就想上生產?用 iframe + sandbox 屬性。
const frame = document.createElement('iframe');
frame.sandbox = 'allow-scripts allow-same-origin';
frame.style.display = 'none';
document.body.appendChild(frame);
frame.contentWindow.eval('console.log("Isolated code")');allow-scripts:允許腳本執行;allow-same-origin:同源場景下更靈活(但要按需評估安全邊界)。 簡而言之:夠用、穩妥、好落地。
什么時候用 Realm:不想背鍋的時候
- 運行不可信代碼:在線編輯器、代碼操場、用戶提交 JS 的任何場景。
- 插件系統:你的應用接第三方插件,彼此互不干擾,也別碰核心代碼。
- 隔離測試環境:每組測試都從干凈的桌面開始,狀態不串門。
因此,當“全局污染”“狀態泄漏”“可預測性”這些詞開始頻繁出現,Realm 就是你的“止疼片”。
未來走向:隔離,將成為默認選項
ShadowRealm 現在在 TC39 Stage 3。一旦在瀏覽器與 Node.js 著陸,我們就有了標準化、輕量的 Realm 創建方式。 與此同時,你完全可以現在就用 polyfill 預演:做個小型插件系統、寫個安全的代碼執行器,踩一踩邊界,心里更有數。
小結
- 問題本質:共享全局,必然串味兒。
- 解決之道:新的 Realm = 新的全局 + 新的內置對象。
- 能用什么:現在上
iframe + sandbox,探索用ShadowRealm。 - 收益:更安全、更可控、更可預測。——不是把風險“按下去”,而是從架構上“抽走”它。
謝謝你看到這里。下次我還想聊一個同樣“冷門但解渴”的 JavaScript 小心機。愿每一行代碼,都在屬于它的 Realm 里,規矩地活著。




























