:Separate 與 Mixed 兩種模式的原理與實(shí)戰(zhàn))
NW.js 中的 JavaScript 上下文JavaScript ContextsSeparate 與 Mixed 兩種模式的原理與實(shí)戰(zhàn)【免費(fèi)下載鏈接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.項(xiàng)目地址: https://gitcode.com/gh_mirrors/nw/nw.js本文深入解析 NW.js 的 JavaScript ContextJavaScript 上下文體系為什么不同窗口/框架各自擁有獨(dú)立的全局對象NW.js 如何在瀏覽器上下文之外引入 Node 上下文以及 Separate Context Mode分離上下文模式與 Mixed Context Mode混合上下文模式兩種架構(gòu)的差異、啟用方式和適用場景。讀完本文你將掌握 NW.js 中require()、global、process、Buffer等對象在不同上下文中的行為差異理解instanceof跨上下文失效的根因并學(xué)會編寫跨上下文可靠的類型檢查代碼。什么是 JavaScript ContextJavaScript Context上下文指一段腳本運(yùn)行時(shí)所處的環(huán)境。運(yùn)行在不同窗口中的腳本各自處于不同的 JavaScript 上下文中每個(gè)窗口擁有自己的全局對象global object以及自己的一套全局構(gòu)造函數(shù)如Array、Object、Date、Function。這是所有 Web 瀏覽器普遍遵循的設(shè)計(jì)也是一種保護(hù)機(jī)制原型污染被隔離當(dāng)某個(gè)庫例如 Prototype.js 這類框架或一段腳本替換、擴(kuò)展了某個(gè)對象的原型時(shí)其他窗口中的同類對象不受影響全局污染被限制當(dāng)程序員寫錯(cuò)代碼例如構(gòu)造器調(diào)用時(shí)漏寫new錯(cuò)誤雖然會污染當(dāng)前全局作用域卻無法波及更大范圍其他窗口安全隔離惡意應(yīng)用無法訪問其他窗口中的機(jī)密數(shù)據(jù)結(jié)構(gòu)。當(dāng)一個(gè)腳本訪問另一個(gè)上下文中定義的對象/函數(shù)時(shí)JS 引擎會臨時(shí)進(jìn)入目標(biāo)上下文執(zhí)行執(zhí)行完畢后再離開。這個(gè)進(jìn)入—離開機(jī)制是理解后續(xù)所有上下文問題的前提。NW.js 中的上下文架構(gòu)NW.js 基于 Chrome Apps 的架構(gòu)構(gòu)建應(yīng)用啟動時(shí)會自動加載一個(gè)不可見的 background page后臺頁面每當(dāng)創(chuàng)建一個(gè)新窗口時(shí)同時(shí)也會創(chuàng)建一個(gè)新的 JavaScript 上下文。在此基礎(chǔ)上NW.js 引入 Node 運(yùn)行時(shí)使 Node.js 模塊可以在不同位置加載默認(rèn)行為下Node 模塊運(yùn)行在 background page 的上下文中在 Mixed Context Mode混合上下文模式下Node 模塊可以運(yùn)行在每個(gè)窗口或 frame框架的上下文中。兩種模式的差異詳見下文。Separate Context Mode分離上下文模式默認(rèn)情況下除了瀏覽器自身創(chuàng)建的上下文NW.js 還為運(yùn)行 Node 模塊額外引入了一個(gè) Node ContextNode 上下文運(yùn)行于后臺頁面。因此 NW.js 中存在兩種類型的 JavaScript 上下文Browser Context瀏覽器上下文運(yùn)行傳統(tǒng) Web 腳本Node ContextNode 上下文運(yùn)行 Node.js 模塊。Web Worker 提示在 Manifest 中添加chromium-args: --enable-node-worker即可在 Web Worker 中訪問 Node.js API。Browser Context瀏覽器上下文加載腳本的方式凡是通過傳統(tǒng) Web 方式加載或內(nèi)嵌的腳本都運(yùn)行在 Browser Context 中典型方式包括使用script元素使用 jQuery 的$.getScript()使用 RequireJS 等模塊加載器。全局對象Browser Context 中可使用兩類全局對象JS 內(nèi)置對象builtin objects如Date、Error、TypedArray等Web API如 DOM API、XMLHttpRequest等。創(chuàng)建新的 Browser Context不同窗口window和不同 frame框架擁有各自獨(dú)立的上下文。因此當(dāng)你創(chuàng)建一個(gè)新的 frame 或 window 時(shí)就得到一個(gè)新的 Browser Context。在 Browser Context 中訪問 Node.js 與 NW.js APINW.js 會把 Node Context 的部分對象拷貝到 Browser Context 中使運(yùn)行在 Browser Context 的腳本可以直接訪問對象說明nw所有 NW.js API 的集合對象對應(yīng) References 文檔中的 API 列表globalNode Context 的全局對象等價(jià)于nw.globalrequire加載 Node.js 模塊的require()函數(shù)與nw.require()類似但額外支持用require(nw.gui)加載 NW.js API 模塊processNode.js 的 process 模塊等價(jià)于nw.processBufferNode.js 的 Buffer 類這一拷貝邏輯可以在源碼 src/nw_content.cc 中看到ContextCreationHook中構(gòu)造了一個(gè)包含global、process、Buffer、require四個(gè)符號的數(shù)組逐一從 Node 全局對象取值并寫入nw對象。而 src/resources/nw_pre13_shim.js 則在檢測到nw對象存在后為瀏覽器上下文建立require代理專門處理require(nw.gui)的情況并在--mixed-context下將process、Buffer、global從nw拷貝到瀏覽器全局作用域。Browser Context 中require()的相對路徑解析Browser Context 中的相對路徑按照主 HTML 文件的路徑解析與所有瀏覽器的行為一致。Node ContextNode 上下文加載腳本的方式以下方式加載的腳本運(yùn)行在 Node Context通過 Node.js 的require()API 加載的腳本通過 Manifest 中的node-main字段指定的腳本。根據(jù) Manifest Format 文檔node-main指向一個(gè) node.js 腳本文件該腳本會在啟動時(shí)、第一個(gè) DOM 窗口加載之前于 Node Context 中執(zhí)行字符串中文件路徑之前的參數(shù)會被當(dāng)作 Node.js 命令行參數(shù)處理。全局對象Node Context 中的腳本可以使用與 Browser Context 相同的 JS 內(nèi)置對象此外還可以使用 Node.js 定義的全局對象例如__dirname、process、Buffer等。注意Node Context 中沒有 Web API。如何在 Node Context 中使用瀏覽器 API見下文訪問瀏覽器與 NW.js API。創(chuàng)建新的 Node Context在 Separate Context Mode 下所有 Node 模塊共享同一個(gè) Node Context。如果需要新的 Node Context有以下兩種途徑調(diào)用Window.open()創(chuàng)建窗口時(shí)將new_instance選項(xiàng)設(shè)為true以--mixed-context命令行選項(xiàng)啟動 NW.js切換到 Mixed Context 模式見下文。訪問瀏覽器與 NW.js APINode Context 中沒有瀏覽器側(cè)或 NW.js API例如alert()、document.*、nw.Clipboard等。要訪問瀏覽器 API必須把相應(yīng)對象如window對象作為參數(shù)傳給 Node Context 中的函數(shù)。例如運(yùn)行在 Node Context 中的腳本myscript.js// el should be passed from browser context exports.setText function(el) { el.innerHTML hello; };在瀏覽器側(cè)index.html調(diào)用div idel/div script var myscript require(./myscript); // pass the el element to the Node function myscript.setText(document.getElementById(el)); // you will see hello in the element /scriptNode Context 中的window對象Node Context 中存在一個(gè)window對象它指向 background page 的 DOM window 對象。Node Context 中require()的相對路徑解析Node 模塊中的相對路徑按照該模塊自身的路徑解析與 Node.js 一貫的行為一致。Mixed Context Mode混合上下文模式Mixed Context混合上下文自 NW.js 0.13 引入。當(dāng)使用--mixed-context命令行選項(xiàng)參見 Command Line Options啟動 NW.js 時(shí)每次創(chuàng)建 Browser Context 的同時(shí)會創(chuàng)建一個(gè)新的 Node Context且兩者運(yùn)行在同一個(gè)上下文中即 Mixed Context。啟用方式在啟動 NW.js 時(shí)追加--mixed-context或者將其加入 Manifest 文件的chromium-args字段。根據(jù) Manifest Format 文檔chromium-args以字符串形式指定 Chromiumcontent shell命令行參數(shù)多個(gè)參數(shù)用空格分隔可用單引號將多個(gè) flag 包在一個(gè)參數(shù)中。此外--mixed-context也可以只作用于單個(gè)新窗口調(diào)用nw.Window.open(url, options)時(shí)同時(shí)設(shè)置new_instance: true與mixed_context: true見 Window 文檔。mixed_context選項(xiàng)的含義是在新窗口的進(jìn)程中合并 Node 上下文與 DOM 上下文僅在new_instance為true時(shí)可用。這一點(diǎn)在源碼 src/resources/api_nw_newwin.js 中有硬性校驗(yàn)若mixed_context為true而new_instance不為true會直接拋出錯(cuò)誤mixed_context should be set with new_instance in nw.Window.open。腳本行為與全局對象在 Mixed Context Mode 下無論通過 Web 方式script等還是通過require()加載的腳本都運(yùn)行在同一個(gè)上下文中。因此Node 模塊中可以直接使用全部瀏覽器 API 和 NW.js API反之亦然。完整的示例——先在 Manifest 中啟用 Mixed Context{ name: test-context, main: index.html, chromium-args: --mixed-context }myscript.jsNode 模塊exports.createDate function() { return new Date(); }; exports.showAlert function() { alert(Im running in Node module!); };index.htmlscript var myscript require(./myscript); console.log(myscript.createDate() instanceof Date); // true myscript.showAlert(); // Im running in Node module! /script上面的比較在 Mixed Context 下會成功myscript.createDate()返回的Date與頁面中的Date是同一個(gè)構(gòu)造器instanceof為trueNode 模塊內(nèi)也能直接調(diào)用alert()。與 Separate Context 的對比Separate Context Mode 的優(yōu)勢不會遇到下文多上下文協(xié)作問題中大量的類型檢查type checking問題Mixed Context Mode 的劣勢不同上下文之間不再容易共享變量。要在上下文之間共享變量應(yīng)把變量放到一個(gè)可被各方共同訪問的公共上下文中或者使用window.postMessage()API 在上下文間收發(fā)消息。從源碼實(shí)現(xiàn)看src/nw_content.cc 的ContextCreationHook在創(chuàng)建上下文時(shí)讀取 Manifest 中的mixed_context標(biāo)記當(dāng) Node 上下文尚未初始化或處于混合模式時(shí)會直接在 DOM 上下文內(nèi)啟動 Node 實(shí)例非混合模式下則通過SetAlignedPointerInEmbedderData(NODE_CONTEXT_EMBEDDER_DATA_INDEX, ...)與安全令牌Security Token的設(shè)置來建立 Browser Context 與共享 Node Context 之間的關(guān)聯(lián)。這也解釋了為什么混合模式每個(gè)窗口各自擁有獨(dú)立的 Node 運(yùn)行時(shí)而分離模式下所有窗口共享后臺頁面的同一個(gè) Node 運(yùn)行時(shí)。多上下文協(xié)作問題Working with Multiple Contexts上下文的隔離總體上是有益的但有時(shí)也會在代碼中引發(fā)問題需要尋找變通方案。最典型的是不同 Browser Context 中的全局對象并不相同一些類型檢查技巧在多上下文場景下會失效。iframe idmyframe srcmyframe.html/iframe script // window is the global object of current browser context // myframe.contentWindow is the global object of the iframes browser context var currentContext window; var iframeContext document.getElementById(myframe).contentWindow; // myfunc is defined in current context function myfunc() { } console.log(currentContext.Date iframeContext.Date); // false console.log(currentContext.Function iframeContext.Function); // false console.log(myfunc instanceof currentContext.Function); // true console.log(myfunc instanceof iframeContext.Function); // false console.log(myfunc.constructor currentContext.Function); // true console.log(myfunc.constructor iframeContext.Function); // false /scriptinstanceof的問題這類問題最常見的根源是 JavaScript 中instanceof運(yùn)算符的行為。someValue instanceof someConstructor檢查的是對象someValue的原型鏈中是否包含給定構(gòu)造函數(shù)someConstructor的prototype屬性。如果someValue來自另一個(gè) JavaScript 上下文它的祖先對象鏈?zhǔn)仟?dú)立的一套someValue instanceof someConstructor的檢查必然失敗。例如一個(gè)簡單的someValue instanceof Array無法確定來自另一個(gè)上下文的變量值是否為數(shù)組。obj.constructor的問題直接檢查obj.constructor屬性會出現(xiàn)同樣的問題例如用someValue.constructor Array代替someValue instanceof Array時(shí)跨上下文同樣會得到錯(cuò)誤結(jié)果。obj.__proto__的問題遺留的obj.__proto__可以讓你直接訪問對象的原型。但拿它的 constructor 與全局對象比較或像上面一樣使用instanceof依然會得到錯(cuò)誤結(jié)果。第三方庫中的問題第三方庫可能使用上述有問題的類型檢查方式從而引發(fā)難以排查的詭異錯(cuò)誤。一旦發(fā)生這通常是第三方庫自身的 bug建議向庫的作者提交 bug 報(bào)告或自行修復(fù)??缟舷挛牡目煽款愋蜋z查預(yù)防上下文相關(guān)問題的核心原則是當(dāng)某個(gè)值可能來自另一個(gè) JavaScript 上下文時(shí)避免使用instanceof。判斷是否為數(shù)組使用Array.isArray()方法它跨上下文也能可靠工作判斷依賴上下文的全局對象類型如Function、Date等可以用如下技巧檢查實(shí)際類型// test a function Object.prototype.toString.apply(someValue) [object Function] // test a Date Object.prototype.toString.apply(someValue) [object Date]如果上述便捷的替代方法不可用或問題出在他人而非你自己的代碼中、修復(fù)成本過高就需要另一種變通方案可以使用nwglobal——它返回 Node Context 中的全局對象在部分場景下可以繞開類型檢查問題??偨Y(jié)NW.js 的 JavaScript 上下文體系可以概括為瀏覽器上下文 Node 上下文的雙軌架構(gòu)默認(rèn)的 Separate Context Mode 中所有 Node 模塊共享后臺頁面里的同一個(gè) Node Context瀏覽器腳本通過被拷貝進(jìn)來的nw、global、require、process、Buffer訪問 Node 能力而--mixed-context或nw.Window.open中的mixed_context: true則把兩者合二為一讓 Node 模塊直接使用 Web API。理解兩種模式的取舍——分離模式更穩(wěn)、混合模式更直接——并根據(jù)項(xiàng)目實(shí)際選擇是寫出健壯 NW.js 應(yīng)用的關(guān)鍵。當(dāng)涉及跨上下文的類型判斷時(shí)請記住instanceof的局限性改用Array.isArray()或Object.prototype.toString等可靠手段?!久赓M(fèi)下載鏈接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.項(xiàng)目地址: https://gitcode.com/gh_mirrors/nw/nw.js創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考