第14~16章 DOM

第十四章,DOM

散碎

NodeList 与 HTMLCollection、NamedNodeMap

  1. 类数组
  2. 实时数组,随文档变化
  3. HTMLCollection 多一个 namedItem 方法(以 name 属性值访问节点)
  4. querySelectorAll 返回 NodeList 快照,并非实时

Node 类型

DOM 一共有 12 种类型,以此为基类,确定类型的方法如下:

Node.类型名 === node.nodeType

类型名如下:

Element 类型(nodeType = 1

  1. 随便打印一个 div 元素,他的原型链是这样的:

HTMLDivElement --> HTMLElement --> Element --> Node --> EventTarget --> Object

  1. tagName 返回 标签名的大写形式

节点关系

.childNodes
  1. NodeList 类数组(这个有:forEach、values、keys、entries
  2. 实时反应此节点的子节点情况,并非快照
  3. 代码换行和空格会成为 text 节点出现在内(.children 不会)
  4. 里面的节点保持有双链表关系(非循环)(指针为:.previousSibling 和 .nextSibling)
.firstChild 和 .lastChild

分别指向 .childNodes[0] 和 .childNodes[childNodes.length - 1]

.hashChildNodes

是否有子节点;返回布尔值

由于空白节点会成为节点出现在 childNodes 上,所以在定位的时候会很麻烦

之前的 childNodes、firstChild、lastChild、previousSibling、nextSibling 都有了替换项

分别是 children、firstElementChild、lastElementChild、previousElementSibling、nextElementSibling

节点操作

注:如果添加的节点已经存在文档中,那么会移动此节点
同一个节点不会在文档中同时出现在两个或更多个地方
parentNode.appendChild(newNode):newNode // 添加节点到所有子节点最后面
parentNode.insertBefore(newNode, childNode):childNode // 添加到指定节点最前面
parentNode.insertBefore(newNode, null):childNode // 添加到子节点最后面
parentNode.replaceChild(newChild, childNode):childNode // 替换子节点
parentNode.removeChild(childNode):childNode // 删除子节点
node.cloneNode(isDeep) // 拷贝节点(不会复制 js 添加的属性 和 事件处理程序
node.createElement('tagName') // 创建 Element 节点
//节点就可以使用(缩小范围),不一定非得 document.
node.getElementByClassName('className')
node.querySelector('css选择器')
//......

属性

下面两种属性大部分都是相通的,互相影响;特殊情况已列出

DOM 属性
const container = document.querySelector('.container');
container.xxx;
HTML 属性
<div class="container" dir="rtl" style="color: red;" onclick="test()"></div>

HTML 属性操作(一般都用于自定义属性,其他用到的地方很少;但其实自定义属性也一般用 DOM 属性的 dataset 了。

container.getAttribute('key'); // 获取
container.setAttribute('key', value); // 添加/替换
container.removeAttribute('key');
DOM 属性 与 HTML 属性差异
  1. class
container.className === container.getAttribute('class'); // true
container.getAttribute('className'); // null
  1. style
container.style; // CSSStyleDeclaration 对象
container.getAttribute('style'); // "color: red;"
  1. 事件处理程序
container.onclick; // test 函数引用,没有则是 null
container.getAttribute('onclick'); // "test()"
  1. 自定义属性
  • 自定义属性不会成为 DOM 属性(但会体现在 attributes 属性上)
  • HTML5 规定自定义属性以 data- 开头,不然不会添加到 DOM 属性的 dataset 对象中
<div class="container" data-self="msg"></div>;

container.getAttribute('data-selef') === container.dataset['self']; // true
attributes

HTML 属性会反映在 DOM 属性 attributes 上

Document 类型(nodeType = 9

全局 document 原型链

HTMLDocument --> Document --> Node

子节点类型

document 的子节点类型只能是

DocumentType(比如)

Element(html)

ProcessingInstruction(xml)

Comment(注释)

iframe 文档跨域

取决于响应头中的 Content-Security-Policy 字段

其他集合

实时集合

  1. .anchors 所有带有 name 属性的 a 元素
  2. forms 所有 forms 元素
  3. images 所有 images 元素
  4. links 所有带有 href 的 a 元素

文档写入

document.write/writeln/open/close

write 和 writeln 就是后者写入后会多个换行;调用如果在加载页面后调用,页面则会被输出内容重写

调用 open 和 close 可以更好的控制何时写入何时结束,open 调用后文档子节点会清空

Text 类型(nodeType = 3

一般来说,大部分业务场景都能用 innerText 代替文本节点的操作方法

echars 的大量数据就用的 appendData

  1. 对于相邻的文本节点,可以执行以下 normalize 方法,将相邻文本节点合成一个
parentNode.normalize();
  1. 操作
textNode.appendData(text); // 往后增加 text
textNode.deleteData(start, count); // 从 start 出开始删除 count 个字符
textNode.insertData(start, text); // 在 start 前添加 text
textNode.replaceData(start, count, text); // 用法类似 array.splice。替换 start 到 start + count 为 text(左闭右开
textNode.splitText(offset); // 从 offset 位置拆分文本,原文本节点保留 [0, offset),返回 [offset, length - 1]
textNode.substringData(start, count); // 从 start 开始提取 count 个字符 [)
doc.createTextNode();

Comment 类型 (nodeType = 8

注释节点,和 Text 节点拥有类似方法,因为他们继承同一基类 CharacterData

document.createComment(); // 创建注释

CDATSection 类型 (nodeType = 4

继承 Text 类型;只在 XML 中有效

DocumentType 类型(nodeType = 10

<!DOCTYPE xxx>

DocumentFragment 类型(nodeType = 11

  1. 继承自 Node 所以可以像 document 那样使用,但是他只作为一个存放节点的仓库
  2. 如果文档中有节点被添加到文档片段,那么这个节点会在文档树中被删除
<div class="pop">hello</div>;
//
const pop = document.querySelector('.pop');
const documentFragment = document.createDocumentFragment();
documentFragment.appendChild(pop);
// pop 会在页面被移除
  1. 添加进文档的时候,文档片段本身不会被添加,只是他的子节点会被添加,且文档片段的子节点会被清空
console.log(documentFragment.childNodes); // NodeList[div.pop]
parentNode.appendChild(documentFragment);
console.log(documentFragment.childNodes); // NodeList[]

Attr 类型(nodeType = 2

一般就是 attributes 属性

document.createAttribute(); // 创建属性节点

表现形式:

将属性作为节点来访问多数情况下并无必要。推荐使用 getAttribute()、 removeAttribute() 和 setAttribute() 方法操作属性,而不是直接操作属性节点。

DOM 编程

动态脚本

  1. 添加外部 js
const script = document.createElement('script');
script.src = 'demo.js';
document.body.appendChild(script);
  1. 添加 js 源代码
const script = document.createElement('script');
script.text = 'function test(){ alert(1) }; test()';
document.body.appendChild(script);

用 innerHTML 添加的 script 代码永远不会执行

动态样式

也是同上可以通过创建 link 或者 style 节点进行添加,往 style 中添加内容可以用 innerText 或者 文本节点的方式都行。

操作表格

同上,但是相当繁琐,所以 table 元素自带了很多操作函数和访问属性

MutationObserver

MutationEvent 已废弃

切勿以为每次改变后回调都会执行,他的形式是 回调+记录队列 的形式;也就是每次改变都会添加到记录队列 mutationRecords 里,回调只在本次执行上下文观察到改变后执行一次

<div class="container"></div>
<div class="footer"></div>
<script>
  const container = document.querySelector('.container');
  const footer = document.querySelector('.footer');

  // Boolean 配置默认值都是 false,至少有一个 Boolean 配置为 true
  const MutationObserverInit = {
    subtree: false, // 是否观察子节点的各种变化
    characterData: false, // 是否观察文本节点变化(注:是文本节点的文本,不是节点的文本
    attributes: true, // 是否观察属性值变化,不包括自定义 DOM 属性;包括 DOM、HTML、自定义 HTML 属性
    childList: false, // 是否观察子节点变化(自身的 childNodes
    characterDataOldValue: false, // 是否需要旧值,体现在 mutatedRecord 实例的 oldValue 上
    attributeOldValue: false, // 是否需要旧值,体现在 mutatedRecord 实例的 oldValue 上
    // attributeFilter: [] // 字符串数组,指定需要观察的属性
  };

  // 异步回调,微任务;会在观察的对象发生范围内的变化时添加到微任务队列中
  const observer = new MutationObserver((mutationRecords, _observer) => {
    console.log(mutationRecords, _observer === observer); // ⑤:[MutationRecord, MutationRecord, MutationRecord] true
  });

  // ①每次改变,改变记录会被存在记录列表,当异步回调执行时传递记录列表过去
  observer.observe(container, MutationObserverInit);
  observer.observe(footer, MutationObserverInit);
  container.className = 'newClass';
  container.id = 'app';
  container.dataset.msg = 'msg';
  container.自定义DOM属性 = '自定义DOM属性值'; // 不会被观察到

  // ②取消观察,属于一刀切,会断开一切观察关系;前面的改变也不会被观察到了;不会结束实例生命周期
  observer.disconnect();
  container.className = 'disconnect-container'; // 不会被观察到
  footer.className = 'discooent-footer'; // 不会被观察到

  // ③任然可以继续观察
  observer.observe(container, MutationObserverInit);
  container.className = 'newClass'; // 至于是在这段代码后执行后添加异步回调,还是同步代码执行完添加,这不重要!
  container.id = 'app';
  container.dataset.msg = 'msg';

  // ④提前取出记录列表,取出后回调不执行,但并未断开连接
  console.log(observer.takeRecords()); // [MutationRecord, MutationRecord, MutationRecord]

  // ⑤这才是回调所打印到的记录
  container.className = 'newClass';
  container.id = 'app';
  container.dataset.msg = 'msg';
</script>
  1. mutationRecords

设置元素文本内容的标准方式是 textContent 属性,innerText 的定义不严谨,浏览器间的实现也存在兼容性问题,因此不建议再使用了。

第十五章,DOM 扩展

元素查找

querySelectorAll 返回 NodeList 快照,并非实时

matches 返回 boolean 是否有这个元素

HTML5

  1. classList 不是数组

DOMTokenList 实例

  1. document.activeElement

始终指向目前的焦点,默认情况下页面加载前为 null ,页面加载完后指向 body

多用于无障碍功能

  1. document.readyState

loading:文档正在加载

complete:文档加载完成

  1. document.compatMode

CSS1Compat:标准模式

BackCompat:混杂模式

  1. outerHTML

返回包括自身的序列化 DOM

<div class="box">1</div>
<script>
  const box = document.querySelector('.box');
  box.outerHTML = '<h1>he</h1>'; // 会取代 <div class="box">1</div>
</script>
  1. scrollIntoView

相对于第一个可滚动容器

/**
  option 为布尔值
    true: 滚动到顶部
    false: 滚动到底部
    
  option 为配置对象
    {
      behavior: 'auto', // 滚动动画 smooth auto 	
      block: 'start', // 滚动到容器中垂直位置 start center end nearest
      inline: 'nearest' // 滚动到容器中水平位置 start center end nearest
    }
*/
element.scrollIntoView(option);

当元素聚焦时,聚焦元素也会滚动到可视范围,默认无动画,居中

  1. contains 与 compareDocumentPosition

判断参数和调用者关系

parentNode.contains(child: Node): boolean // 是否为后代
parentNode.compareDocumentPosition(child: Node): number
// 相对于 child 而言
0x1  不在
0x2  在前面
0x4  在后面
0x8  包含
0x10 被包含

child 是 parentNode 的后代, compareDocumentPosition 会返回 20,0x14; 异于常规思想;因为 child 既被包含,也在 parentNode 后面

  1. innerText 和 outerText

非标准

在用于读取值时, innerText 会按照深度优先的顺序将子树中所有文本节点的值拼接起来。在用于写入值时,innerText 会移除元素的所有后代并插入一个包含该值的文本节点。

第十六章,DOM2 和 DOM3

XML:一种数据格式,多用于存储数据,配置(类似 JSON 的功能

HTML:一种数据格式,多用于定义页面

XHTML:HTML 的严格子集,比如必须闭合标签、必须小写....

本章关于 XML 和 XHTML 略

node.style.float; // ❌
node.style.cssfloat; // ✔
// 节点
node.isSameNode(node: Node): boolean // 是否相同节点(引用相同
node.isEqualNode(node: Node): boolean // 节点是否相等(深克隆

// iframe
iframe.contentDocument
iframe.contentWindow

......