Loading... 在某些特殊情况下用node进行js逆向或者一些操作的时候会用到dom,尽管这种情况挺少,但是就是要用,所以jsdom在这种场合下挺好使 --- ### 使用 ```npm npm i jsdom ``` ```JavaScript const jsdom = require("jsdom"); const { JSDOM } = jsdom; const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`); console.log(dom.window.document.querySelector("p").textContent); console.log(dom.window.navigator) ``` ### 文档 jsdom 是许多 web 标准的纯 JavaScript 实现,特别是 WHATWG [DOM](https://dom.spec.whatwg.org/)和[HTML](https://html.spec.whatwg.org/multipage/)标准,用于 Node.js。一般来说,该项目的目标是模拟足够多的 Web 浏览器子集,以用于测试和抓取真实世界的 Web 应用程序。 最新版本的 jsdom 需要 Node.js v14 或更高版本。(低于 v20 的 jsdom 版本仍可与以前的 Node.js 版本一起使用,但不受支持。) ## 基本用法 ```js notranslate position-relative overflow-auto const jsdom = require("jsdom"); const { JSDOM } = jsdom; ``` 要使用 jsdom,您将主要使用`JSDOM`构造函数,它是 jsdom 主模块的命名导出。向构造函数传递一个字符串。您将返回一个`JSDOM`对象,该对象具有许多有用的属性,特别是`window`: ```js notranslate position-relative overflow-auto const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`); console.log(dom.window.document.querySelector("p").textContent); // "Hello world" ``` (请注意,jsdom 会像浏览器一样解析您传递给它的 HTML,包括隐含`<html>`的`<head>`、 和`<body>`标签。) 生成的对象是`JSDOM`该类的一个实例,其中包含许多有用的属性和方法`window`。一般来说,它可以用来从“外部”作用于 jsdom,做一些普通 DOM API 无法做到的事情。对于不需要任何此功能的简单情况,我们建议使用类似的编码模式 ```js notranslate position-relative overflow-auto const { window } = new JSDOM(`...`); // or even const { document } = (new JSDOM(`...`)).window; ``` 下面是“对象 API”部分中有关您可以使用`JSDOM`该类执行的所有操作的完整文档。`JSDOM` ## 自定义 jsdom 构造函数接受第二个参数,该`JSDOM`参数可用于通过以下方式自定义您的 jsdom。 ### 简单的选项 ```js notranslate position-relative overflow-auto const dom = new JSDOM(``, { url: "https://example.org/", referrer: "https://example.com/", contentType: "text/html", includeNodeLocations: true, storageQuota: 10000000 }); ``` * `url`设置 、 和 的返回值,`window.location`并影响文档中相对 URL 的解析以及获取子资源时使用的同源限制和引用者等内容。它默认为.`document.URL``document.documentURI``"about:blank"` * `referrer`只会影响从 读取的值`document.referrer`。它默认为无推荐人(反映为空字符串)。 * `contentType`影响从 读取的值`document.contentType`,以及文档的解析方式:作为 HTML 或 XML。不是[HTML mime 类型](https://mimesniff.spec.whatwg.org/#html-mime-type)或[XML mime 类型](https://mimesniff.spec.whatwg.org/#xml-mime-type)的值将被抛出。它默认为`"text/html"`. 如果存在`charset`参数,它会影响[二进制数据处理](https://github.com/jsdom/jsdom#encoding-sniffing)。 * `includeNodeLocations`保留由 HTML 解析器生成的位置信息,允许您使用`nodeLocation()`方法检索它(如下所述)。它还确保在异常堆栈跟踪中报告的在`<script>`元素内运行的代码的行号是正确的。它默认`false`提供最佳性能,并且不能与 XML 内容类型一起使用,因为我们的 XML 解析器不支持位置信息。 * `storageQuota`是`localStorage`和使用的单独存储区域的最大代码单元大小`sessionStorage`。尝试存储大于此限制的数据将导致 a`DOMException`被抛出。默认情况下,受 HTML 规范的启发,每个源设置为 5,000,000 个代码单元。 请注意,`url`和`referrer`在使用之前都已规范化,因此例如,如果您传入`"https:example.com"`, jsdom 将解释为就好像您已经给出了`"https://example.com/"`。如果你传递一个不可解析的 URL,调用会抛出。(根据[URL Standard](https://url.spec.whatwg.org/)解析和序列化 URL 。) ### 执行脚本 jsdom最强大的能力是可以在jsdom内部执行脚本。这些脚本可以修改页面的内容并访问 jsdom 实现的所有 Web 平台 API。 但是,在处理不受信任的内容时,这也是非常危险的。jsdom 沙箱并不是万无一失的,运行在 DOM 中的代码`<script>`如果足够努力,就可以访问 Node.js 环境,从而访问您的机器。因此,默认情况下禁用执行嵌入在 HTML 中的脚本的功能: ```js notranslate position-relative overflow-auto const dom = new JSDOM(`<body> <script>document.body.appendChild(document.createElement("hr"));</script> </body>`); // The script will not be executed, by default: dom.window.document.body.children.length === 1; ``` 要在页面内启用执行脚本,您可以使用以下`runScripts: "dangerously"`选项: ```js notranslate position-relative overflow-auto const dom = new JSDOM(`<body> <script>document.body.appendChild(document.createElement("hr"));</script> </body>`, { runScripts: "dangerously" }); // The script will be executed and modify the DOM: dom.window.document.body.children.length === 2; ``` 我们再次强调,仅在提供您知道是安全的 jsdom 代码时才使用它。如果您在任意用户提供的代码或来自 Internet 的代码上使用它,您实际上是在运行不受信任的 Node.js 代码,并且您的机器可能会受到威胁。 如果您想执行*外部*脚本,包括通过`<script src="">`,您还需要确保它们加载它们。为此,请添加`resources: "usable"` [如下](https://github.com/jsdom/jsdom#loading-subresources)所述的选项。(出于那里讨论的原因,您可能还想设置该`url`选项。) 事件处理程序属性,如`<div onclick="">`,也受此设置控制;`runScripts`除非设置为 ,否则它们将不起作用`"dangerously"`。(但是,事件处理程序 *属性* ,如`div.onclick = ...`,将不管`runScripts`.) 如果您只是尝试“从外部”执行脚本,而不是让`<script>`元素和事件处理程序属性“从内部”运行,则可以使用该`runScripts: "outside-only"`选项,它可以安装所有 JavaScript 规范提供的全局变量的新副本上`window`。这包括诸如`window.Array`,`window.Promise`等之类的东西。值得注意的是,它还包括`window.eval`,它允许运行脚本,但 jsdom`window`作为全局: ```js notranslate position-relative overflow-auto const { window } = new JSDOM(``, { runScripts: "outside-only" }); window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`); window.document.body.children.length === 1; ``` 出于性能原因,此功能默认关闭,但可以安全启用。 (注意,在默认配置中,如果不设置, , 等`runScripts`的值将与外部 Node.js 环境提供的值相同。即会保持,因此不会以有用的方式运行脚本。)`window.Array``window.eval``window.eval === eval``window.eval` 我们强烈建议不要尝试通过将 jsdom 和 Node 全局环境混合在一起(例如通过做`global.window = dom.window`)来“执行脚本”,然后在 Node 全局环境中执行脚本或测试代码。相反,您应该像对待浏览器一样对待 jsdom,并使用`window.eval`或运行所有需要访问 jsdom 环境中的 DOM 的脚本和测试`runScripts: "dangerously"`。例如,这可能需要创建一个 browserify 包以作为`<script>`元素执行——就像在浏览器中一样。 最后,对于高级用例,您可以使用`dom.getInternalVMContext()`下面记录的方法。 ### 假装是一个可视化浏览器 jsdom 不具备渲染视觉内容的能力,默认情况下会像无头浏览器一样工作。它通过 API 向网页提供提示,例如`document.hidden`其内容不可见。 当该`pretendToBeVisual`选项设置为 时`true`,jsdom 将假装它正在渲染和显示内容。它通过以下方式做到这一点: * 改为`document.hidden`返回`false`而不是`true` * 改为`document.visibilityState`返回`"visible"`而不是`"prerender"` * 启用`window.requestAnimationFrame()`和`window.cancelAnimationFrame()`方法,否则不存在 ```js notranslate position-relative overflow-auto const window = (new JSDOM(``, { pretendToBeVisual: true })).window; window.requestAnimationFrame(timestamp => { console.log(timestamp > 0); }); ``` 请注意,jsdom 仍然[不做任何布局或渲染](https://github.com/jsdom/jsdom#unimplemented-parts-of-the-web-platform),所以这实际上只是*假装*是可视的,而不是实现平台的部分,一个真实的、可视的 web 浏览器将实现。 ### 加载子资源 #### 基本选项 默认情况下,jsdom 不会加载任何子资源,例如脚本、样式表、图像或 iframe。如果您希望 jsdom 加载此类资源,您可以传递该`resources: "usable"`选项,该选项将加载所有可用资源。那些是: * 框架和 iframe,通过`<frame>`和`<iframe>` * 样式表,通过`<link rel="stylesheet">` * 脚本,通过`<script>`,但前提`runScripts: "dangerously"`是也设置了 * 图片,通过`<img>`,但`canvas`前提是还安装了 npm 包(请参阅下面的“[画布支持](https://github.com/jsdom/jsdom#canvas-support)”) 尝试加载资源时,请记住该`url`选项的默认值为`"about:blank"`,这意味着通过相对 URL 包含的任何资源都将无法加载。(尝试根据 URL 解析 URL 的结果`/something`是`about:blank`一个错误。)因此,在这些情况下,您可能希望为该`url`选项设置一个非默认值,或者使用自动执行此操作的[便利 API](https://github.com/jsdom/jsdom#convenience-apis)之一。 #### 高级配置 *这个资源加载器系统是 jsdom v12.0.0 的新版本,我们希望您能就它是否满足您的需求以及它的易用性提供反馈。请提出问题进行讨论!* 要更全面地自定义 jsdom 的资源加载行为,您可以将`ResourceLoader`类的实例作为`resources`选项值传递: ```js notranslate position-relative overflow-auto const resourceLoader = new jsdom.ResourceLoader({ proxy: "http://127.0.0.1:9001", strictSSL: false, userAgent: "Mellblomenator/9000", }); const dom = new JSDOM(``, { resources: resourceLoader }); ``` 构造函数的三个选项`ResourceLoader`是: * `proxy`是要使用的 HTTP 代理的地址。 * `strictSSL`可以设置为 false 以禁用 SSL 证书有效的要求。 * `userAgent`影响`User-Agent`发送的标头,从而影响`navigator.userAgent`. 它默认为``Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}``. `ResourceLoader`您可以通过子类化和覆盖该`fetch()`方法来进一步自定义资源获取。例如,这是一个仅返回对可信来源的请求结果的版本: ```js notranslate position-relative overflow-auto class CustomResourceLoader extends jsdom.ResourceLoader { fetch(url, options) { // Override the contents of this script to do something unusual. if (url === "https://example.com/some-specific-script.js") { return Promise.resolve(Buffer.from("window.someGlobal = 5;")); } return super.fetch(url, options); } } ``` jsdom 将`fetch()`在遇到“可用”资源时调用您的自定义资源加载器的方法,根据上述部分。该方法接受一个 URL 字符串,以及一些选项,如果调用`super.fetch()`. 它必须返回一个 Node.js`Buffer`对象的承诺,或者`null`如果有意不加载资源则返回。通常,大多数情况下都希望委托给`super.fetch()`,如图所示。 您将收到的选项之一`fetch()`是获取资源的元素(如果适用)。 ```js notranslate position-relative overflow-auto class CustomResourceLoader extends jsdom.ResourceLoader { fetch(url, options) { if (options.element) { console.log(`Element ${options.element.localName} is requesting the url ${url}`); } return super.fetch(url, options); } } ``` ### 虚拟控制台 与 Web 浏览器一样,jsdom 具有“控制台”的概念。这记录了通过在文档中执行的脚本直接从页面发送的信息,以及来自 jsdom 实现本身的信息。我们将用户可控的控制台称为“虚拟控制台”,以区别于 Node.js `console`API 和页内`window.console`API。 默认情况下,`JSDOM`构造函数将返回一个带有虚拟控制台的实例,该虚拟控制台将其所有输出转发到 Node.js 控制台。要创建自己的虚拟控制台并将其传递给 jsdom,您可以通过执行来覆盖此默认值 ```js notranslate position-relative overflow-auto const virtualConsole = new jsdom.VirtualConsole(); const dom = new JSDOM(``, { virtualConsole }); ``` 像这样的代码将创建一个没有任何行为的虚拟控制台。您可以通过为所有可能的控制台方法添加事件侦听器来赋予它行为: ```js notranslate position-relative overflow-auto virtualConsole.on("error", () => { ... }); virtualConsole.on("warn", () => { ... }); virtualConsole.on("info", () => { ... }); virtualConsole.on("dir", () => { ... }); // ... etc. See https://console.spec.whatwg.org/#logging ``` (请注意,最好在调用*之前*`new JSDOM()`设置这些事件侦听器,因为在解析期间可能会发生错误或控制台调用脚本。) 如果您只是想将虚拟控制台输出重定向到另一个控制台,例如默认的 Node.js 控制台,您可以这样做 ```js notranslate position-relative overflow-auto virtualConsole.sendTo(console); ``` 还有一个特殊事件 ,`"jsdomError"`它会触发错误对象以报告来自 jsdom 本身的错误。这类似于错误消息经常出现在 Web 浏览器控制台中,即使它们不是由`console.error`. 到目前为止,以这种方式输出以下错误: * 加载或解析子资源(脚本、样式表、框架和 iframe)时出错 * 返回或调用的窗口`onerror`事件处理程序未处理的脚本执行错误`true``event.preventDefault()` * 调用方法导致的未实现错误,如`window.alert`jsdom 未实现,但仍会安装以实现 Web 兼容性 如果您使用`sendTo(c)`将错误发送到`c`,默认情况下它将`c.error(errorStack[, errorDetail])`使用来自`"jsdomError"`事件的信息进行调用。如果您希望保持严格的事件到方法调用的一对一映射,并且可能`"jsdomError"`自己处理 s,那么您可以这样做 ```js notranslate position-relative overflow-auto virtualConsole.sendTo(c, { omitJSDOMErrors: true }); ``` ### 饼干罐 和 web 浏览器一样,jsdom 也有 cookie jar 的概念,用来存储 HTTP cookie。具有与文档相同域中的 URL 且未标记为仅 HTTP 的 Cookie 可通过`document.cookie`API 访问。此外,cookie jar 中的所有 cookie 都会影响子资源的获取。 默认情况下,`JSDOM`构造函数将返回一个带有空 cookie jar 的实例。要创建自己的 cookie jar 并将其传递给 jsdom,您可以通过执行覆盖此默认值 ```js notranslate position-relative overflow-auto const cookieJar = new jsdom.CookieJar(store, options); const dom = new JSDOM(``, { cookieJar }); ``` 如果您想在多个 jsdom 之间共享同一个 cookie jar,或者提前使用某些值填充 cookie jar,这将非常有用。 饼干罐是由[艰难饼干](https://www.npmjs.com/package/tough-cookie)包提供的。`jsdom.CookieJar`构造函数是tough-cookie cookie jar 的子类,它默认设置选项`looseMode: true`,因为它[更好地匹配浏览器的行为方式](https://github.com/whatwg/html/issues/804)。如果您想自己使用tough-cookie 的实用程序和类,可以使用`jsdom.toughCookie`模块导出来访问使用jsdom 打包的tough-cookie 模块实例。 ### 在解析之前进行干预 jsdom 允许您在很早的时候干预 jsdom 的创建:在创建`Window`and`Document`对象之后,但在解析任何 HTML 以使用节点填充文档之前: ```js notranslate position-relative overflow-auto const dom = new JSDOM(`<p>Hello</p>`, { beforeParse(window) { window.document.childNodes.length === 0; window.someCoolAPI = () => { /* ... */ }; } }); ``` 如果您想以某种方式修改环境,例如为 jsdom 不支持的 Web 平台 API 添加 shim,这将特别有用。 ## `JSDOM`对象 API 构建`JSDOM`对象后,它将具有以下有用的功能: ### 特性 该属性`window`检索`Window`为您创建的对象。 属性`virtualConsole`并`cookieJar`反映您传入的选项,或者如果没有为这些选项传入任何内容,则为您创建的默认值。 ### 序列化文档`serialize()` 该`serialize()`方法将返回文档的[HTML 序列化](https://html.spec.whatwg.org/#html-fragment-serialisation-algorithm),包括文档类型: ```js notranslate position-relative overflow-auto const dom = new JSDOM(`<!DOCTYPE html>hello`); dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>"; // Contrast with: dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>"; ``` ### 获取节点的源位置`nodeLocation(node)` 该`nodeLocation()`方法将查找 DOM 节点在源文档中的位置,并返回该节点的[parse5 位置信息](https://www.npmjs.com/package/parse5#options-locationinfo): ```js notranslate position-relative overflow-auto const dom = new JSDOM( `<p>Hello <img src="foo.jpg"> </p>`, { 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 } ``` 请注意,此功能仅在您设置了该`includeNodeLocations`选项时才有效;出于性能原因,节点位置默认关闭。 ### 使用与 Node.js`vm`模块交互`getInternalVMContext()` Node.js的内置[`vm`](https://nodejs.org/api/vm.html)模块是 jsdom 脚本运行魔法的基础。一些高级用例,例如预编译脚本然后多次运行,可以从`vm`直接使用 jsdom-created 的模块中受益`Window`。 要访问适用于API的[上下文全局对象](https://nodejs.org/api/vm.html#vm_what_does_it_mean_to_contextify_an_object),您可以使用以下方法:`vm``getInternalVMContext()` ```js notranslate position-relative overflow-auto const { Script } = require("vm"); const dom = new JSDOM(``, { runScripts: "outside-only" }); const script = new Script(` if (!this.ran) { this.ran = 0; } ++this.ran; `); const vmContext = dom.getInternalVMContext(); script.runInContext(vmContext); script.runInContext(vmContext); script.runInContext(vmContext); console.assert(dom.window.ran === 3); ``` 这是有点高级的功能,我们建议坚持使用普通的 DOM API(例如`window.eval()`或`document.createElement("script")`),除非您有非常特殊的需求。 `JSDOM`请注意,如果实例是在未`runScripts`设置的情况下创建的,或者您[在 Web 浏览器中使用 jsdom,](https://github.com/jsdom/jsdom#running-jsdom-inside-a-web-browser)则此方法将引发异常。 ### 重新配置jsdom`reconfigure(settings)` `top`属性 on在规范中`window`被标记,这`[Unforgeable]`意味着它是一个不可配置的自有属性,因此不能被 jsdom 内运行的正常代码覆盖或隐藏,即使使用`Object.defineProperty`. 同样,目前 jsdom 不处理导航(如设置`window.location.href = "https://example.com/"`);这样做会导致虚拟控制台发出一个`"jsdomError"`解释,说明这个特性没有实现,并且什么都不会改变:不会有新的`Window`或`Document`对象,现有`window`的`location`对象仍然具有所有相同的属性值。 但是,如果您是在窗口外进行操作,例如在某些创建 jsdom 的测试框架中,您可以使用特殊`reconfigure()`方法覆盖其中一个或两个: ```js notranslate position-relative overflow-auto 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/"; ``` 请注意,更改 jsdom 的 URL 将影响所有返回当前文档 URL 的 API,例如`window.location`、`document.URL`和`document.documentURI`,以及文档中相对 URL 的解析,以及在获取子资源时使用的同源检查和引用。但是,它不会导航到该 URL 的内容;DOM 的内容将保持不变,不会创建 , 等的新`Window`实例`Document`。 ## 便利 API ### `fromURL()` 除了`JSDOM`构造函数本身,jsdom 还提供了一个返回 promise 的工厂方法,用于从 URL 构造 jsdom: ```js notranslate position-relative overflow-auto JSDOM.fromURL("https://example.com/", options).then(dom => { console.log(dom.serialize()); }); ``` `JSDOM`如果 URL 有效且请求成功,则返回的 Promise 将通过实例实现。任何重定向都将被跟踪到其最终目的地。 提供给构造函数的选项`fromURL()`类似于提供给`JSDOM`构造函数的选项,但具有以下附加限制和后果: * 无法提供和`url`选项。`contentType` * 该`referrer`选项用作`Referer`初始请求的 HTTP 请求标头。 * 该`resources`选项也会影响初始请求;例如,如果您想配置代理(见上文),这很有用。 * 生成的 jsdom 的 URL、内容类型和引用者由响应确定。 * 通过 HTTP`Set-Cookie`响应标头设置的任何 cookie 都存储在 jsdom 的 cookie jar 中。同样,提供的 cookie jar 中已有的任何 cookie 都作为 HTTP`Cookie`请求标头发送。 ### `fromFile()` 与 类似`fromURL()`,jsdom 也提供了一个`fromFile()`工厂方法,用于从文件名构造 jsdom: ```js notranslate position-relative overflow-auto JSDOM.fromFile("stuff.html", options).then(dom => { console.log(dom.serialize()); }); ``` `JSDOM`如果可以打开给定的文件,则返回的承诺将通过实例实现。与 Node.js API 中的往常一样,文件名是相对于当前工作目录给出的。 提供的选项`fromFile()`类似于提供给`JSDOM`构造函数的选项,但具有以下附加默认值: * 该`url`选项将默认为与给定文件名对应的文件 URL,而不是`"about:blank"`. * 如果给定的文件名以 、 或 ; 结尾,该选项将默认`contentType`为 否则它将继续默认为.`"application/xhtml+xml"``.xht``.xhtml``.xml``"text/html"` ### `fragment()` 对于最简单的情况,您可能不需要`JSDOM`具有所有相关功能的整个实例。您甚至可能不需要`Window`or `Document`!相反,您只需要解析一些 HTML,并获得一个可以操作的 DOM 对象。为此,我们有,它从给定的字符串`fragment()`创建一个:`DocumentFragment` ```js notranslate position-relative overflow-auto const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`); frag.childNodes.length === 2; frag.querySelector("strong").textContent === "Hi!"; // etc. ``` 这`frag`是一个[`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment)实例,其内容是通过解析提供的字符串创建的。解析是使用`<template>`元素完成的,因此您可以在其中包含任何元素(包括具有奇怪解析规则的元素,例如`<td>`)。同样重要的是要注意结果`DocumentFragment`不会有[关联的浏览上下文](https://html.spec.whatwg.org/multipage/#concept-document-bc):也就是说,元素`ownerDocument`将有一个空`defaultView`属性,资源不会加载,等等。 工厂的所有调用都会`fragment()`导致`DocumentFragment`s 共享相同的模板 owner `Document`。这允许`fragment()`在没有额外开销的情况下进行多次调用。但这也意味着`fragment()`不能使用任何选项自定义调用。 请注意,使用`DocumentFragment`s 进行序列化并不像使用完整`JSDOM`对象那样容易。如果您需要序列化您的 DOM,您可能应该`JSDOM`更直接地使用构造函数。但是对于包含单个元素的片段的特殊情况,通过正常方式很容易做到: ```js notranslate position-relative overflow-auto const frag = JSDOM.fragment(`<p>Hello</p>`); console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>" ``` ## 其他值得注意的功能 ### 画布支持 jsdom 支持使用该[`canvas`](https://www.npmjs.com/package/canvas)包通过 canvas API 扩展任何`<canvas>`元素。要完成这项工作,您需要`canvas`在项目中包含一个依赖项,作为`jsdom`. 如果 jsdom 可以找到`canvas`包,它将使用它,但如果它不存在,则`<canvas>`元素的行为类似于`<div>`s。从 jsdom v13 开始,`canvas`需要 2.x 版本;版本 1.x 不再受支持。 ### 编码嗅探 除了提供字符串之外,`JSDOM`还可以为构造函数提供二进制数据,以 Node.js[`Buffer`](https://nodejs.org/docs/latest/api/buffer.html)或标准 JavaScript 二进制数据类型(如`ArrayBuffer`、`Uint8Array`、`DataView`等)的形式。完成后,jsdom 将从提供[的编码中嗅探](https://html.spec.whatwg.org/multipage/syntax.html#encoding-sniffing-algorithm)字节,`<meta charset>`就像浏览器一样扫描标签。 如果提供的`contentType`选项包含`charset`参数,则该编码将覆盖嗅探编码 - 除非存在 UTF-8 或 UTF-16 BOM,在这种情况下,它们优先。(同样,这就像一个浏览器。) 这种编码嗅探也适用于`JSDOM.fromFile()`和`JSDOM.fromURL()`。在后一种情况下,`Content-Type`与响应一起发送的任何标头都将具有优先级,与构造函数的`contentType`选项相同。 请注意,在许多情况下,以这种方式提供字节可能比提供字符串更好。例如,如果您尝试使用 Node.js 的`buffer.toString("utf-8")`API,Node.js 将不会剥离任何前导 BOM。如果您随后将此字符串提供给 jsdom,它将逐字解释它,而 BOM 保持不变。但是 jsdom 的二进制数据解码代码会剥离前导 BOM,就像浏览器一样;在这种情况下,`buffer`直接供应将产生预期的结果。 ### 关闭一个 jsdom 根据定义, jsdom 中的计时器(由`window.setTimeout()`or设置`window.setInterval()`)将来会在窗口的上下文中执行代码。由于将来如果不保持进程处于活动状态就无法执行代码,出色的 jsdom 计时器将使您的 Node.js 进程保持活动状态。类似地,由于没有办法在对象的上下文中执行代码而不保持该对象处于活动状态,因此未完成的 jsdom 计时器将阻止对它们调度的窗口进行垃圾收集。 如果您想确保关闭 jsdom 窗口,请使用`window.close()`,它将终止所有正在运行的计时器(并删除窗口和文档上的任何事件侦听器)。 ### 在 Web 浏览器中运行 jsdom [jsdom 使用browserify](https://browserify.org/)支持在 Web 浏览器中运行。也就是说,在 Web 浏览器中,您可以使用浏览器化的 jsdom 创建一组完全独立的纯 JavaScript 对象,这些对象的外观和行为与浏览器现有的 DOM 对象非常相似,同时完全独立于它们。确实是“虚拟 DOM”! jsdom 的主要目标仍然是 Node.js,因此我们使用仅存在于最近的 Node.js 版本中的语言功能。因此,较旧的浏览器可能无法正常工作。(即使是转译也无济于事:我们`Proxy`在整个 jsdom 代码库中广泛使用 s。) 值得注意的是,jsdom 在 web worker 中运行良好。使这成为可能的原始贡献者[@lawnsea](https://github.com/lawnsea/)[发表了一篇](https://pdfs.semanticscholar.org/47f0/6bb6607a975500a30e9e52d7c9fbc0034e27.pdf)关于他使用此功能的项目的论文。 在 Web 浏览器中运行 jsdom 时,并非一切都能完美运行。有时这是因为基本限制(例如没有文件系统访问权限),但有时仅仅是因为我们没有花足够的时间进行适当的小调整。错误报告当然是受欢迎的。 ### 使用 Chrome DevTools 调试 DOM 在 Node.js 中,您可以使用 Chrome DevTools 调试程序。请参阅[官方文档](https://nodejs.org/en/docs/inspector/)了解如何开始。 默认情况下,jsdom 元素在控制台中被格式化为普通的旧 JS 对象。为了更容易调试,您可以使用[jsdom-devtools-formatter](https://github.com/viddo/jsdom-devtools-formatter),它可以让您像检查真实的 DOM 元素一样检查它们。 ## 注意事项 ### 异步脚本加载 人们在使用 jsdom 时经常遇到异步脚本加载的问题。许多页面异步加载脚本,但无法判断它们何时完成,因此何时是运行代码并检查生成的 DOM 结构的好时机。这是一个基本的限制;我们无法预测网页上的脚本会做什么,因此无法告诉您何时完成加载更多脚本。 这可以通过几种方式解决。如果您控制有问题的页面,最好的方法是使用脚本加载器提供的任何机制来检测加载完成的时间。例如,如果您使用像 RequireJS 这样的模块加载器,代码可能如下所示: ```js notranslate position-relative overflow-auto // On the Node.js side: const window = (new JSDOM(...)).window; window.onModulesLoaded = () => { console.log("ready to roll!"); }; ``` ```html <!-- Inside the HTML you supply to jsdom --> <script> requirejs(["entry-module"], () => { window.onModulesLoaded(); }); </script> ``` 如果您不控制页面,您可以尝试变通方法,例如轮询特定元素的存在。 有关更多详细信息,请参阅[#640](https://github.com/jsdom/jsdom/issues/640)中的讨论,尤其是[@matthewkastor](https://github.com/matthewkastor)的[有见地的评论](https://github.com/jsdom/jsdom/issues/640#issuecomment-22216965)。 ### Web 平台的未实现部分 尽管我们喜欢为 jsdom 添加新功能并使其与最新的 Web 规范保持同步,但它仍然缺少许多 API。请随时为缺少的任何内容提交问题,但我们是一个小而忙碌的团队,因此拉取请求可能会更好。 除了我们尚未了解的功能之外,还有两个主要功能目前超出了 jsdom 的范围。这些是: * **导航**`location.href`:在单击链接或分配或类似时更改全局对象和所有其他对象的能力。 * **布局** :计算 CSS 的结果元素在视觉上布局的能力,这会影响类似的方法`getBoundingClientRects()`或类似的属性`offsetTop`。 目前 jsdom 在这些功能的某些方面具有虚拟行为,例如向`"jsdomError"`虚拟控制台发送“未实现”以进行导航,或者为许多与布局相关的属性返回零。通常您可以在您的代码中解决这些限制,例如,通过`JSDOM`为您在爬网期间“导航”到的每个页面创建新实例,或者使用`Object.defineProperty()`来更改各种与布局相关的 getter 和方法返回的内容。 请注意,同一空间中的其他工具(例如 PhantomJS)确实支持这些功能。在 wiki 上,我们有更完整的关于[jsdom 与 PhantomJS 的文章](https://github.com/jsdom/jsdom/wiki/jsdom-vs.-PhantomJS)。 [Github仓库地址](https://github.com/jsdom/jsdom) 最后修改:2022 年 11 月 12 日 © 允许规范转载 打赏 赞赏作者 微信 赞 0 如果觉得我的文章对你有用,请随意赞赏