摘要:我們將用戶可控制的控制臺(tái)稱為虛擬控制臺(tái),以便將其與和頁(yè)面內(nèi)部的區(qū)分開(kāi)來(lái)。同樣,目前在中是不能夠處理相關(guān)信息的比如設(shè)置這樣做會(huì)導(dǎo)致虛擬控制臺(tái)發(fā)出,說(shuō)明此功能未實(shí)現(xiàn),并且沒(méi)有任何變化也將不會(huì)有新的或?qū)ο螅⑶椰F(xiàn)有對(duì)象仍保持當(dāng)前所有相同的屬性值。
jsdom是一個(gè)純粹由 javascript 實(shí)現(xiàn)的一系列 web標(biāo)準(zhǔn),特別是 WHATWG 組織制定的DOM和 HTML 標(biāo)準(zhǔn),用于在 nodejs 中使用。大體上來(lái)說(shuō),該項(xiàng)目的目標(biāo)是模擬足夠的Web瀏覽器子集,以便用于測(cè)試和挖掘真實(shí)世界的Web應(yīng)用程序。
最新版本的 jsdom 運(yùn)行環(huán)境需要 node.js v6或者更高的版本。(jsdom v10以下版本在 nodejs v4以下仍然可用,但是我們已經(jīng)不支持維護(hù)了)
v10版本的 jsdom 擁有全新的 API(如下所述).舊的 API 現(xiàn)在仍然支持;詳細(xì)的參照文檔
基本用法const jsdom = require("jsdom"); const { JSDOM } = jsdom;
為了使用 jsdom,主要用到j(luò)sdom主模塊的一個(gè)命名導(dǎo)出的 jsdom 構(gòu)造函數(shù)。往構(gòu)造器傳遞一個(gè)字符串,將會(huì)得到一個(gè) jsdom 構(gòu)造實(shí)例對(duì)象,這個(gè)對(duì)象有很多實(shí)用的屬性,特別是 window 對(duì)象:
const dom = new JSDOM(`Hello world
`); console.log(dom.window.document.querySelector("p").textContent); // "Hello world"
(請(qǐng)注意,jsdom會(huì)像瀏覽器一樣解析您傳遞的HTML,包括隱含的,和標(biāo)記)
生成的對(duì)象是JSDOM類的一個(gè)實(shí)例,其中包括 window 對(duì)象在內(nèi)的許多有用的屬性和方法。一般來(lái)說(shuō),它可以用來(lái)從“外部”對(duì)jsdom進(jìn)行操作,而這些操作對(duì)于普通DOM API來(lái)說(shuō)是不可能的。對(duì)于不需要任何功能的簡(jiǎn)單場(chǎng)景,我們推薦使用類似的編碼模式
const { window } = new JSDOM(`...`); // or even const { document } = (new JSDOM(`...`)).window;
下面是關(guān)于JSDOM類所能做的一切的完整文檔,在“JSDOM對(duì)象API”部分。
定制 jsdomJSDOM構(gòu)造函數(shù)接受第二個(gè)參數(shù),可以用以下方式定制您的jsdom。
簡(jiǎn)單選項(xiàng)const dom = new JSDOM(``, { url: "https://example.org/", referrer: "https://example.com/", contentType: "text/html", userAgent: "Mellblomenator/9000", includeNodeLocations: true });
url 設(shè)置的值可以通過(guò)window.location,document.URL和document.documentURI來(lái)返回,并會(huì)影響文檔中相關(guān)URL的解析以及獲取子資源時(shí)使用的同源限制和referrer。默認(rèn)值為"about:blank"。
referrer 僅僅影響document.referrer的值。默認(rèn)沒(méi)有引用(即為空字符串)。
contentType 影響document.contentType的值,是按照HTML解析文檔還是 XML來(lái)解析。它的值如果不是text/html或XML mime type 值的話將會(huì)拋出異常。默認(rèn)值為"text/html"。
userAgent 影響navigator.userAgent的值以及請(qǐng)求子資源時(shí)發(fā)送的User-Agent頭。默認(rèn)值為Mozilla / 5.0($ {process.platform})AppleWebKit / 537.36(KHTML,如Gecko)jsdom / $ {jsdomVersion}。
includeNodeLocations 保留由HTML解析器生成的位置信息,允許您使用nodeLocation()方法(如下所述)檢索它。
它還能確保在元素內(nèi)運(yùn)行的代碼的異常堆棧跟蹤中報(bào)告的行號(hào)是正確的。
默認(rèn)值為false以提供最佳性能,并且不能與XML內(nèi)容類型一起使用,因?yàn)槲覀兊腦ML解析器不支持位置信息。
請(qǐng)注意,url 和referrer在使用之前已經(jīng)被規(guī)范化了,例如
如果你傳入"https:example.com",jsdom會(huì)自動(dòng)規(guī)范化解釋為"https://example.com/"。
如果你傳遞了一個(gè)不可解析的URL,該調(diào)用將拋出錯(cuò)誤。
(URL根據(jù)URL標(biāo)準(zhǔn)進(jìn)行分析和序列化。)
jsdom最強(qiáng)大的功能是它可以在jsdom中執(zhí)行腳本。這些腳本可以修改頁(yè)面的內(nèi)容并訪問(wèn)jsdom實(shí)現(xiàn)的所有Web平臺(tái)API。
但是,這在處理不可信內(nèi)容時(shí)也非常危險(xiǎn)。
jsdom沙箱并不是萬(wàn)無(wú)一失的,在DOM的內(nèi)部運(yùn)行的代碼如果足夠深入,就可以訪問(wèn)Node.js環(huán)境,從而訪問(wèn)您的計(jì)算機(jī)。
因此,默認(rèn)情況下,執(zhí)行嵌入在HTML中的腳本的功能是禁用的:
const dom = new JSDOM(` `); // 腳本默認(rèn)將不能執(zhí)行: dom.window.document.body.children.length === 1;
要在頁(yè)面內(nèi)啟用腳本,可以使用runScripts:"dangerously"選項(xiàng):
const dom = new JSDOM(` `, { runScripts: "dangerously" }); // 腳本將執(zhí)行并修改 DOM: dom.window.document.body.children.length === 2;
我們?cè)俅螐?qiáng)調(diào)只有在提供給jsdom的代碼是你已知道是安全的代碼時(shí)方可使用它。如果您運(yùn)行了任意用戶提供的或Internet上的不可信的Node.js代碼,可能會(huì)危及您的計(jì)算機(jī)。
假如你想通過(guò)來(lái)執(zhí)行外部腳本,你需要確保已經(jīng)加載了它們。為此,請(qǐng)?zhí)砑舆x項(xiàng)resources:"usable" 如下所述。
請(qǐng)注意,除非runScripts設(shè)置為"dangerously",否則事件處理程序?qū)傩裕ㄈ?b>
如果您只是試圖從“外部”執(zhí)行腳本,而不是通過(guò)元素(和內(nèi)聯(lián)事件處理程序)從內(nèi)部運(yùn)行“,則可以使用runScripts: "outside-only"選項(xiàng),該選項(xiàng)會(huì)啟用window.eval:
const window = (new JSDOM(``, { runScripts: "outside-only" })).window; window.eval(`document.body.innerHTML = "Hello, world!
";`); window.document.body.children.length === 1;
由于性能原因,默認(rèn)情況下會(huì)關(guān)閉此功能,但可以安全啟用。
請(qǐng)注意,我們強(qiáng)烈建議不要試圖通過(guò)將jsdom和Node全局環(huán)境混合在一起(例如,通過(guò)執(zhí)行g(shù)lobal.window = dom.window)來(lái)“執(zhí)行腳本”,然后在Node全局環(huán)境中執(zhí)行腳本或測(cè)試代碼。相反,您應(yīng)該像對(duì)待瀏覽器一樣對(duì)待jsdom,并使用window.eval或runScripts: "dangerously"來(lái)運(yùn)行需要訪問(wèn)jsdom環(huán)境內(nèi)的DOM的所有腳本和測(cè)試。例如,這可能需要?jiǎng)?chuàng)建一個(gè)browserify包作為元素執(zhí)行 - 就像在瀏覽器中一樣。
最后,對(duì)于高級(jí)用例,您可以使用dom.runVMScript(腳本)方法,如下所述。
假裝成一個(gè)視覺(jué)瀏覽器jsdom沒(méi)有渲染可視內(nèi)容的能力,并且默認(rèn)情況下會(huì)像無(wú)頭瀏覽器一樣工作。它通過(guò)API(如document.hidden)向網(wǎng)頁(yè)提供提示,表明其內(nèi)容不可見(jiàn)。
當(dāng)pretendToBeVisual選項(xiàng)設(shè)置為true時(shí),jsdom會(huì)假裝它正在呈現(xiàn)并顯示內(nèi)容。它是這樣做的:
更改document.hidden以返回false而不是true
更改document.visibilityState以返回“visible”而不是“prerender”
啟用window.requestAnimationFrame() 和window.cancelAnimationFrame()方法,否則不存在
const window = (new JSDOM(``, { pretendToBeVisual: true })).window; window.requestAnimationFrame(timestamp => { console.log(timestamp > 0); });
請(qǐng)注意,jsdom仍然不做任何布局或渲染,因此這實(shí)際上只是假裝為可視化,而不是實(shí)現(xiàn)真正的可視化Web瀏覽器將實(shí)現(xiàn)的部分。
加載子資源默認(rèn)情況下,jsdom不會(huì)加載任何子資源,如腳本,樣式表,圖像或iframe。如果您希望jsdom加載這些資源,則可以傳遞resources: "usable"選項(xiàng),該選項(xiàng)將加載所有可用資源。資源列表如下:
frame 和 iframe,通過(guò) 和 實(shí)現(xiàn)
樣式,通過(guò)
腳本,通過(guò),但是前提是runScripts: "dangerously"設(shè)置了
圖片,通過(guò),但是前提是canvas(或者 canvas-prebuilt) npm 包已安裝
未來(lái),我們計(jì)劃通過(guò)此選項(xiàng)提供更多的資源加載定制,但現(xiàn)在只提供的兩種模式:"default"和 "usable"。
虛擬控制臺(tái)像網(wǎng)頁(yè)瀏覽器一樣,jsdom也具有“控制臺(tái)”的概念。通過(guò)在文檔內(nèi)執(zhí)行的腳本以及來(lái)自jsdom本身實(shí)現(xiàn)的信息和記錄會(huì)從頁(yè)面直接發(fā)送過(guò)來(lái)。我們將用戶可控制的控制臺(tái)稱為“虛擬控制臺(tái)”,以便將其與Node.js console API和頁(yè)面內(nèi)部的window.console API區(qū)分開(kāi)來(lái)。
默認(rèn)情況下,JSDOM構(gòu)造函數(shù)將返回一個(gè)具有虛擬控制臺(tái)的實(shí)例,該虛擬控制臺(tái)將其所有輸出轉(zhuǎn)發(fā)到Node.js控制臺(tái)。為了創(chuàng)建自己的虛擬控制臺(tái)并將其傳遞給jsdom,可以通過(guò)執(zhí)行下面代碼來(lái)覆蓋此默認(rèn)值
const virtualConsole = new jsdom.VirtualConsole(); const dom = new JSDOM(``, { virtualConsole });
這樣的代碼將創(chuàng)建一個(gè)沒(méi)有任何行為的虛擬控制臺(tái)。您可以為所有可能的控制臺(tái)方法添加事件偵聽(tīng)器來(lái)為其提供行為:
virtualConsole.on("error", () => { ... }); virtualConsole.on("warn", () => { ... }); virtualConsole.on("info", () => { ... }); virtualConsole.on("dir", () => { ... }); // ... etc. See https://console.spec.whatwg.org/#logging
(請(qǐng)注意,最好在調(diào)用 new JSDOM()之前設(shè)置這些事件偵聽(tīng)器,因?yàn)樵诮馕銎陂g可能會(huì)發(fā)生錯(cuò)誤或控制臺(tái)調(diào)用腳本錯(cuò)誤。)
如果你只是想將虛擬控制臺(tái)輸出重定向到另一個(gè)控制臺(tái),比如默認(rèn)的Node.js,你可以這樣做
virtualConsole.sendTo(console);
還有一個(gè)特殊的事件,"jsdomError",它的觸發(fā)將通過(guò)錯(cuò)誤對(duì)象來(lái)記錄jsdom本身的錯(cuò)誤。這與錯(cuò)誤消息在Web瀏覽器控制臺(tái)中的顯示方式類似,即使它們不是由console.error輸出的。到目前為止,錯(cuò)誤會(huì)按照下面的方式輸出:
加載或解析子資源時(shí)出錯(cuò)(腳本,樣式表,frames和iframe)
不是由window onerror事件處理程序處理的腳本執(zhí)行錯(cuò)誤,它將會(huì)返回true或調(diào)用event.preventDefault()
由于調(diào)用jsdom沒(méi)有實(shí)現(xiàn)的方法而導(dǎo)致的錯(cuò)誤,例如window.alert,兼容性的 web 瀏覽器都實(shí)現(xiàn)了這些方法
如果您使用sendTo(c)將錯(cuò)誤發(fā)送給c,則默認(rèn)情況下,它將使用來(lái)自"jsdomError"事件的信息調(diào)用console.error。如果您希望保持事件與方法調(diào)用的嚴(yán)格的一對(duì)一映射,并且可能自己處理"jsdomError",那么您可以執(zhí)行
virtualConsole.sendTo(c, { omitJSDOMErrors: true });Cookie jars(存儲(chǔ)Cookie的容器)
像網(wǎng)頁(yè)瀏覽器一樣,jsdom也具有cookie jar的概念,存儲(chǔ)HTTP cookie 。在文檔的同一個(gè)域上一個(gè)URL,并且沒(méi)有標(biāo)記為HTTP only的cookies,可以通過(guò)document.cookie API來(lái)訪問(wèn)。此外,Cookie jar中的所有cookie都會(huì)影響子資源的http加載。
默認(rèn)情況下,JSDOM構(gòu)造函數(shù)將返回一個(gè)帶有空cookie的實(shí)例。要?jiǎng)?chuàng)建自己的cookie jar并將其傳遞給jsdom,可以通過(guò)以下代碼來(lái)覆蓋默認(rèn)值
const cookieJar = new jsdom.CookieJar(store, options); const dom = new JSDOM(``, { cookieJar });
如果您想要在多個(gè)jsdoms中共享同一個(gè)cookie jar,或者提前使用特定的值來(lái)填充cookie jar,這將非常有用。
Cookie jar包由tough-cookie包提供的。jsdom.CookieJar構(gòu)造函數(shù)是tough-cookie cookie jar的子類,并且默認(rèn)設(shè)置了looseMode:true選項(xiàng),因?yàn)樗蠟g覽器的行為方式。如果您想自己使用tough-cookie的方法和類,則可以使用jsdom.toughCookie模塊導(dǎo)出來(lái)訪問(wèn)使用jsdom打包的tough-cookie模塊實(shí)例。
在解析之前進(jìn)行干預(yù)jsdom允許您在很早的時(shí)候介入創(chuàng)建jsdom:創(chuàng)建Window和Document對(duì)象之后,但在解析任何HTML并使用節(jié)點(diǎn)填充文檔之前:
const dom = new JSDOM(`Hello
`, { beforeParse(window) { window.document.childNodes.length === 0; window.someCoolAPI = () => { /* ... */ }; } });
如果您希望以某種方式修改環(huán)境,這尤其有用,例如添加jsdom不支持的Web API的填充程序。
JSDOM object API一旦你構(gòu)建了一個(gè)JSDOM對(duì)象,它將具有以下有用的功能:
Propertieswindow屬性: window對(duì)象的key 從Window 對(duì)象檢索而來(lái)
virtualConsole和cookieJar:可以傳入或者使用默認(rèn)值
const dom = new JSDOM(`hello`); dom.serialize() === "hello"; // Contrast with: dom.window.document.documentElement.outerHTML === "hello";通過(guò)nodeLocation(node)獲取 dom 節(jié)點(diǎn)的源位置信息
nodeLocation()方法將查找DOM節(jié)點(diǎn)在源文檔中的位置,并返回節(jié)點(diǎn)的parse5位置信息:
const dom = new JSDOM( `Hello
`, { includeNodeLocations: true } ); const document = dom.window.document; const bodyEl = document.body; // implicitly created const pEl = document.querySelector("p"); const textNode = pEl.firstChild; const imgEl = document.querySelector("img"); console.log(dom.nodeLocation(bodyEl)); // null; it"s not in the source console.log(dom.nodeLocation(pEl)); // { startOffset: 0, endOffset: 39, startTag: ..., endTag: ... } console.log(dom.nodeLocation(textNode)); // { startOffset: 3, endOffset: 13 } console.log(dom.nodeLocation(imgEl)); // { startOffset: 13, endOffset: 32 }![]()
請(qǐng)注意,只有您設(shè)置了includeNodeLocations選項(xiàng)才能使用此功能;由于性能原因,節(jié)點(diǎn)位置默認(rèn)為關(guān)閉。
使用runVMScript(script)運(yùn)行vm創(chuàng)建的腳本Node.js的內(nèi)置vm模塊允許您創(chuàng)建Script實(shí)例,這些腳本實(shí)例可以提前編譯,然后在給定的“VM上下文”上運(yùn)行多次。在這個(gè)場(chǎng)景背后,jsdom Window是一個(gè)確定的VM上下文。要訪問(wèn)此功能,請(qǐng)使用runVMScript()方法:
const { Script } = require("vm"); const dom = new JSDOM(``, { runScripts: "outside-only" }); const s = new Script(` if (!this.ran) { this.ran = 0; } ++this.ran; `); dom.runVMScript(s); dom.runVMScript(s); dom.runVMScript(s); dom.window.ran === 3;
這是高級(jí)功能,除非您有特殊的需求,否則我們建議堅(jiān)持使用普通的DOM API(如window.eval()或document.createElement(“script”))。
通過(guò)reconfigure(settings)重新配置jsdomwindow.top屬性在規(guī)范中被標(biāo)記為Unforgeable,這意味著它是一個(gè)不可配置的私有屬性,因此在jsdom內(nèi)運(yùn)行的普通代碼是不能覆蓋或遮擋它的,即使使用Object.defineProperty。
同樣,目前在jsdom中是不能夠處理navigation相關(guān)信息的(比如設(shè)置window.location.href ="https://example.com/");這樣做會(huì)導(dǎo)致虛擬控制臺(tái)發(fā)出"jsdomError",說(shuō)明此功能未實(shí)現(xiàn),并且沒(méi)有任何變化,也將不會(huì)有新的Window或Document對(duì)象,并且現(xiàn)有window.location對(duì)象仍保持當(dāng)前所有相同的屬性值。
但是,如果您從 jsdom 窗口之外進(jìn)行演示,例如在一些創(chuàng)建jsdoms的測(cè)試框架中,可以使用特殊的reconfigure()方法覆蓋其中的一個(gè)或兩個(gè):
const dom = new JSDOM(); dom.window.top === dom.window; dom.window.location.href === "about:blank"; dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" }); dom.window.top === myFakeTopForTesting; dom.window.location.href === "https://example.com/";
請(qǐng)注意,更改jsdom的URL將影響所有返回當(dāng)前 document URL的API,例如window.location,document.URL`和document.documentURI,以及文檔中相對(duì)URL的解析以及同源檢查和提取子資源時(shí)使用的引用。但是,它不會(huì)執(zhí)行導(dǎo)航到該URL的內(nèi)容;DOM的內(nèi)容將保持不變,并且不會(huì)創(chuàng)建Window,Document`等新的實(shí)例。
便捷的 APIs fromURL()除了JSDOM構(gòu)造函數(shù)本身之外,jsdom還提供了一個(gè)返回 Promise 的工廠方法,用于通過(guò)URL構(gòu)建一個(gè)jsdom實(shí)例
JSDOM.fromURL("https://example.com/", options).then(dom => { console.log(dom.serialize()); });
如果URL有效且請(qǐng)求成功,則onFullfilled回調(diào)執(zhí)行并返回JSDOM實(shí)例。任何URL重定向都將遵循其最終目的地。
fromURL()提供的參數(shù)選項(xiàng)與提供給JSDOM構(gòu)造函數(shù)的選項(xiàng)類似,但具有以下額外的限制和后果:
url 和 contentType 參數(shù)不能被提供
referrer 選項(xiàng)用作初始請(qǐng)求的HTTP Referer請(qǐng)求頭
userAgent 選項(xiàng)用作任何請(qǐng)求的HTTP User-Agent請(qǐng)求頭
生成的jsdom的url 和 contentType和referrer是 由 http response來(lái)決定
任何通過(guò)HTTP Set-Cookie響應(yīng)頭設(shè)置的cookie都存儲(chǔ)在jsdom的cookie jar中。同樣,已提供的cookie jar中的任何cookie都會(huì)作為HTTP Cookie請(qǐng)求標(biāo)頭發(fā)送。
初始的請(qǐng)求并不能無(wú)限定制到像request npm 包一樣的程度;fromURL()旨在為大多數(shù)情況提供便利的API。如果您需要更好地控制初始請(qǐng)求,您應(yīng)該自己執(zhí)行它,然后手動(dòng)使用JSDOM構(gòu)造函數(shù)。
fromFile()與fromURL()類似,jsdom還提供了一個(gè)fromFile()工廠方法,用于從文件名構(gòu)建jsdom
JSDOM.fromFile("stuff.html", options).then(dom => { console.log(dom.serialize()); });
如果可以打開(kāi)給定的文件,則onFullfilled回調(diào)執(zhí)行并返回JSDOM實(shí)例。和Node.js API一樣,文件名是相對(duì)于當(dāng)前工作目錄的。
fromFile()提供的選項(xiàng)與提供給JSDOM構(gòu)造函數(shù)的選項(xiàng)相似,但具有以下額外的默認(rèn)值:
url選項(xiàng)將默認(rèn)為給定文件名相對(duì)應(yīng)的文件URL,而不是"about:blank"
假如給定的文件名是以.xhtml或者.xml為后綴的話,contentType選項(xiàng)默認(rèn)為"application/xhtml+xml";反之為"text/html"。
fragment()對(duì)于最簡(jiǎn)單的情況,你可能不需要一個(gè)完整的JSDOM實(shí)例及其所有相關(guān)的功能。您甚至可能不需要Window或Document!相反,你只需要解析一些HTML片段,并獲得一個(gè)你可以操作的DOM對(duì)象。為此,我們提供了fragment(),它可以從給定的字符串中創(chuàng)建一個(gè)DocumentFragment:
const frag = JSDOM.fragment(`Hello
Hi!`); frag.childNodes.length === 2; frag.querySelector("strong").textContent = "Why hello there!"; // etc.
frag是DocumentFragment的實(shí)例對(duì)象,其內(nèi)容是通過(guò)提供的字符串解析創(chuàng)建的。解析是通過(guò)使用元素完成的,因此您可以在其中包含任何元素(包括具有奇怪解析規(guī)則的元素,如
fragment()工廠函數(shù)的所有調(diào)用結(jié)果的DocumentFragments實(shí)例都會(huì)共享相同的Document和Window。這允許多次調(diào)用fragment()而沒(méi)有額外的開(kāi)銷。但這也意味著對(duì)fragment()的調(diào)用不能用任何選項(xiàng)自定義。
請(qǐng)注意,對(duì)DocumentFragments的序列化并不像使用JSDOM對(duì)象那樣容易。如果你需要序列化你的DOM,你應(yīng)該直接使用JSDOM構(gòu)造函數(shù)。但對(duì)于包含單個(gè)元素的片段的特殊情況,通過(guò)常規(guī)方法就很容易做到。
const frag = JSDOM.fragment(`其他值得注意的功能 支持 CanvasHello
`); console.log(frag.firstChild.outerHTML); // logs "Hello
"
jsdom支持使用canvas或canvas-prebuilt包來(lái)擴(kuò)展任何使用canvas API的元素。為了做到這一點(diǎn),您需要將canvas作為依賴項(xiàng)加入到您的項(xiàng)目中,和 jsdom包并列。如果jsdom可以找到canvas包,它將使用它,但是如果它不存在,那么元素的行為就像 除了提供一個(gè)字符串外,JSDOM構(gòu)造函數(shù)還支持Node.js Buffer或標(biāo)準(zhǔn)JavaScript二進(jìn)制數(shù)據(jù)類型(如ArrayBuffer,Uint8Array,DataView等)的形式提供二進(jìn)制數(shù)據(jù)。當(dāng)完成后,jsdom將從提供的字節(jié)進(jìn)行嗅探編碼,就像瀏覽器掃描標(biāo)簽一樣。 這種編碼嗅探也適用于JSDOM.fromFile()和JSDOM.fromURL()。在后一種情況下,就像在瀏覽器中一樣,任何與response響應(yīng)一起發(fā)送的Content-Type頭信息優(yōu)先級(jí)更高。 請(qǐng)注意,在許多情況下,提供字節(jié)這種方式可能比提供字符串更好。例如,如果您試圖使用Node.js的buffer.toString("utf-8")API,則Node.js將不會(huì)去除任何前導(dǎo)BOM。如果您將此字符串提供給jsdom,它會(huì)逐字解釋,從而使BOM保持不變。但jsdom的二進(jìn)制數(shù)據(jù)解碼代碼將剝離前導(dǎo)的BOM,就像瀏覽器一樣;在這種情況下,直接提供buffer將會(huì)得到想要的結(jié)果。 jsdom中定義的定時(shí)器(通過(guò)window.setTimeout或window.setInterval設(shè)置)將在window上下文中執(zhí)行代碼。由于進(jìn)程在不活躍的情況下無(wú)法執(zhí)行未來(lái)的定時(shí)器代碼,所以卓越的jsdom定時(shí)器將保持您的Node.js進(jìn)程處于活動(dòng)狀態(tài)。同樣,對(duì)象不活躍的情況下也沒(méi)有辦法在對(duì)象的上下文中執(zhí)行代碼,卓越的jsdom定時(shí)器將阻止垃圾回收調(diào)度它們的window。 如果你想確保關(guān)閉jsdom窗口,使用window.close(),它將終止所有正在運(yùn)行的定時(shí)器(并且還會(huì)刪除 window和document上的任何事件監(jiān)聽(tīng)器)。 使用browserify模塊,jsdom某些方面也支持在Web瀏覽器中運(yùn)行。也就是說(shuō),在Web瀏覽器中,您可以使用被browserify模塊編譯過(guò)的jsdom去創(chuàng)建完全獨(dú)立的普通JavaScript對(duì)象集,其外觀和行為與瀏覽器的現(xiàn)有DOM對(duì)象非常相似,但完全獨(dú)立于它們,也就是"虛擬DOM"! jsdom的主要目標(biāo)對(duì)象仍然是Node.js,因此我們使用僅存在于最新Node.js版本(即Node.js v6 +)中的語(yǔ)言特性功能。因此,在舊版瀏覽器可能無(wú)法正常工作。(即使編譯也不會(huì)有多大幫助:我們計(jì)劃在jsdom v10.x的整個(gè)過(guò)程中廣泛使用Proxy。) 值得注意的是,jsdom在web worker中能很好的運(yùn)行。項(xiàng)目的開(kāi)發(fā)者@lawnsea使這一功能點(diǎn)成為可能,他發(fā)表了一篇關(guān)于他的項(xiàng)目的論文,該論文就使用了這種能力。 在Web瀏覽器中運(yùn)行jsdom時(shí),并非所有的工作都完美。有些情況下,這是由于基礎(chǔ)的條件限制(比如沒(méi)有文件系統(tǒng)訪問(wèn)),但有些情況下也是因?yàn)槲覀儧](méi)有花足夠的時(shí)間去進(jìn)行適當(dāng)?shù)男≌{(diào)整。歡迎大家來(lái)提BUG。 從Node.js v6開(kāi)始,您可以使用Chrome Devtools來(lái)調(diào)試程序。請(qǐng)參閱官方文檔了解如何使用。 默認(rèn)情況下,jsdom元素在控制臺(tái)中被格式化為普通的舊JS對(duì)象。為了便于調(diào)試,可以使用jsdom-devtools-formatter,它可以讓你像真正的DOM元素一樣調(diào)試它們。 使用jsdom時(shí),開(kāi)發(fā)者在加載異步腳本時(shí)經(jīng)常遇到麻煩。許多頁(yè)面異步加載腳本,但無(wú)法分辨腳本什么時(shí)候完成,因此無(wú)法知道何時(shí)是運(yùn)行代碼并檢查生成的DOM結(jié)構(gòu)的好時(shí)機(jī)。這是一個(gè)基本的限制;我們無(wú)法預(yù)測(cè)網(wǎng)頁(yè)上的哪些腳本會(huì)做什么,因此無(wú)法告訴您腳本何時(shí)加載完畢。 這個(gè)問(wèn)題可以通過(guò)幾種方法來(lái)解決。如果您能控制頁(yè)面邏輯,最好的方法是使用腳本加載器提供的機(jī)制來(lái)檢測(cè)何時(shí)加載完成。例如,如果您使用像RequireJS這樣的模塊加載器,代碼可能如下所示: 如果您不能控制該頁(yè)面,則可以嘗試其他解決方法,例如輪詢檢查特定元素是否存在。有關(guān)更多詳細(xì)信息,請(qǐng)查看#640中的討論,尤其是@ matthewkastor的深刻見(jiàn)解。 目前,對(duì)于大多數(shù)Web平臺(tái)API,jsdom在多個(gè)看似獨(dú)立的jsdoms之間共享相同的類定義。這將意味著,可能會(huì)出現(xiàn)以下情況 這主要是出于性能和內(nèi)存的原因:如果在Web平臺(tái)上每次創(chuàng)建jsdom時(shí),創(chuàng)建所有類的多帶帶副本,開(kāi)銷將會(huì)相當(dāng)昂貴。 盡管如此,我們?nèi)匀挥信d趣在有一天提供一個(gè)選項(xiàng)配置來(lái)創(chuàng)建一個(gè)“獨(dú)立”的jsdom,但要犧牲一些性能。 與v9.x之前的舊版jsdom API相比,新API顯然缺少對(duì)資源加載的精細(xì)控制。先前版本的jsdom允許您設(shè)置request時(shí)使用的選項(xiàng)(既可以用于初始請(qǐng)求,也可以用于舊版本的JSDOM.fromURL()和子資源請(qǐng)求)。他們還允許您控制請(qǐng)求哪些子資源并將其應(yīng)用于主文檔,以便您可以下載樣式表,但不下載腳本文件。最后,他們提供了一個(gè)可定制的資源加載器,可以攔截任何傳出的請(qǐng)求并用完全合成的response 響應(yīng)來(lái)結(jié)束。 以上這些功能尚未在新的jsdom API中實(shí)現(xiàn),盡管我們也希望盡快將它們添加回來(lái),但不幸的是,這需要相當(dāng)大的幕后工作去實(shí)施。 同時(shí),請(qǐng)隨時(shí)使用舊的jsdom API來(lái)訪問(wèn)此功能。它一直處于支持和維護(hù)中,但它不會(huì)獲得新功能。舊的文檔位于lib/old-api.md中。 目前jsdom中有很多缺失的API,盡管我們也想要在jsdom中添加新的功能并保持最新的Web規(guī)范。請(qǐng)隨時(shí)為缺失的任何內(nèi)容提交issue,但我們是一個(gè)很小并且忙碌的團(tuán)隊(duì),因此大家一起來(lái)提交 pull request可能會(huì)更好。 除了我們尚未擁有的功能之外,還有兩個(gè)主要功能目前超出了jsdom的范圍。這些是: Navigation:在點(diǎn)擊鏈接或賦值location.href或類似操作時(shí)可以更改全局對(duì)象和所有其他的對(duì)象。 Layout:計(jì)算CSS元素的視覺(jué)布局的能力,這會(huì)影響諸如getBoundingClientRects()或者諸如offsetTop之類的屬性 目前,jsdom對(duì)某些功能的某些方面具有虛擬行為,例如操作navigation 時(shí)向虛擬控制臺(tái)發(fā)送“未實(shí)現(xiàn)的”"jsdomError",或者為許多與布局相關(guān)的屬性返回0。您通??梢栽诖a中解決這些限制,例如通過(guò)在爬網(wǎng)過(guò)程中為每個(gè)頁(yè)面創(chuàng)建新的JSDOM實(shí)例,或使用Object.defineProperty更改各種與布局相關(guān)的getter和方法的返回值 請(qǐng)注意,相同領(lǐng)域中的其他工具(如PhantomJS)確實(shí)支持這些功能。在wiki上,我們有關(guān)于jsdom vs. PhantomJS的更完整的比較介紹。 如果您需要jsdom的幫助,請(qǐng)隨時(shí)使用以下任何方式:
郵件組(問(wèn)題最好以"how do i"的形式)
報(bào)iusse(最好用BUG 報(bào)告) IRC頻道:#jsdom on freenode 以上文檔翻譯自開(kāi)源項(xiàng)目 jsdom,如有翻譯錯(cuò)誤,歡迎指正。 jsdom 原文鏈接 jsdom 項(xiàng)目鏈接 jsdom 中文翻譯wiki鏈接 原文博客地址 文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/94976.html 摘要:酷庫(kù),每天兩分鐘,了解一個(gè)流行庫(kù)。昨天認(rèn)識(shí)了一個(gè)在環(huán)境下操作的庫(kù),實(shí)現(xiàn)了接口,用起來(lái)十分方便。今天,我們要學(xué)習(xí)的就是一個(gè)純實(shí)現(xiàn)的,可以在環(huán)境中模擬出環(huán)境,像這樣對(duì)依賴的庫(kù)就可以在中運(yùn)行了。
NPM酷庫(kù),每天兩分鐘,了解一個(gè)流行NPM庫(kù)。
昨天認(rèn)識(shí)了一個(gè)在Node.js環(huán)境下操作HTML的庫(kù) cheerio,cheerio實(shí)現(xiàn)了jQuery接口,用起來(lái)十分方便。為什么不直接用jQuery... 摘要:酷庫(kù),每天兩分鐘,了解一個(gè)流行庫(kù)。昨天認(rèn)識(shí)了一個(gè)在環(huán)境下操作的庫(kù),實(shí)現(xiàn)了接口,用起來(lái)十分方便。今天,我們要學(xué)習(xí)的就是一個(gè)純實(shí)現(xiàn)的,可以在環(huán)境中模擬出環(huán)境,像這樣對(duì)依賴的庫(kù)就可以在中運(yùn)行了。
NPM酷庫(kù),每天兩分鐘,了解一個(gè)流行NPM庫(kù)。
昨天認(rèn)識(shí)了一個(gè)在Node.js環(huán)境下操作HTML的庫(kù) cheerio,cheerio實(shí)現(xiàn)了jQuery接口,用起來(lái)十分方便。為什么不直接用jQuery... 摘要:做這個(gè)記錄之前,剛完成使用作為公司前端項(xiàng)目的持續(xù)交付工具的實(shí)踐,打算寫的教程前先把官方文檔扒下來(lái)做個(gè)翻譯站。在實(shí)踐一番后,卡在不能頻密調(diào)取翻譯這塊上,項(xiàng)目無(wú)法進(jìn)行下去。
做這個(gè)記錄之前,剛完成使用drone作為公司前端項(xiàng)目的持續(xù)交付工具的實(shí)踐,打算寫的教程前先把官方文檔扒下來(lái)做個(gè)翻譯站。在實(shí)踐一番后,卡在不能頻密調(diào)取google翻譯這塊上,項(xiàng)目無(wú)法進(jìn)行下去。最后覺(jué)得經(jīng)歷的過(guò)程涉及的內(nèi)容... 摘要:但是,如果我們使用過(guò)多的函數(shù)式編程的抽象概念,我們的函數(shù)式編程也會(huì)非常難以理解。相比于不太合理的純函數(shù)式編程,我們的代碼更加可讀理解和修改,這也是我們重構(gòu)代碼的目的。
本文是篇譯文,原文鏈接An Introduction to Reasonably Pure Functional Programming,不當(dāng)之處還請(qǐng)指正。
一個(gè)好的程序員應(yīng)該有能力掌控你寫的代碼,能夠以最簡(jiǎn)單的方法使你... 摘要:前同事留下的測(cè)試,是基于瀏覽器的,主要還是功能測(cè)試。這里不詳細(xì)說(shuō)怎么在瀏覽器端使用測(cè)試了。而且作者也是建議和支持這樣做的,簡(jiǎn)單明了的測(cè)試腳本,重要性有時(shí)候可能和測(cè)試本身一樣重要。經(jīng)測(cè)試,在瀏覽器也有這種問(wèn)題。
2016-09-03 更新
隨著在工作學(xué)習(xí)中更多地接觸、使用測(cè)試工具,發(fā)現(xiàn)自己在本文中的一些記錄是不準(zhǔn)確、不正確的。
今天(九月三日)在家看了 NingJs 的直播,其中有一個(gè)分... 閱讀 3627·2019-08-30 12:58 閱讀 989·2019-08-29 16:37 閱讀 2903·2019-08-29 16:29 閱讀 3185·2019-08-26 12:18 閱讀 2452·2019-08-26 11:59 閱讀 3502·2019-08-23 18:27 閱讀 2885·2019-08-23 16:43 閱讀 3366·2019-08-23 15:23// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
console.log("ready to roll!");
};
const dom1 = new JSDOM();
const dom2 = new JSDOM();
dom1.window.Element.prototype.expando = "blah";
console.log(dom2.window.document.createElement("frameset").expando); // logs "blah"
相關(guān)文章
NPM酷庫(kù):jsdom,純JS實(shí)現(xiàn)的DOM
NPM酷庫(kù):jsdom,純JS實(shí)現(xiàn)的DOM
記一次翻譯站經(jīng)歷
合理的使用純函數(shù)式編程
使用 ava 和 jsdom 做前端測(cè)試
發(fā)表評(píng)論
0條評(píng)論
SHERlocked93
男|高級(jí)講師
TA的文章
閱讀更多
angularjs特效之分散的字符串--解析compile link $compile $inter
overflow:hidden失效問(wèn)題
如何對(duì)GitHubPages上的靜態(tài)資源進(jìn)行CDN加速
React生命周期
一個(gè)前端菜鳥(niǎo)的成長(zhǎng)歷程
Smartour——讓網(wǎng)頁(yè)導(dǎo)覽變得更簡(jiǎn)單
apply與call方法
NEO DAPP全球區(qū)塊鏈應(yīng)用開(kāi)發(fā)挑戰(zhàn)賽