DOM大锅炖(红宝书笔记)

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的数据,建立另外开辟一个对象保存有用的属性。不要影响目标节点的回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值