日记
jsdom是许多网络标准的纯Java脚本执行,尤其是WHWG 管理部 财务报告和审定财务报表 HTML 语言 标准,与Node.js一起使用. 一般来说,该项目的目标是模仿足够多的网络浏览器子集,用于测试和刮去真实世界的网络应用.
Jsdom的最新版本需要Node.js v6或更新. (v10下方的jsdom版本仍然与Node.js v4合作,但并不支持
截至v10,jsdom有一个新的API(下面记载). 原有的API目前仍然得到支持; 见文件 详细情况.
基本用途
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
要使用jsdom, 您将主要使用 JSDOM 构造器,是 jsdom 主模块的命名导出 。 传出构造器一串. 你会回来的 JSDOM 对象,它具有一些有用的属性,特别是 window数字 :
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不可能做的事情。 对于简单的案例,如果您不需要任何这种功能,我们建议一个编码模式,比如:
const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;
关于你所能做的每件事的完整文档 JSDOM 分类如下,在一节中 "JSDOM 对象 API".
自定义jsdom
这个 JSDOM 构造器接受第二个参数,可以用以下方式自定义您的jsdom .
简单选项
const dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
contentType: "text/html",
userAgent: "Mellblomenator/9000",
includeNodeLocations: true
});
url设置返回的值window.location, (中文(简体) ),document.URL,以及document.documentURI,并影响诸如文档中相对URL的解析度,以及获取子资源时使用的同源限制和裁判员。 默认为"about:blank"。 。 。 .referrer只影响从document.referrer。它默认为无引用者(它反映为空字符串).contentType影响从document.contentType,以及文档是如何解析的:作为 HTML 或 XML 。 非数值"text/html"或一个 XML MIME 类型 会投出。 默认为"text/html"。 。 。 .userAgent影响从navigator.userAgent,以及User-Agent获取子资源时发送了标题 。 默认为`Mozilla/5.0 (${process.platform}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`。 。 。 .includeNodeLocations保存 HTML 解析器生成的位置信息,允许您用nodeLocation()方法(见下文)。 默认为false以给出最佳性能,不能使用 XML 内容类型,因为我们的 XML 解析器不支持位置信息.
注意两者 url 财务报告和审定财务报表 referrer 在他们使用之前就被卡通化了, 所以,例如,如果你通过 "https:example.com",jsdom会解释,就像你给了 "https://example.com/"。如果通过一个无法解析的 URL,则该调用会发出。 (按 URL 标准. ) (中文(简体) )
执行脚本
jsdom最强大的能力是可以在jsdom内部执行脚本. 这些脚本可以修改页面内容并访问所有网络平台APIS jsdom执行.
然而,在处理不可信的内容时,这也非常危险。 jsdom 沙盒不是愚蠢的, 代码运行在 DOM 内部 <script>s可以,如果它足够努力, 获得进入节点环境, 从而进入你的机器。 因此,HTML中嵌入的脚本执行能力默认被禁用:
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" 选项 :
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代码时才使用这个代码,你知道是安全的。 如果您在任意用户提供代码上使用,或者在互联网上使用代码,则您正在有效运行不可信Node.js代码,您的机器可能会被损坏.
注意事件处理器属性, 如 <div onclick="">,除非 runScripts 设置为 "dangerously". (然而,事件处理器 属性一样 div.onclick = ...,将不管 runScripts. ) (中文(简体) )
如果你只是试图从外部执行脚本,而不是让 <script> 元素(和内置事件处理器)从内部运行,您可以使用 runScripts: "outside-only" 选项,允许 window.eval数字 :
const window = (new JSDOM(``, { runScripts: "outside-only" })).window;
window.eval(`document.body.innerHTML = "<p>Hello, world!</p>";`);
window.document.body.children.length === 1;
由于性能原因,它默认关闭,但安全启用.
请注意,我们强烈建议不要试图通过将jsdom和Node全球环境(例如通过做)混合起来来"执行脚本" global.window = dom.window),然后在节点全球环境中执行脚本或测试代码. 相反,你应该像浏览器一样对待jsdom,运行所有需要访问jsdom环境中的DOM的脚本和测试,使用 window.eval 或者说 runScripts: "dangerously"。例如,这可能需要创建一个浏览器捆绑以作为执行 <script> 元素 - 就像你在浏览器中一样.
最后,对于高级用途,您可以使用 dom.runVMScript(script) 方法,见下文.
正在装入子资源
默认情况下, jsdom 将不会装入任何子资源, 如脚本, 样式表, 图像, 或 iframes 。 如果你希望Jsdom 装载这样的资源,你可以通过 resources: "usable" 选项,将载入所有可用的资源。 它们是:
- 框架和iframes,通过
<frame>财务报告和审定财务报表<iframe> - 样式表,通过
<link rel="stylesheet"> - 脚本,通过
<script>,但只有在runScripts: "dangerously"还设置了 - 图像,通过
<img>,但只有在canvas(或canvas-prebuiltnpm 软件包也被安装(见下文"Canvas Support")
今后我们计划通过这个选项对资源加载进行更多的定制,但目前默认和 "usable" 选项是提供的两种模式.
虚拟控制台
和网页浏览器一样,jsdom也有"主机"的概念. 这既记录了从页面直接发送的信息、文件内执行的脚本,也记录了jsdom执行本身的信息。 我们称用户控制控制控制台为"虚拟控制台",以区别于节点.js console API和内页 window.console API (英语).
默认情况下 JSDOM 构造器将返回一个带有虚拟控制台的实例,该虚拟控制台将把全部输出转发到节点.js控制台. 要创建自己的虚拟控制台并将其传递到jsdom,您可以通过做来推翻这个默认
const virtualConsole = new jsdom.VirtualConsole();
const dom = new JSDOM(``, { virtualConsole });
像这样的代码将创建一个没有行为的虚拟控制台. 您可以通过为所有可能的控制台方法添加事件听众来给出行为 :
virtualConsole.on("error", () => { ... });
virtualConsole.on("warn", () => { ... });
virtualConsole.on("info", () => { ... });
virtualConsole.on("dir", () => { ... });
// ... etc. See https://console.spec.whatwg.org/#logging
(注意,也许最好设置这些活动的听众) 在此之前 电话 new JSDOM(),因为解析时可能会出错或引用控制台脚本. )
如果您只是想要将虚拟控制台的输出重定向到另一个控制台, 就像默认的节点. js 一样, 您可以这样做
virtualConsole.sendTo(console);
还有一件特别的事, "jsdomError",它会用出错的物体来报告jsdom本身的错误。 这类似于错误消息常常出现在网页浏览器控制台中,即使它们不是由启动的 console.error。到目前为止,以下错误是用这种方式输出的:
- 装入或解析子资源出错( 脚本、 样式表、 框架和 iframe)
- 未由窗口处理的脚本执行错误
onerror返回的事件处理器true或打电话event.preventDefault() - 调用方法造成的未执行错误,例如
window.alert,jsdom不执行,但无论如何为网络兼容性安装
如果你在用的话 sendTo(c) 发送错误到 c,默认情况下将调用 console.error 信息从 "jsdomError" 事件。 如果你愿意保持一个严格的一对一 事件映射 方法调用,也许处理 "jsdomError"自己一个人,你就可以做
virtualConsole.sendTo(c, { omitJSDOMErrors: true });
饼干罐
像网页浏览器一样,jsdom有饼干罐的概念,存储了HTTP饼干. 在与文档相同的域上拥有URL且不只标有 HTTP 的饼干可以通过 document.cookie API (英语). 此外,饼干罐中的所有饼干都会影响子资源的取取取.
默认情况下 JSDOM 构造器会用空饼干罐返回一个实例 。 要创建自己的饼干罐并将其传递到jsdom,你可以通过做来克服这个默认
const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });
如果您想要在多个jsdoms中共享同样的饼干罐, 或提前将饼干罐装入具有一定值的质素, 这一点就大有用处 .
Cookie罐头由 硬饼干 软件包。 这个 jsdom.CookieJar 构造器是硬饼干罐子的子类,默认是设置 looseMode: true 选项,因为 更匹配浏览器的表现。如果你想使用硬饼干的公用事业 和课程你自己,你可以使用 jsdom.toughCookie 模块导出以获取使用 jsdom 包装的硬饼干模块实例.
解析前的干涉
jsdom允许您很早就介入 jsdom 的创建: Window 财务报告和审定财务报表 Document 对象被创建,但在解析出 HTML 以将文档填入节点之前:
const dom = new JSDOM(`<p>Hello</p>`, {
beforeParse(window) {
window.document.childNodes.length === 0;
window.someCoolAPI = () => { /* ... */ };
}
});
如果您想要以某种方式修改环境, 这尤其有用, 例如为网络平台 APIS jsdom 添加 shims 不支持 .
JSDOM 对象 API
一旦你建造了一个 JSDOM 对象,它将具有以下有用的能力:
属性
财产 window 获取 Window 为您创建的对象.
属性 virtualConsole 财务报告和审定财务报表 cookieJar 反映您通过的选项, 或为您创建的默认选项, 如果这些选项没有被输入的话.
将文档序列化 serialize()
这个 serialize() 方法将返回 HTML 序列化 包括文件类型:
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 节点,返回 解析5 位置信息 用于节点:
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 选项;由于性能原因,节点位置默认关闭.
运行 vm 创建脚本 runVMScript(script)
内置 vm 模块允许您创建 Script 实例,可以提前编译,然后在给定的"VM上下文"上多次运行. 在幕后,一个jsdom Window 这的确是一个自愿参与的背景。 为了获得这种能力 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;
这功能有些先进,我们建议坚持使用普通的DOM API(例如: window.eval() 或者说 document.createElement("script")除非你有非常具体的需求.
将jsdom重新配置为 reconfigure(settings)
这个 top 属性在 window 标记 [Unforgeable] 在 spec中,意思是它是一个不可定义的自有财产,因此不能被运行在jsdom内部的普通代码所压倒或阴影,甚至使用 Object.defineProperty。 。 。 .
同样,目前jsdom不处理导航(例如设置) window.location.href === "https://example.com/");这样做将使虚拟控制台发出一个 "jsdomError" 解释这一特性没有实施,没有什么变化:没有新的 Window 或者说 Document 对象和现有的 window是因为 location 对象仍然具有所有相同的属性值.
然而,如果你从窗外行动,例如在某些创建jsdoms的测试框架中,可以使用特制来覆盖其中的一种或两种 reconfigure() 方法 :
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等将创建.
方便性 APIs
fromURL()
计外 JSDOM 建构器本身, jsdom 提供了从 URL 构建 jsdom 的许诺回放厂法 :
JSDOM.fromURL("https://example.com/", options).then(dom => {
console.log(dom.serialize());
});
回来的诺言将以一个 JSDOM 如果 URL 有效且请求成功。 任何改道都将被跟踪到最终目的地.
向 fromURL() 与向难民署提供的类似 JSDOM (a) 具有下列额外限制和后果的构造器:
- 这个
url财务报告和审定财务报表contentType无法提供选项. - 这个
referrer选项用作 HTTPReferer请求初始请求标题. - 这个
userAgent选项用作 HTTPUser-Agent请求任何请求的页眉. - 由此而来jsdom的URL,内容类型,以及参考者由响应决定.
- 通过 HTTP 设置任何饼干
Set-Cookie响应头被存储在jsdom的饼干罐. 类似地,任何已经在供应的饼干罐中的饼干都会作为HTTP发送Cookie请求标题.
初始请求并不是无限定制的 尽可能在类似包中 请求· ; fromURL() 目的是为大多数案件提供方便。 需要更多控制初始请求的,应当自行执行,然后使用 JSDOM 人工构造器.
fromFile()
类似 fromURL(),jsdom 也提供了一个 fromFile() 从文件名构建 jsdom 的工厂方法 :
JSDOM.fromFile("stuff.html", options).then(dom => {
console.log(dom.serialize());
});
回来的诺言将以一个 JSDOM 实例,如果可以打开给定文件。 和通常在Node.js APIs中一样,文件名是相对于当前工作目录给出的.
向 fromFile() 与向难民署提供的类似 JSDOM 构造器,并附加下列默认值:
- 这个
url选项将默认为对应给定文件名的文件 URL,而不是"about:blank"。 。 。 . - 这个
contentType选项默认为"application/xhtml+xml"如果给定的文件名以.xhtml或者说.xml;否则将继续默认为"text/html"。 。 。 .
fragment()
最简单的是,你可能不需要一个整体 JSDOM 拥有一切相关权力 你可能甚至不需要一个 Window 或者说 Document相反,你只需要解析一些 HTML,然后得到一个您可以操纵的 DOM 对象 。 为此,我们有 fragment(),从而创建一个 DocumentFragment 从给定字符串中:
const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);
frag.childNodes.length === 2;
frag.querySelector("strong").textContent = "Why hello there!";
// etc.
给 frag 是一个 DocumentFragment 实例,其内容是通过解析提供的字符串创建的。 解析是使用 <template> 元素,所以您可以包含那里的任何元素(包括那些有奇怪解析规则的元素,例如: <td>) (中文(简体) ).
所有援引 framgment() 工厂结果 DocumentFragments,共用同一个所有者 Document 财务报告和审定财务报表 Window。这允许许多电话 fragment() 没有额外的间接费用。 但这也意味着呼吁 fragment() 无法以任何选项定制 .
注意序列化并不容易 DocumentFragment满地都是这样 JSDOM 对象。 如果你需要序列化你的DOM, 你也许应该使用 JSDOM 更直接的构造器。 但对于包含单一元素的碎片的特殊情况,通过正常方式相当容易做到:
const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"
其他值得注意的特征
屏幕支持
jsdom 包括对使用 canvas 或者说 canvas-prebuilt 要扩展任意 <canvas> 包含 API 的元素。 要做到这一点,你需要包括 canvas 作为项目中的依赖者 作为同行 jsdom如果jsdom能够找到 canvas 包,它会使用它, 但如果它不在场, 那么 <canvas> 元素将像 <div>编号.
编码嗅觉
除了提供串 JSDOM 也可以以节点.js的形式提供二进制数据 Buffer 或标准 JavaScript 二进制数据类型 ArrayBuffer, (中文(简体) ), Uint8Array, (中文(简体) ), DataView等 (中文(简体) ). 完成后,Jsdom会 嗅出编码 从提供的字节中扫描 <meta charset> 标记就像浏览器一样.
这种编码嗅觉也适用于 JSDOM.fromFile() 财务报告和审定财务报表 JSDOM.fromURL()在后一种情况下,如浏览器中,任意 Content-Type 随答复而发送的信头将优先处理.
关闭一个jsdom
Jsdom中的计时器( 由 window.setTimeout() 或者说 window.setInterval())顾名思义,将来会在窗口上下文中执行代码. 由于未来没有方法执行代码而不保持进程的生命力,杰出的jsdom定时器将保持你的Node.js进程的生命力. 同样,由于无法在对象上下文中执行代码而不使该对象存活,未完成的jsdom定时器会阻止其预定的窗口的垃圾收集.
如果您想要确定关闭 Jsdom 窗口, 请使用 window.close(),它将终止所有运行的定时器(并删除窗口和文档上的任何事件听众).
在网页浏览器内运行jsdom
jsdom 支持在网页浏览器内运行,使用 浏览器。也就是说,在网页浏览器内,您可以使用浏览器化的jsdom来创建一套完全自成一体的平版JavaScript对象,这些对象看起来和浏览器现有的DOM对象很像,同时完全独立于它们. "虚拟DOM",真的!
jsdom的首要目标仍然是Node.js,因此我们使用仅存在于最近Node.js版本(即Node.js v6+)中的语言特征. 因此,更古老的浏览器很可能无法工作. (即使转接也无济于事:我们计划使用) Proxys 在整个jsdom v10.x.过程中广泛存在.)
值得注意的是,jsdom在网络工人内部运作良好。 原始贡献者, @ 劳伦斯:使这一切成为可能 发表论文 关于他利用这种能力的项目.
在网页浏览器内运行 jsdom 时, 并非一切都完美 。 这有时是因为根本的限制(比如没有文件系统访问),但有时只是因为我们没有花足够的时间做适当的小调. 虫子报告当然是值得欢迎的.
洞穴
同步装入脚本
人们在使用jsdom时经常会遇到同步脚本加载的困难. 许多页面同步地加载脚本,但无法分辨何时完成,因此当是运行您的代码并检查由此产生的DOM结构的好时机时. 这是一个根本性的限制;我们无法预测网页上的脚本会做什么,因此无法在装入更多脚本时告诉你.
这可以有几种方式。 最好的方法是,如果控制相关页面,则使用脚本加载器给出的任何机制,在加载完成后进行检测. 例如,如果使用像RequestJS这样的模块加载器,代码可能看起来像:
// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
console.log("ready to roll!");
};
<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], () => {
window.onModulesLoaded();
});
</script>
如果您不控制页面, 您可以尝试投票等工作方式.
详细情况,请参见下文讨论 640号特别是 @ matthewkastor (英语)是因为 深刻的评论。 。 。 .
共用建筑工和原型机
目前,对于大多数网络平台API,jsdom在多个看起来独立的jsdom之间共享相同的类定义. 这意味着,例如,以下情况可能发生:
const dom1 = new JSDOM();
const dom2 = new JSDOM();
dom1.window.Element.prototype.expando = "blah";
console.log(dom2.window.document.createElement("frameset").expando); // logs "blah"
这主要是出于性能和内存的原因:在网络平台上创建所有众多课程的单独拷贝,每次我们创建jsdom,都会相当昂贵.
然而,我们仍有兴趣有一天提供一种选择,以创造出一种“独立”的jsdom,以牺牲某种表演.
新API中缺失的特性
相较于从v9.x及之前的旧jsdom API,新API明显缺失了对资源负载的精度控制. 以前版本的jsdom允许您设置在请求时使用的选项(既用于初始请求,也用于旧的等同请求) JSDOM.fromURL(),并用于次级资源请求)。 它们也允许您控制哪些子资源被请求并应用到主文档中,以便您可以例如下载样式表而不是脚本. 最后,它们提供了一种自定义的资源加载器,可以让你拦截任何发出的请求,并以完全合成的响应来实现.
这些功能还没有出现在新的jsdom API中,尽管我们希望很快再添加回来! 不幸的是,这要求以合理的方式开展体面的幕后工作.
在此期间,请随意使用旧的jsdom API来获取此功能. 它得到支持和维护,尽管它不会获得新的特征。 文件见 自由/旧阿皮德。 。 。 .
未执行的网络平台部分
虽然我们喜欢在jsdom中添加新的功能并不断更新最新的网页规格,但它有许多缺失的API. 请自由提出一个问题 任何缺失, 但我们是一个小而繁忙的团队, 所以拉请求可能更好.
除了我们还没有达到的特征外, 有两个主要的特征 目前不在jsdom的范围。 它们是:
- 导航: 单击链接或指定时更改全局对象和所有其他对象的能力
location.href或类似. - 版式: 能够计算由于CSS而将哪些元素直观地布置出来,而CSS会影响各种方法,例如:
getBoundingClientRects()或类似属性offsetTop。 。 。 .
目前jsdom对这些功能的某些方面有假行为,例如发送"未执行" "jsdomError" ,或返回许多布局相关属性的零。 通常你可以围绕代码中的这些限制工作,例如创建新的 JSDOM 在爬行时,或使用 Object.defineProperty() 以改变各种布局相关的获取器和方法返回的内容.
注意同一空间中的其他工具,如PhantomJS,确实支持这些特性. 维基百科中的相关条目: 克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克 jsdom对幽灵JS。 。 。 .
获得帮助
如果您需要Jsdom的帮助,请随意使用下列任何场所: