DOM
1. 节点层级
1.1 Node类型
DOM Level 1 描述了Node接口,所有DOM节点类型都必须实现,每个节点都有nodeType
- Node.ELMENT_NODE
- Node.ATTRIBUTE_NODE
- Node.TEXT_NODE
- Node.CDATA_SECTION_NODE
- Node.ENTITY_REFERENCE_NODE
- Node.ENTITY_NODE
- Node.PROCESSING_INSTRUCTION_NODE
- Node.COMMENT_NODE
- Node.DOCUMENT_NODE
- Node.DOCUMENT_TYPE_NODE
- Node.DOCUMENT_FRAGMENT_NODE
- Node.NOTATION_NODE
1.1.1 nodeName和nodeValue
nodeName 标签名
nodeValue null
1.1.2 节点关系
- childNodes 是一个类数组(NodeList的实例),可以用索引访问,有length属性。childNodes保存的不是一个快照而是实时获取的。
item方法
let secondChild = someNode.chlidNodes.item(1);
转换为数组
let arrayOfNodes = Array.from(someNode.childNodes);
- parentNode: 指向父节点
- previousSibling:指向上一个兄弟元素
- nextSibling:指向下一个兄弟元素
- hasChildNodes( ): 判断有没有子节点
- ownerDocument:指向自己所属的文档
1.1.3 操纵节点
- appendChild:插入节点,返回被插入的节点
let returnedNode = someNode.appendChlid(newNode);
returnedNode == newNode // true
someNode.lastChild == newNode // true
如果节点本身已经存在于dom树中,那么他会从之前的位置移动到新位置
- insertBefore:在当前元素前面插入新节点,返回新插入的节点
let returnedNode = someNode.insertBefore(newNode, null); // 最后一个节点
let returnedNode = someNode.insertBefore(newNode, someNode.firstChild); // 作为第一个节点插入
- replaceChild:替换节点,返回插入的节点
let returnedChild = someNode.replaceChild(newNode, oldNode);
- removeChild:移除节点
let removedChild = someNode.removeChild(node);
repalceChild removeChild 节点虽然被删除但是任然存在于文档中,只是没有他们的位置而已。
这些方法都需要获得parentNode才能下一步操作。
1.1.4 其他方法
- cloneNode:克隆某一个节点
let deepList = myList.cloneNode(true);
let shallowList = myList.cloneNode(false);
// 深拷贝:拷贝当前节点以及下面的子树
// 浅拷贝:仅拷贝当前节点,
cloneNode方法不会复制js属性,比如事件处理函数
- normalize:合并textNode成为一个新节点
1.2 Document类型
在浏览器中,document是HTMLDocument的实例,document是window的一个属性
- nodeType: 9
- nodeName: ‘#document’
- nodeValue: null
- parentNode: null
- ownerDocument: null
1.2.1 文档子节点
- documentElement属性
let html = document.documentElement;
html === document.childNodes[0]; // true
html === document.fistChild; // true
- body属性
let body = document.body;
- doctype属性
let type = document.doctype;
1.2.2 文档信息
- title
let originalTitle = document.title;
document.title = 'new Title'; // 修改title,但是并不会反映到title标签上
- URL: 只读
let url = document.url;
// http://www.baidu.com/query
- domain:可写可读,但是是有限制的,一旦放松就不能收缩
let domain = document.domain;
// www.baidu.com
www.wrox.com // 1 初始状态
wrox.com // 2 放松成功
p2p.wrox.com // 3 收缩错误!
- referrer:只读
let domain = document.domain;
domain应用场景:当页面存在子页面(frame 或者 iframe)。一个页面domain 是 www.wrox.com ,其中一个子页面p2p.wrox.com这时由于安全策略,两个页面不能直接通信。那么我们把子页面domain设置为wrox.com 便可以相互访问js对象。
1.2.3 定位元素(由Document类型提供)
- getElementById:通过ID获取元素第一个元素
- getElementsByTagName:通过标签名获取元素
在HTML文档中这个方法返回一个HTMLCollection对象 NodeList 和HTMLCollection 都是实时的(动态获取的)。
let images = document.getElementsByTagName('img');
let myImage = images.namedItem('myImage'); // 获取name等于myImage的元素
let myImage1 = images.item(1); // 获取第一个元素
let myImage2 = iamges[1]; // 内部调用item方法
let mayImage3 = images['myImage']; // 内部调用namedItem方法
let allElements = document.getElementsByTagName('*'); // 获取所有元素
getElementsByTagName:在html页面中实际上是不区分大小写的,但是XML页面中是严格区分的
- getElementsByName:根据name属性获取元素
let namedElement = document.getElementsByName('myName');
返回的集合上同样由 namedItem(只会取到第一项)、 item、[ ] 三种方法访问具体元素
1.2.4 特殊集合
- document.anchors:包含文档中所有带name属性的a标签
- document.applets:文档中所有applet标签
- document.forms:包含所有的form元素
- document.images:所有图片标签
- doucment.links:所有代友href属性的a标签
1.2.5 DOM兼容性检测
- document.implementation:一个对象,上面定义了hasFeature方法
let hasXmlDom = document.impletation.hasFeature('XML', '1.0');
这个方法已经废弃,无论检测什么值都返回true
1.2.6 文档写入
- write
<script type="text/script">
// 错误,这样写会因为</script>标签提前被匹配出现问题
document.wirte("<script></script>");
// 正确
document.wirte("<script><\/script>");
</script>
<script type="text/script">
window.onload = function() {
document.write('Hello World'); // 在文档渲染之后再write会重写整个文档(覆盖)
}
</script>
-
writeLn
-
open
-
close
open 和 close 开启文档的输出流,有write和writeLn的情况下,这些方法不是必要的。
在严格XHTML文档中以上的方法都不起作用。
1.3 Element类型
Element类型表示暴露元素标签名,子节点和属性的能力
- nodeType:1
- nodeName:标签名
- nodeValue:null
- parentNode:Document或者Element对象
let div = document.getElementById("myDiv");
div.tagName === "DIV"
div.nodeName === "DIV"
// 通用方法:判断元素标签的时候全部toLowerCase
1.3.1 HTML元素
所有的HTML元素都通过HTMLElement类型表示,包括他的直接实例或者间接实例。HTMLElement直接继承了Element并添加了一些自有属性。
- id:元素在文档中的唯一标识
- title:包含元素的额外信息,通常以提示条展示
- lang:语言
- dir:语言的书写方向 ltr rtl
- className:类名
1.3.2 获得属性
- getAttribute:获得属性
- setAttribute:设置属性
- removeAttribute:移除属性
通过DOM对象属性直接访问和通过getAttribute取得的值是不一样的。
- style
用getAttribute获得的是一个CSS字符串
属性获取的是一个CSSStyleDeclaration
- 事件处理函数
getAttribute获得的是一个字符串源代码
而属性获得的是一个函数
总结:进行DOM编程的时候不会用getAttribute,而获取自定义属性的时候才用getAttribute方法。
属性名是不区分大小写的。根据HTML5规范要求,自定义属性 " data- "开头以方便验证以及收集。
1.3.3 attributes属性
Element 类型是唯一一个使用attributes属性的DOM节点类型。attributes属性包含一个NamedNodeMao实例,是一个类似NodeList的实时集合。包括下列方法
- getNamedItem(name): 返回nodeName等于name的节点
- removeNamedItem(name):删除nodeName等于name的节点
- setNamedItem(node): 向列表添加node节点
- item(pos):返回索引位置pos处的节点
let id = element.attributes.getNamedItem('id').nodeValue;
let id = element.attributes['id'];
let oldAttr = element.attributes.removeNamedItem('id'); // 移除元素id属性
function outputAttributes(element) {
let pairs = [];
for(let i = 0; i < element.attributes.length; i++) {
const attribute = element.attributes[i];
pairs.push(`${attribute.nodeName} = ${attribute.nodeValue}`);
}
pairs.join(' ');
}
1.3.4 创建元素
document.createElement(“div”)
1.3.5 元素后代
<ul>
<li>*3
获取所有元素的办法(不要空格)
for(let i = 0; i < element.childNodes.length; i++) {
if(element.childNodes[i].nodeType === 1) { // 是元素节点
}
}
1.4 Text类型
纯文本(不包好html代码),如果text中包含html代码,那么他会被转义(< $lt;)
- nodeType: 3
- nodeName: ‘#text’
- nodeValue: 文本值
- parentNode: Element对象
- 不支持子节点
访问值
- nodeValue
- data
两个属性都可以访问到文本,改变一个也回影响另外一个
操作方法
- appendData(text): 向文本后面加入字符串
- deleteData(offset, count): 从offset开始删除coount个字符
- insertData(offset, text):在offset开始加入text
- replaceData(offset, count, text):用text替换从位置offset到offset+count的文本
- splitText(offset):从offset位置拆分成两个文本节点
- substringData(offset, count):提取count长文本
1.4.1 创建文本节点
let textNode = document.createTextNode('balabala');
let element = document.createElement("div");
element.appendChild(textNode);
1.4.2 规范化文本节点
通过父节点调用normalize方法合并多个textNode
element.appendChild(textNode1);
element.appendChild(textNode2);
element.childNodes.length; // 2
element.normalize()
element.childNodes.length; // 1
1.4.3 拆分文本节点
splitText(offset)
分成下面两个节点
[0, offset - 1]
[offset, end]
1.5 Comment类型
注释节点
- nodeType: 8
- nodeName: ‘#comment’
- nodeValue: ‘注释的内容’
- parentNode: Document或者Element对象
- 不支持子节点
Comment类型和Text类型继承同一个基类(CharacterData),所以comment类型拥有处splitText之外Text类型所有的方法
1. 6 CDATASection类型
标识CDATA区块。CDATASection继承Text类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idVbZ30T-1640353993216)(images/1.png)]
- nodeType: 4
- nodeName: ‘#cdata-section’
- nodeValue: CDATA区块的内容
- parentNode: Document或者Element对象
<div>
<![CDATA[This is some content]]
</div>
只有在XML文档中有效,浏览器一般不支持这种类型
1.7 DocumentType类型
doctype信息
- nodeType: 10
- nodeName: ‘文档类型的名称’
- nodeValue: null
- parentNode: Document
- 不支持子节点
三个属性
- name(通常为html)
- entities
- notations
document.doctype.name // html
1.8 DocumentFragment类型
documentFragment是一种轻量级的文档。能够包含和操作节点
- nodeType: 11
- nodeName: ‘#document-fragment’
- nodeValue: null
- parentNode: null
let fragment = document.createDocumentFragment();
let ul = list;
fragment.appendChild(document.createTextNode('balabala')) * 3
ul.appendChild(fragment);
1.9 Attr类型
元素的属性,存在于元素的attributes对象中
- nodeType: 2
- nodeName: '属性名
- nodeValue: null
- parentNode: null
属性
- name
- value
- specified: 是否自己指定的值(非默认值)
let attr = document.createAttribute("align");
attr.value = 'left';
element.setAttribute(attr);
总结:感觉没啥用。直接用setAttribute getAttribute 那一套就够了
2. DOM编程
下次再整理
3. MutationObserver接口
观察整个文档,DOM树的一部分,当dom别修改时异步执行回调。引入其是为了取代废弃的MutationEvent
3.1 基本用法
// 创建
let observer = new MutationObserver(() => {})
3.1.1 observer方法
observer.observe(document.body, {attributes: true}); // 监听document.body下面的属性
3.1.2回调和MutationRecords
let observer = new MutationObserver((mutationRecords) => {
})
mutationRecords 作为容器存放 mutationRecord(修改记录)
let observer = new MutationObserver = ((mutationRecords) => {
});
observer.observe(document.body, {attributes: true});
document.body.className = 'foo';
document.body.className = 'bar';
document.body.className = 'baz';
[mutationRecord, mutationRecord, mutationRecord]
属性
- target: 别修改影响的节点
- type: [attributes | characterData | childList]
- oldValue: 如果mutationObserverInit对象启用(attributeOldValue和characterDataOldValue 为true), attributes和characterData的会出现旧值,但是childList始终是null
- attributeName:对于attributes的变化,这里会记录
- attributeNamespace:使用了命名空间的变化,则保存被修改属性名字。其他设置为null
- addedNodes:childList变化,默认是空
- removedNodes:被移除的节点
- previousSibling:变化节点的前一个
- nextSibling:下一个
3.1.3 disconnect
只要被观察的元素没有被垃圾回收,那么observe就不会断,如果想主动断开,可以用disconnect方法。而且不仅会停止此后事件的监听,而且前面已经加入任务队列的也会被清除,如下
let observer = new MutationObserver(() = > {console.log('改变了')}]);
observer.observe(document.body, {attributes: true});
document.body.className = 'foo';
observer.disconnect();
document.body.className = 'bar';
// 不会有任何输出
要想要有输出,可以用setTimeout包裹 disconect。
setTimeout(() => {
observer.disconnect();
document.body.className = 'bar';
}, 0);
3.1.4 复用MutationObserver
对于一个observer实例多次调用observe()方法可以建立多个观察
3.1.5 重用MutationObserver
调用disconnect, 该observer的声明周期并不是直接走向毁灭。我们可以重新调用observe开启该observer
setTimeout(() => {
disconnect()
someThingchange // 不会有反应
}, 0)
setTimeout(() => {
observe();
someThingChange // 有反应
})
3.2 MutationObserverInit 和观察范围
属性
- subtree: 布尔值,是否观察子节点
- arrtibutes:布尔值,是否观察属性变化
- arrtibuteFilter:表示哪些属性是要被观察的,默认观察全部
- arrtibuteOldValue:布尔值,把这个设置为true会把arrtibutes也设置成true,默认false
- characterData:布尔值,字符串数据是否触发变化事件
- characterDataOldValue:布尔值,是否记录oldValue
- childList:布尔值,修改目标子节点是否触发事件
调用observe之前, characterData attributes chldList 三个一定要有一个是true 无论是通过显示开启true还是通过oldValue
3.2.1 观察属性
observer.observe(document.body, { attributes: true, attributeFilter: ['foo']});
// 只记录foo的变化
3.2.2 观察字符串
observer.observe(document.body.firstChild, {characterData: true});
document.body.firstChild.textContent = 'foo';
document.body.firstChild.textContent = 'bar';
document.body.firstChild.textContent = 'baz';
// 默认不会有oldValue,开启characterDataOldValue才有
3.2.3 观察子节点(添加, 删除)
observer.observe(document.body, {childList: true});
需要注意的对于节点重新排序(Eg: insertBefore(document.body.firstChild, document.boyd.lastChild)) 会触发两次 一次删除一次加入
3.2.4 观察子树
observer.observe(document.body, {subTree: true});
这个和childList不同,subTree是整棵子树变化而不单单是子节点❗️
即使节点被移出该树结构,还能检测到他的变化👊
3.3 异步回调和记录队列
3.3.1 记录队列
每一个MutationObserver都有一个记录队列(唯一),记录曾经发生的变化。只有当之前没有已已经入队的微任务,才会将回调传入微任务队列。
MutationRecord实例的数组,顺序记录变化,回调处理完这个数组,这些记录就会被清空。
3.3.2 takeRecords方法
document.body.className = 'foo';
document.body.className = 'bar';
document.body.className = 'baz';
observer.takeRecords() // [rc, rc, rc]; 并且回调将不会执行
3.4 性能、内存和垃圾回收
DOM Level 2 的mutationEvent 有严重性能问题,被抛弃。MutationObserver将回调委托给微任务来保证同步。
3.4.1 MutationObserver的引用
MutationObserver实例保存对目标节点的弱引用,不会影响目标节点的垃圾回收。而目标节点保存对MutationObsever的强引用,若目标节点没了那观察者也没了。
3.4.2 MutationRecord的引用
一个MutationRecord至少保存一个节点的引用。处理每个mutationRecord可以耗尽记录队列,然后超出作用域并被回收
总结:如果需要保存mutationRecord的数据,建立另外开辟一个对象保存有用的属性。不要影响目标节点的回收。
。只有当之前没有已已经入队的微任务,才会将回调传入微任务队列。MutationRecord实例的数组,顺序记录变化,回调处理完这个数组,这些记录就会被清空。
3.3.2 takeRecords方法
document.body.className = 'foo';
document.body.className = 'bar';
document.body.className = 'baz';
observer.takeRecords() // [rc, rc, rc]; 并且回调将不会执行
3.4 性能、内存和垃圾回收
DOM Level 2 的mutationEvent 有严重性能问题,被抛弃。MutationObserver将回调委托给微任务来保证同步。
3.4.1 MutationObserver的引用
MutationObserver实例保存对目标节点的弱引用,不会影响目标节点的垃圾回收。而目标节点保存对MutationObsever的强引用,若目标节点没了那观察者也没了。
3.4.2 MutationRecord的引用
一个MutationRecord至少保存一个节点的引用。处理每个mutationRecord可以耗尽记录队列,然后超出作用域并被回收
总结:如果需要保存mutationRecord的数据,建立另外开辟一个对象保存有用的属性。不要影响目标节点的回收。