基于官方规范系列篇之——HTML 篇(三)Document 对象Document 对象相关的知识; 资源元数据管理的相关
大家好呀,我是前端创可贴。
在我们的页面上,有一个非常重要的对象,我们可能不经常直接用到它(尤其是在如今框架大行其道的项目中),但是其实在它背后隐藏了非常多的功能与机制,我们在项目中用到的很多概念都跟它有关,要想对我们的页面有一个更高层次的了解,我们一定绕不开它,它就是—— Document
对象。
每一个 HTML 和 XML 文档,在 HTML 的 User Agent(用户代理,比如浏览器)中都会用一个 Document
对象来表示。
Document 对象
URL
Document 对象的 URL 是在 DOM 中定义的,在创建 Document 对象时被初始化设置。
在 Document 对象的生命周期内 URL 的值可以发生变化。注意是在同一个 Document 对象的生命周期,即还在当前页面,没有跳转到别的页面,也就是没有刷新。如果触发了刷新,那就是一个新的 Document 对象的生命周期了。
这也是一道面试题,面试官会问:使用 JavaScript 如何改变页面的 URL 而不刷新页面?
- 浏览器跳转到当前页面中的特定位置或元素,也就是改变 hash,hash 值为页面元素的 id 值;
- 调用
history.pushState()
方法传入一个新 URL。
学习过 Vue Router 和 React Router 的同学应该比较熟悉,这就是路由的两种模式的基本实现原理。
源
源(origin) 其实是跟 Document 对象紧密相关的,我们常说的同源策略,就是基于 Document 对象的源来决定哪些资源和脚本可以访问和交互。
Document 对象的源(origin)是在 DOM 中定义的,在创建 Document 对象时被初始化设置,并且只有在 document.domain
变化时才会发生改变。Document 的源可以与其 URL 的源不同,比如 iframe 被创建时,就算这个 iframe 的初始 URL 是 about:blank
,即空页面,它的 origin 仍然可以继承父文档的 origin。
创建 Document 对象
我们不仅仅可以通过网络请求加载 HTML 文件、创建 iframe 元素等从而创建 Document 对象,我们还可以自己通过 JavaScript 来创建 Document 对象:
- 通过
createDocument
方法创建 XML 文档; - 通过
createHTMLDocument
方法创建 HTML 文档。 - 通过
Document
构造函数创建一个新的 Document 对象。
通过这些方式创建的 Document 对象是在内存中创建的,不涉及网络请求,不需要经历传统的加载过程(从网络获取内容、解析 HTML 等),它们一创建就已经是完整状态,可以直接进行操作,例如修改 DOM 结构、添加节点、设置属性等,所以性能相对较好,适合需要频繁操作 DOM 的场景。
<h1>前端创可贴</h1>
<h2>我是 Document 的元素</h2>
<script>
// 使用 createHTMLDocument() 创建一个 HTML 文档
const htmlDoc = document.implementation.createHTMLDocument(
"你好呀,createHTMLDocument"
);
// 添加内容到 body 中
const h2 = htmlDoc.createElement("h2");
h2.textContent = "我是新创建 Document 的元素";
htmlDoc.body.appendChild(h2);
console.log(htmlDoc.documentElement.outerHTML); // <html><head><title>你好呀,createHTMLDocument</title></head><body><h2>我是新创建 Document 的元素</h2></body></html>
document.body.appendChild(htmlDoc.documentElement)
</script>
策略容器
每个 Document 对象都有一个策略容器(policy container),这个容器中保存了一些影响文档行为的策略,包括内容安全策略(CSP)、Referrer 策略(Referrer Policy),以及其他控制文档执行和资源加载的策略。
策略容器确保了在加载、显示和交互的过程中,文档遵循某些规定和安全性约束。策略容器的存在是为了集中管理文档的安全和权限设置,从而提升安全性和可管理性。
策略容器的内容会影响文档的执行环境,比如说哪些资源可以加载、哪些 API 可以使用,以及如何处理跨域请求等。
对于我们开发者来说,我们可以修改一些策略从而影响到它。
- 比如常见的 CSP:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
- 还有我们有时会设置 referrer:
<meta name="referrer" content="no-referrer">
权限策略
每个 Document 对象都有一个权限策略(Permissions Policy),其初始值是空。
权限策略是一种机制,用于控制和限制哪些特定的浏览器功能或 API 可以在页面中使用。在没有显式定义之前,所有的权限控制都是未设定的状态,一般是默认的允许或不允许。
其主要目的是提高网站的安全性和隐私控制,使开发者可以限制或允许某些功能的使用。这对于保护用户数据、减少潜在安全漏洞还是很重要的。
对于我们开发者来说,可以通过以下方式定义或修改权限策略:
- HTTP 响应头设置权限策略:通过服务器响应头
Permissions-Policy
设置权限策略。
Permissions-Policy: geolocation=(), microphone=()
该响应头就设置了当前文档禁止访问地理位置和麦克风功能。
- HTML 属性设置:可以在嵌入的内容(例如 iframe)中使用 allow 属性设置策略。
<iframe src="example.html" allow="fullscreen; geolocation 'self'"></iframe>
这就设置了 iframe 中的内容可以使用全屏和地理位置功能,但地理位置只能在同源 (self) 的情况下使用。
模块映射
每个 Document 对象都有一个模块映射(module map),初始值是空。
模块映射跟我们代码的模块化强相关,它是浏览器内部的一种数据结构,用于管理和跟踪 JavaScript 模块的加载和解析。模块映射帮助浏览器知道哪些模块已经加载,哪些正在加载,哪些加载失败了,以及它们之间的依赖关系。这对于我们现代的 JavaScript 应用特别重要,因为模块化的代码结构依赖于高效的模块加载和依赖管理。
模块映射的 key 是一个元组,由一个 URL 记录和一个字符串组成,URL 记录是用于获取模块的请求 URL,字符串表示模块的类型,如 JavaScript 模块("javascript")或 WebAssembly 模块("wasm")。
模块映射的 value 可以是一个模块脚本、null
(用于表示获取失败),或占位值 "fetching"
。
模块映射确保在每个文档或 worker 中,导入的模块脚本只被获取、解析和执行一次。
我们通过 import 关键字引入模块代码:
<script type="module">
import { myFunction } from './myModule.js';
myFunction();
</script>
跨域开放者策略
每个 Document 对象都有一个跨域开放者策略(cross-origin opener policy, COOP)。
跨域开放者策略是用于增强网页安全性的一个机制,它控制一个网页和它打开的或打开它的其他网页之间的共享资源和通信行为。
具体来说,COOP 主要用于隔离不同来源的上下文,确保文档不会和跨域的文档共享浏览上下文,防止跨域的安全风险,例如数据泄露和跨站脚本攻击(XSS)。
浏览上下文(browsing context),是指在浏览器中用于显示文档的环境,它可以是一个窗口、标签页、iframe,或者其他能够呈现文档的区域。例如在一个浏览器标签页内加载的 HTML 页面,它的浏览上下文是该标签页;而例如通过上面说的 createHTMLDocument 创建出来的文档,如果没有被插入到 DOM 中,则它的浏览上下文为 null。
介绍完 COOP 的基本概念后,我们先一起来看看 window 对象上的 opener 属性,该属性会返回打开当前页面的 window 对象的引用,并且是通过 window.open
或者带有 target 属性的 a 标签(如果 target 是 _blank,相当于设置了 rel="noopener"
,即不会设置 window.opener
,除非显式地指定 rel="opener"
)。简单来说,页面 A 打开页面 B,页面 B 的 window.opener
属性返回 页面 A 的 window 对象。
假设我们现在有两个页面,源页面 A 和新打开的新页面 B,页面 A 通过 window.open 或者带有 target 属性的 a 标签打开了页面 B,此时分为两种情况:
- 同源
-
- 页面 A:
<h1>Page A</h1>
<a href="B.html" target="_blank" rel="opener">打开 B 页面</a>
<script>
window.A = 'This is page A';
</script>
-
- 页面 B:
<h1>Page B</h1>
<script>
console.log(window.opener);
</script>
- 非同源
-
- 页面 A:
<h1>Page A</h1>
<div onclick="window.open('https://baidu.com')">打开百度</div>
<script>
window.A = 'This is page A';
</script>
-
- 百度页面:
可以看到,同源时,新打开的页面 B 可以获取源页面 A 的 window 对象。注意如果页面 A 关闭了,页面 B 的 opener 值就会变成 null。
不同源时,也可以获取到 window 对象,只不过对象的功能是受限的,变量和函数都是获取不到的。但是可以改变源页面 A 的 URL,所以还是有安全风险,比如你的网站打开了一个跨域的第三方恶意网站,恶意网站可以通过 window.opener.location.replace('钓鱼网站')
修改源页面 A 的 URL,换成跟你的网站长得很像的钓鱼网站,导致用户受骗。
所以建议当你的网站嵌入第三方网站的链接时,设置 rel="noopener"
,即可避开上述安全问题。
window.open('第三方网站 url', '_blank', 'noopener')
说完了 opener 属性,再来看看 COOP 如何设置。COOP 是通过 HTTP 响应头来设置的:
- same-origin:只允许同源页面共享同一浏览上下文。设置了以后,新打开的非同源页面的
window.opener
值变为 null。
Cross-Origin-Opener-Policy: same-origin
- same-origin-allow-popups:允许同源页面共享上下文,但不限制弹出窗口(例如 window.open)。设置了以后,如果新打开的非同源页面没有设置 COOP 或设置为 unsafe-none,则
window.opener
有值,否则为 null。
Cross-Origin-Opener-Policy: same-origin-allow-popups
- unsafe-none(默认值):不进行任何限制,允许所有来源的页面共享上下文。
Cross-Origin-Opener-Policy: unsafe-none
还是用上面的源页面 A 和新打开的新页面 B 举例,COOP 的设置可以分别设置在页面 A 和页面 B 上,每个页面的 COOP 设置都会影响它们之间的交互和隔离行为。
当在页面 A 上设置 COOP,它会影响从 A 打开的所有新窗口或标签页(包括页面 B)的引用保留情况。例如,如果页面 A 设置为 same-origin-allow-popups
,它允许与新开的弹出窗口保持引用(window.opener 有值),前提是页面 B 没有设置严格的隔离策略。
当在页面 B 设置了 COOP,它的设置会影响自己对页面 A 的引用保留。例如,如果页面 B 设置为 same-origin
,即使页面 A 设置为 same-origin-allow-popups
,window.opener
仍然会是 null
,因为 B 的设置优先将自己与 A 隔离开来。
is initial about:blank
每个文档都有一个 is initial about:blank
属性,这是一个布尔值,初始值为 false
。该属性用于标识当前文档是否是最初的 about:blank
文档。
加载期间导航 ID
每个 Document 对象都有一个用于 WebDriver BiDi 协议的加载期间导航 ID(during-loading navigation ID),它的值是一个导航 ID(在导航时生成的一个 UUID 字符串) 或 null,初始值为 null。
它是一个标识文档在加载过程中(即正在加载过程中)的唯一 ID,这个 ID 用于跟踪和管理 WebDriver BiDi 的导航过程。
WebDriver BiDi(WebDriver Bidirectional)是一个用于实现浏览器自动化的协议,支持双向通信,以便更好地控制和获取浏览器的状态信息。这个协议我们前端开发是接触不到的,它主要面向测试工具和自动化框架的开发者。通过这些测试工具遵循 WebDriver BiDi 协议,可以完全自动化地模拟用户在浏览器中的操作,测试应用的用户界面和交互等,可以省去很多测试时间。
推荐一些常用的支持 WebDriver BiDi 的工具:Selenium、Puppeteer、WebDriverIO,可以用来进行 UI 测试、性能测试、端到端测试等。
about base URL
每个 Document 对象都有一个 about base URL,值是一个 URL 或 null,初始值为 null。
它用于存储文档的 base URL 信息,但它只对使用 "about:" 协议的文档(如 about:blank)相关,对于其他协议的文档(例如 http:、https:),这个属性不会被使用,因此它保持 null。
about base URL 是为了在解析特定情况下的 URL 时保持一致性。例如,在 "about:" 协议的上下文中,可能需要一个特定的基准 URL 来解析相对路径。
前进后退缓存阻塞详细信息
每个 Document 对象都有一个 bfcache 阻塞详细信息(bfcache blocking details),它是一个未恢复原因详细信息的集合,初始值为空。
bfcache(Back-Forward Cache)是浏览器的一种缓存机制,用于快速加载前进和后退导航的页面。它缓存了页面的完整状态,用户在使用浏览器的前进和后退按钮时能够快速加载页面,而无需重新请求服务器。大家平时在浏览一些网站,前进后退页面的时候会发现,很多时候加载的速度特别快,并且也没有发送任何请求,就是因为 bfcache 机制。
但是在某些情况下,文档可能无法被缓存到 bfcache 中。这些阻塞详细信息会列出阻止文档进入 bfcache 的原因。常见的原因有:
- 页面使用了不支持 bfcache 的 API,例如
window.onunload
。 - 页面中有未正确处理的生命周期事件。
- 使用了不安全的缓存策略或涉及敏感数据的操作。
- 动态修改了页面的内容,使其不能安全地从缓存中恢复。
在浏览器控制台是可以看到 bfcache 的信息的,Application -> Back/forward cache。
点击 Test back/forward cache 按钮即可测试当前页面是否可使用 bfcache 缓存。
我们可以通过观察 bfcache 的状态,对页面的缓存行为进行诊断和优化,提升用户在前进和后退导航时的体验和性能。
资源元数据管理
referrer
Document 对象的 referrer 属性是一个字符串,表示访问当前文档的来源 URL,比如从页面 A 跳转到页面 B 时,referrer 就是页面 A 的 URL。
<h1>Page A</h1>
<a href="B.html" target="_blank">打开 B 页面</a>
通过 noreferrer 的方式可以使浏览器不发送 Referer 头,因此目标站点不会知道用户是从哪里过来的。
<a href="B.html" target="_blank" rel="noreferrer">打开 B 页面</a>
window.open('B.html', '_blank', 'noreferrer');
或者
<meta name="referrer" content="no-referrer">
该属性一般常见用于防止 CSRF 安全问题的方案之一、服务器(如 Nginx)实现防盗链功能等。
cookie
如果一个 Document 对象满足以下任一条件,则该对象是一个拒绝 cookie 的 Document 对象(cookie-averse Document):
- 该 Document 对象的浏览上下文为
null
。 - 该 Document 的 URL 的协议不是 HTTP 或 HTTPS。
通过 document.cookie
返回文档的 HTTP cookies。如果没有 cookies 或者 cookies 无法应用于该资源,将返回空字符串。如果文档是一个拒绝 cookie 的 Document 对象,用户代理必须返回空字符串。
通过 document.cookie
也可以添加一个新的 cookie,设置时只需要设置新添加的字段,会追加在原来的值里面。如果文档是一个拒绝 cookie 的 Document 对象,用户代理不会做任何操作。
如果文档内容被限制在一个不透明的源(opaque origin)中(例如,在带有 sandbox
属性的 iframe 中),在获取和设置 cookie 时都将抛出一个 "SecurityError" DOMException。
<iframe srcdoc="<h1>我是 iframe 元素</h1>" sandbox="" name="myIframe"></iframe>
<script>
// 带有 name 属性的 iframe 元素可以通过 document.xxx 的形式获取元素,后面的 命名属性访问 小节有详细介绍
const iframe = document.myIframe;
iframe.document.cookie = 'name=前端创可贴';
</script>
大家注意,cookie 属性的获取和设置操作存在同步性问题,因为 cookie 属性的获取和设置是同步访问共享状态的,由于没有锁机制,在多进程用户代理中,其他浏览上下文可以在脚本运行时修改 cookies。例如,一个网站可能尝试读取一个 cookie,增加它的值,然后将新的值写回去,将该 cookie 的新值用作会话的唯一标识符;如果该网站在两个不同的浏览器窗口中同时执行这个操作,可能会导致两个会话使用相同的“唯一”标识符,从而产生潜在的灾难性后果。这种竞态条件(race condition)可能会导致会话管理出现问题,例如多个用户会话被错误地识别为同一会话,从而引发安全问题或功能异常。
lastModified
document.lastModified
返回 Document 源文件最新修改日期和时间,并且是本地的时区。
返回值格式为:月/日/年 时:分:秒
,即MM/DD/YYYY hh:mm:ss
。
除了年份之外,其他数字都必须以两个 ASCII 数字表示,使用十进制数表示,并在必要时进行零填充。年份必须以最短的四位或更多 ASCII 数字字符串表示,用十进制数表示,并在必要时进行零填充。
文档源文件的 lastModified 日期和时间必须从所使用的网络协议的相关特性中得出,例如,从文档的 HTTP Last-Modified
头的值,或从本地文件系统中的元数据得出。如果最后修改日期和时间未知,则该属性必须返回当前的日期和时间。
一般都是根据 HTML 文件的 HTTP Response Headers 中的 Last-Modified 的值,例如 MDN 首页的 HTML 请求中,response header 的 Last-Modified 的值为下图:
document.lastModified
的值即为 header 中的值,并且会加上本地时区:
而如果 HTML 文件的 Response Headers 中没有 Last-Modified,那么document.lastModified
的值即为当前时间,比如百度首页的 HTML 文件是没有 Last-Modified 响应头的,则 document.lastModified
会显示当前时间。
文档加载状态
readyState
document.readyState
获取文档当前的加载状态,在文档的加载过程中,不同时期会返回不同状态:
- 当文档正在加载时,返回 "loading"。
- 当文档解析完成但仍在加载子资源时,返回 "interactive"。类似于
DOMContentLoaded
事件。 - 当文档和所有子资源加载完成时,返回 "complete"。类似于
load
事件。
初始值为 complete
,在 Document 对象被创建后中,document.readyState
的初始值会立即被重置为 loading
。
该值发生变化时,会在 Document 对象上触发 readystatechange
事件。
document.addEventListener("readystatechange", () => {
console.log("readyState 变成了", document.readyState);
});
在状态从 "loading" 过渡到 "interactive" 后,但在过渡到 "complete" 之前,会触发 DOMContentLoaded
事件。
浏览器内部机制更新 readyState 的值为 readinessValue 的具体流程为:
- 检查现有值:如果文档的当前状态 (document.readyState) 已经等于 readinessValue,则不做任何操作并返回。该步骤是为了避免在状态未改变的情况下进行不必要的更新。
- 更新状态:将文档的当前状态 (
document.readyState
) 设置为新的readinessValue
。
- 如果文档关联了 HTML 解析器:
-
- 获取当前时间:将 now 设为由全局对象(例如 window 对象)提供的当前高精度时间(high resolution time,基于 monotonic clock time,用于度量时间间隔,相比普通的系统时钟,High Resolution Time 提供了更细粒度的时间测量能力,常用于对时间要求非常高的应用,比如性能分析、动画帧渲染、硬件驱动的调度等,并且不会受到系统时间更改的影响。我们熟知的
performance.now()
的结果就是一个 high resolution time)。
- 获取当前时间:将 now 设为由全局对象(例如 window 对象)提供的当前高精度时间(high resolution time,基于 monotonic clock time,用于度量时间间隔,相比普通的系统时钟,High Resolution Time 提供了更细粒度的时间测量能力,常用于对时间要求非常高的应用,比如性能分析、动画帧渲染、硬件驱动的调度等,并且不会受到系统时间更改的影响。我们熟知的
-
- 设置加载时间信息:
-
-
- 如果 readinessValue 为 "complete",且文档的加载时间信息中
DOM complete time
****为 0(表示尚未设置),则将该值设置为 now。 - 否则,如果 readinessValue 为 "interactive",且文档的加载时间信息中
DOM interactive time
为 0,则将该值设置为 now。
- 如果 readinessValue 为 "complete",且文档的加载时间信息中
-
- 在文档上触发一个名为
readystatechange
的事件。每当文档的就绪状态发生变化时,都会触发此事件。
文档加载时间信息
每个 Document 对象都有一个文档加载时间信息(document load timing info),包含了以下部分,每个部分的值都是 DOMHighResTimeStamp
类型(用来存储以毫秒为单位的一段时间,代表了上面提到的 high resolution time。例如 performance.now()
的结果就是 DOMHighResTimeStamp 类型)的数值,用于精确测量文档加载的各个阶段的时间点。默认情况下,这些值都为 0,随着文档的加载和解析,这些时间点会被更新。
- navigation start time: 表示从用户发起导航(如输入 URL、点击链接或重定向)开始的时间点,是整个加载过程的起始点。默认值为 0。
- DOM interactive time:
表示文档被完全解析为可交互状态的时间点,这时 DOM 树已构建完成,但子资源(如图片、样式表等)可能仍在加载中,
document.readyState
值变为 interactive。默认值为 0。
- DOM content loaded event start time:
表示
DOMContentLoaded
事件开始触发的时间点,这是在 DOM 树完全解析后触发的,但此时不保证所有的外部资源已经加载完成。默认值为 0。
- DOM content loaded event end time:
表示
DOMContentLoaded
事件处理完成的时间点,这时所有绑定在该事件上的脚本都已执行完毕。默认值为 0。
- DOM complete time:
表示文档以及所有子资源(如图片、样式表、脚本等)都已经加载完毕的时间点,
document.readyState
变为 "complete"。默认值为 0。
- load event start time:
表示
load
事件开始触发的时间点,这是在所有资源都完全加载完成后触发的事件。默认值为 0。
- load event end time:
表示
load
事件处理完成的时间点,这时所有绑定在load
事件上的脚本都已执行完毕。默认值为 0。
结合上面的 readyState,我们来验证一下文档加载时间信息的顺序:
<img src="xxx.png" alt="" onload="console.log('img 图片加载完成')" />
<script>
document.addEventListener("readystatechange", () => {
console.log("readyState 变成了", document.readyState);
});
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded 事件触发");
});
window.addEventListener("load", () => {
console.log("load 事件触发");
});
</script>
文档卸载时间信息
每个 Document 对象都有一个文档卸载时间信息(document unload timing info)包含以下部分,每个部分的值也都是 DOMHighResTimeStamp 类型的数值,用于精确测量文档卸载过程的时间点。默认情况下,这些值都为 0,随着文档的卸载过程,这些时间点会被更新。
- unload event start time:
表示
unload
事件开始触发的时间点。unload
事件是在文档即将卸载之前触发的,通常用于清理操作,如保存用户数据或取消未完成的请求。默认值为 0。
- unload event end time:
表示
unload
事件处理完成的时间点。这时,所有与unload
事件相关的脚本都已经执行完毕,文档即将从浏览器中移除。默认值为 0。
DOM 树访问器
head
document.head
返回 html 元素的第一个 head 类型子元素,如果没有就返回空。
页面指定多个 head 元素时,会被合并成一个 head 元素,子元素会被合并在一起。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<head>
<meta charset="UTF-16" />
<title>前端创可贴</title>
</head>
<body></body>
</html>
title
document.title
返回文档中第一个 title 元素,如果没有就返回空。
可以赋值 document.title
直接修改标题,title 元素的值也会跟着被修改。
body
body element 是 html 元素的第一个 body 类型子元素,要么是 body 元素,要么是 frameset 元素,要么就是空。
frameset 元素是一种在 HTML 中用于定义框架集(frameset)的标签,可以将浏览器窗口划分为多个框架(frame),每个框架可以独立地加载不同的 HTML 文档。这个元素在早期的网页布局中比较常见,尤其在 HTML 4 中广泛使用。frameset 元素取代了 body 元素,用于定义一组框架,不能与 body 元素同时使用。
当然这个元素已经被废弃了,它对可访问性、用户体验和搜索引擎优化(SEO)有负面影响。现代网页布局通常使用 CSS 的 flexbox、grid、iframe 等来代替框架布局。此外,使用 frameset 的页面不支持 body,会导致脚本和样式的管理更加复杂。
document.body
返回 body element。也可以给它赋值直接修改 body,值必须是 body 或者 frameset 元素,否则会抛出错误。
images
document.images
返回一个 HTMLCollection,包含文档中所有的 img 元素。
embeds
document.embeds
返回一个 HTMLCollection,包含文档中所有的 embed 元素(用于嵌入外部内容,如插件、视频等)。
plugins
document.plugins
结果同上面的 document.embeds
。
links
document.links
返回一个 HTMLCollection,包含具有 href 属性的 a 元素和 area 元素。
forms
document.forms
返回一个 HTMLCollection,包含文档中所有的表单 form 元素。
scripts
document.scripts
返回一个 HTMLCollection,包含文档中所有的 script 元素。
getElementsByName
getElementsByName(elementName) 方法返回一个 NodeList,包含文档中所有具有 name 属性且其值与 elementName 参数相同的 HTML 元素,按照 DOM 树的顺序排列。
<div name="name">前端创可贴</div>
<div name="name">这是一个 div</div>
<span name="name">这是一个 span</span>
<script>
console.log(document.getElementsByName('name'));
</script>
该方法返回的是一个 NodeList 对象,它是动态的,如果文档中的符合条件的元素发生变化(例如添加或删除元素),NodeList 会自动更新,反映当前的文档状态。
为了提高性能,当对同一个 Document 对象使用相同的参数调用 getElementsByName 方法时,浏览器可能会返回与之前调用时相同的 NodeList 对象(即复用之前的对象)。如果方法是用不同的参数调用,或者其他情况下,则必须返回一个新的 NodeList 对象。
currentScript
如果文档正在执行 JavaScript 代码,document.currentScript
会返回当前正在执行的 script 元素。Document 对象被创建后,currentScript 属性值会被初始化为 null。
<script>
console.log(document.currentScript);
</script>
在递归脚本执行的情况下(例如脚本调用自身或触发其他脚本),会返回最近开始执行但尚未完成的脚本元素。
如果文档当前没有执行任何脚本(例如当前运行的脚本是通过事件触发的,或者是由 setTimeout 设置的延时任务等),则返回 null。
如果当前正在执行的脚本是模块化脚本(module script),而不是普通脚本,也会返回 null。
不过这个属性已经开始被遗弃了,因为 document.currentScript
会在全局范围内暴露正在执行的 script
元素,这种全局暴露的方式很不安全,尤其是在一些新的应用场景中,比如模块脚本和 Shadow DOM 中执行的脚本,这些新环境中的脚本具有更严格的隔离性和隐私要求,需要避免全局暴露的风险,所以该属性在这些场景下是不可用的。
命名属性访问
在 HTML 中,Document 支持命名属性(Named Properties),这些属性可以通过名称直接在 Document 对象上访问,例如 document["someName"]
。这些命名属性与 DOM 树中的特定元素相关联,并且是从某些 HTML 元素的 name 或 id 属性派生而来的。
命名属性来源于特定的元素(如 embed、form、iframe、img 和 object):
- embed、form、iframe、img 和暴露的 object 元素的 name 属性值,前提是这些元素的 name 属性不为空。
- 暴露的 object 元素的 id 属性值,前提是 id 属性不为空。
- img 元素的 id 属性值,前提是该元素同时具有非空的 id 和 name 属性。
<iframe
srcdoc="<h1>前端创可贴</h1>"
frameborder="0"
name="myIframe"
></iframe>
<object data="https://baidu.com" type="text/html" name="myObject"></object>
<img src="xxx" alt="" name="myImg" />
浏览器会根据以下步骤确定命名属性 name
的值:
- 获取 DOM 树种具有指定名称
name
的元素列表。 - 如果只有一个元素,并且是一个
iframe
元素且其内容可导航(content navigable
)不为空,则返回该iframe
元素的活动 WindowProxy 对象。 - 如果只有一个元素,则直接返回该元素。
- 如果有多个元素,则返回一个以 Document 节点为根的 HTMLCollection,该集合只包含具有指定名称
name
的命名元素。
对于 embed
和 object
元素,如果它们没有暴露的 object
祖先元素,则被称为“暴露的”。对于 object
元素,如果其未显示后备内容或者没有 object
或 embed
后代元素,也会被视为是暴露的。
结束语
这一章我们一起学习了 Document 对象相关的知识,包括 URL、Origin、策略容器、权限策略、跨域开放者策略、bfcache 等,有一些是我们前端开发者接触不到的底层知识,但是它们是真正存在的,学习他们可以帮助我们更好的了解和优化我们的页面。
也学习了资源元数据管理的相关知识,referrer、cookie、lastModified 属性是我们平时较为常用的知识,涉及到我们页面的隐私安全、资源凭证等方面。
还学习了文档加载状态的相关内容,学习到了 readyState、文档加载时间信息和文档卸载时间信息,这对于我们理解页面的生命周期、内容渲染顺序等非常有帮助。
最后还学习 DOM 树访问器相关内容,方便我们在开发中获取想要的元素。
这一章也只是介绍了 Document 对象的一部分内容,大家也可以看到仅仅这一部分内容就已经非常重要了,Document 对象背后代表的是整个文档,所以想要更好的理解我们的页面,我们就必须要掌握 Document 对象。
那么这一章的介绍就结束了,咱们下一章再见啦。
欢迎关注我的公众号,前端创可贴。
转载自:https://juejin.cn/post/7414772107672256563