操作网页内容(html和css),js中只是实现了DOM接口
节点层级
正如第一章而言,浏览器会将html解析成节点树(属性节点不在树上),基本上所有内容都是节点,所以节点也有很多类型,当然浏览器并不是支持所有节点
节点类型(部分)
1.Node类型
所有类型的基类,所以其上的方法和属性在每个节点都有实现
属性
nodeType:数值,不同数值代表类型不同- nodeName:取决于节点类型,比如元素节点就是其标签名
- nodeValue:取决于节点类型,如果是文本节点就是其文本值,元素节点则为null
childNodes:保存其子节点,值为NodeList实例,同一个childNodes中的元素称为同胞节点
NodeList:类Array,但是是动态的,并不是某次html的快照
- firstChild/lastChild:指向第一个/最后一个节点
- parentNode:指向其父节点
方法
主要是操作childNodes中的元素(包括自身所在的childNodes和自身包含的)
nextSibling/previousSibling():返回当前元素所在的childNodes中的后/前一个同胞节点,如果没有则为null- hasChildNodes():如果存在子节点,则为true
appendChild(节点类型):向childNodes列表末尾添加节点,会更新其中的lastChild属性insertBefore(添加节点,参照节点):insert添加节点before参照节点,参照节点为null时则添加到最后replaceChild(插入节点,被替换节点):replace插入节点到被替换节点removeChild(节点类型):返回移除的节点- cloneNode(Boolean):返回调用该方法的节点的副本,true时会将ChildNodes列表也拷贝,false则ChildNodes为空列表,即没有子列表,注意副本是
孤儿节点,还没有父元素指向他,无法被浏览器渲染 normalize():将childNodes中相邻的文本节点合并为一个文本节点
2.Document类型
网页中,会直接暴露出document实例对象,但是document是HTMLDocument实例,而HTMLDocument继承自document,因此document上的方法其实是Document上没有的,但是学习Document类型就是为了学习document,所以这里主要讲解document
属性
- nodeType:9
- nodeName:'#document'
- nodeValue=parentNode=ownerDocument=null
- documentElement:指向的是<html>对应的节点
- body:指向的是<body>对应的节点
- childNodes:只能是DocumentType、Element、Comment和ProcessingInstruction类型
以下为HTMLDocument暴露的,Document上没有
- title:元素<title>的文本值,修改后会反应到网页标题,但是不会反应到元素文本的修改
- URL/domain/referrer:能设置的只有domain,而且必须设置成URL中包含的值,用于解决<iframe>跨域问题
- forms/images/links:指向包含文档中所有的form、img、带有href属性的a标签的
HTMLCollaction
HTMLCollaction基本上与NodeList相同,些许区别在于其上存在方法namedItem(名字字符串),可用于查找其中元素的name属性等于该值的元素,也可简写为[名字字符串],如果有多项,则只返回第一项
方法
- getElementById(字符串):返回Id为查找字符串的节点
- getElementsByTagName(标签字符串):返回所有查找标签对应的节点的
HTMLCollaction列表,HTML中不区分大小写 - createElement(name):返回新建的Element节点,标签名为name
- createTextNode(文本):返回创建的文本节点
- createAttribute(name):返回创建的属性节点,属性名为name
- createDocumentFragment():返回创建的文档片段节点
<img src='image.jpg' name='myImage'>
const images = document.getElementByTagName('img');
const myImage = images.namedItem('myImage');
//const myImage = images['myImage'];
以下为HTMLDocument暴露
- getElementsByName()/getElementsByClassName():根据属性name、class返回HTMLDocument列表
- write/writeln(html字符串):将字符串以html形式写入网页,后者会在后面加个换行符;注意会立即写入网页,如同之前就在网页中,但是注意如果字符串中包含</script>,会立即匹配最外层的<script>导致脚本直接结束,必须使用
<\\/scripat>才能进行动态脚本
document.write('\<sctipt type=\'text.javascript\' src=\'file.js\'>'+'<\\/scripat>'); - open/close():打开和关闭网页输出流,当然使用write/writeln()时并不是必须使用这两个
function createDoc(){
var doc=document.open("text/html","replace");
var txt="<!DOCTYPE html><html><body>Learning about the HTML DOM is fun!</body></html>";
doc.write(txt);
doc.close();
//上面等价于(不用写open和close):
//document.write("<!DOCTYPE html><html><body>Learning about the HTML DOM is fun!</body></html>");
}
createDoc()
3. Element类型
元素节点,能暴露出元素标签名、子节点和属性的能力,注意属性也是节点;但是所有的元素是HTMLElement类型的实例,而HTMLElement继承自Element
属性
- nodeType=1
- nodeName=HTML中为大写标签名
- nodeValue=null
- parentNode可以是Document和Element类型
- childNodes可以是Element、Text、Comment、ProcessingInstruction、EntityReference类型
以下为HTMLElement增加的属性
- id/title/lang/dir:获取并可修改对应元素属性
- className:获取并可修改元素属性class,由于在js中class为关键字,所以只能使用className来代替
- attributes:值为
NamedNodeMap实例,类似于NodeList实时节点集合,元素是属性节点Attr(后续章节会讲);该属性使用场景为迭代所有属性
NamedNodeMap对象暴露如下方法:
- getNamedItem(name):获取属性名为name的属性,可简写为[name]
- removeNamedItem(name):移除属性name
- setNamedItem(node):向列表中增加node节点,以其nodeName为索引
- item(pos):返回索引位置为pos处的节点
方法
- getAttribute(属性名):获取属性(不区分大小写)的属性值
- setAttribute(属性名,属性值):将属性设置为属性值,如果不存在则新增
- removeAttribute(属性名):移除属性
获取修改元素属性的方法小结
- 总共三种方法:
- 直接在节点以属性形式访问和修改
- getAttribute/setAttribute()
- attributes属性,以及getAttributeNode/setAttributeNode()
- 区别
- 第一种不能访问和修改自定义属性,最常用
element.mycolor = 'red'; console.log(div.getAttribute('mycolor'));//null
- 第二种主要用于访问自定义属性,与第一种的主要差别在于:
- style属性,第一种会返回CSSStyleDeclaration对象,第二种返回字符串
- 处理程序,第一种会返回js函数,第二种会返回源码字符串
- 第三种最麻烦,主要用于迭代属性
- getElementById/getElementsByClassName():从
后代节点中进行查找并返回
4.Attr类型
属性节点,但是并不认为处于DOM树中,注意添加方式
属性
- nodeType=2
- nodeName=属性名
- nodeValue=属性值
parentNode:null,说明属性节点并不是element节点的子节点- childNodes:null,不支持子节点
- name=nodeName
- value=nodeValue
- specified:如果属性值是在代码中设置的,则返回true,如果为默认值,则返回false
方法
自身没有什么方法,添加到element节点上时,使用的是特定方法
setAttributeNode(Attr),而不是appendChild()
5.Text类型
只要开始标签和结束标签之间有内容(包括空格),就会创建一个文本节点,继承自CharacterData
属性
- nodeType=3
- nodeName='#text'
- nodeValue=文本内容,可进行修改,注意文本值中的HTML代码:大于小于引号会被转换成实体编码,如大于号变为>
- parentNode为Element类型
- childNodes:null,不支持子节点
- data=nodeValue
方法
都是操作的文本
- appendData(text):向文本末尾添加text
- deleteData(offset,count):从位置offset开始删除count个字符
- insertData(offset,text):在位置offset插入text
- replaceData(offset,count,text):从offset到offset+count替换为text
- splitText(offset):从offset位置将文本节点拆分成两个文本节点,与document.normalize()相反
- substringData(offset,count):提取从offset到offset+count的文本
6.Comment类型
注释所对应的节点,和Text节点一样继承自CharacterData
属性
- nodeType=8
- nodeName='#comment'
- nodeValue=注释内容
- parentNode:Document或Element对象
- childNodes:null,不支持子节点
- data=nodeValue
方法
- 由于和Text节点继承同一个,所以基本方法都是一样的,只是没有splitText()方法
7. DocumentFragment类型
节点仓库(中转站),由于每次挂载一个节点后,浏览器就会更新渲染一遍,如果连续挂载多个节点,则会多次渲染,故此退出了DocumentFragment
属性
- nodeType=11
- nodeName='#document-fragment'
- nodeValue:null
- parentNode:null
- childNodes:可以是element、comment、text类型
documentFragment无法被添加进DOM树中,如果DOM树中某节点被添加到documentFragment中,则其会从DOM树中删除,当调用appendChild()/insertBefore()方法时,并将documentFragment对象作为参数传入,并不是将documentFragment对象添加进去,而是将挂载在其下的所有节点都添加到相应位置
let fragment = document.createDocumentFragment();
let ul = document.getElementById('myList');
for(let i = 0;i<3;++i){
let li = document.createElement('li');
li.appendChild(document.createTextNode('Item ${i}'));
fragment.appendChild(li);
}
ul.appendChild(fragment);//此时会将fragment中所有子节点都加入ul之下
DOM编程
就是对上面的各节点进行创建、属性更改
1.动态添加脚本
通过js代码添加脚本,其实就是操作script标签上的两个属性:src或text,当然text还可以使用添加文本节点的方式加入
//操作src
script.src = url;
//操作text
script.text='()=>{console.log(123)};';
//添加Text节点
script.appendChild(document.createTextNode('()=>{console.log(123)};'));
2. 动态样式
添加样式可以使用link和style标签,前者链接到外部,后者可以在内部添加,注意这两个标签一般都是head标签子节点
//操作link节点
link.rel='stylesheet';
link.type= 'text/css';
link.href = url;
//操作style下的text节点
style.type= 'text/css';
style.appendChild(document.createTextNode('body{background-color:red}'));
//操作style的styleSheet属性
style.type= 'text/css';
style.styleSheet.cssText = 'body{background-color:red}';
3.操作表格
为了能更快的创建表格,而不是一步步的创建table、tbody、tr、td元素,DOM为table节点提供了一些属性和方法,能更快的创建一个表格
4,使用NodeList
NodeList、NamedNodeMap和HTMLCollection都是“实时的”,一旦元素增删后都会立即变化,包括其length属性
MutationObserver接口
用于指定监视DOM树特定部位是否发生变化,如果变化则调用处理程序
构建
const observer = new MutationObserver(回调函数)
- 回调函数是宏任务,所以可能在
同步代码中会变化很多次,但是最终回调只会执行一次- 调用时会给回调函数传入参数:
MutationRecord实例的数组,每次突变都会产生一个MutationRecord实例入队,等到调用时将整个队列变为数组后传入回调函数中;
方法
- observe(DOM节点,
MutationObserverInit对象):对DOM节点进行监控,监控信息由MutationObserverInit实例(后续单独讲解)来指定,同时可以多次调用observe(),来观察不同DOM节点
MutationObserverInit
- 普通对象,只是属性名固定为以下,以及属性值也有对应的限制
- childList子节点只是儿子节点,而subtree子树则是节点所有后代
- attribute、characterData 和childList 属性必须至少有一项为true
![]()
let observer = new MutationObserver(() => console.log(' attributes changed')); // 观测body节点的属性,如果变化则触发异步回调 observer.observe(document.body, { attributes: true }); document.body.className = 'foo'; console.log('Changed body class'); // Changed body class // attributes changed
- disconnect():将观测的所有DOM节点与observer断开(
一刀切),但是observer并不会被回收,依然存在;注意如果此时任务队列中还有对应的未执行的异步回调,也会抛弃不执行
MutationRecord
突变后会将对应的MutationRecord实例压入队列,等到回调时,会将队列按顺序变为数组传入回调函数中;
属性
作用
- 观察属性
- attribute:true
- attributeFilter:[属性名],设置白名单,监视这些属性,可以不设置,默认监控所有属性
- attributeOldValue:如果为true则会在本次的MutationRecord.oldValue中保存被替换的属性值
- 观察字符数据
只能观察Text/Comment节点
- characterData:true
- characterDataOldValue:如果为true则会在本次的MutationRecord.oldValue中保存被替换的属性值
- 观察子节点
观察节点的增删
- childList:true
- 观察子树
注意必须与上面三个结合,不能只设置其为true,且被移出子树的节点发生变化,依然会触发事件变化
- subtree:true
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords));
observer.observe(document.body, { attributes: true }); //监视属性值的变化
document.body.className = 'foo';
document.body.className = 'bar';
document.body.className = 'baz';
// 虽然上面有三处更改,但是最后只会调用一次回调函数
// [MutationRecord, MutationRecord, MutationRecord]