以下内容总结自《js高级程序设计 第三版》
今天来说说JS中的DOM以及DOM操作。
DOM是什么,做了什么?
DOM是针对HTML和XML文档的一个API,也叫文档对象模型。
DOM描绘了一个层次化的节点树,允许开发人员添加,移除,修改页面的某一部分。
DOM并不难,但需要各位同学记忆和了解的部分居多,所以本章少说废话,API部分尽量简单明了,供各位同学参阅。
一、节点层次
刚才说过,DOM是一个节点树,既然是树,那肯定就会分层级。
以HTML为例,它的结构如下
<html>
<head>
<title>ATitle</title>
</head>
<body>
<p>Hello World :)</p>
</body>
</html>
是不是感觉根节点是最外层的<html>标签?诶,并不是。
文档节点才是每个文档(这里指这个HTML页面)的根节点。而文档节点的子节点,才是<html>元素。
我们也叫<html>元素为文档元素,它是元素中的老大,所有的其他元素都包含在<html>中。
每一个文档只能有一个文档元素。在HTML中,始终就是<html>;而在XML中,据随便了,谁都可能成为文档元素。
了解了大概的层级,接下来就得看看每层都是什么类型的节点。毕竟,类型不同,干的活也不一样。
1 Node类型
一个文档中,总会有多种类型的节点配合使用,这样做才会分工明确,逻辑清晰。
在JS中,所有的节点类型都继承自Node类型,所以它们共享着一些相同的基本属性和方法。
我们先来说说都有哪些节点类型,再来讲讲共同属性,方法。
Node.ELEMENT_NODE(1)
Node.ATTRIBUTE_NODE(2)
Node.TEXT_NODE(3)
Node.CDATA_SECTION_NODE(4)
Node.ENTITY_REFERENCE_NODE(5)
Node.ENTITY_NODE(6)
Node.PROCESSING_INSTRUCTION_NODE(7)
Node.COMMENT_NODE(8)
Node.DOCUMENT_NODE(9)
Node.DOCUMENT_TYPE_NODE(10)
Node.DOCUMENT_FRAGMENT_NODE(11)
Node.NOTATION_NODE(12)
每个节点的类型必是其中之一,虽然这里12个类型看着密密麻麻的有点慌,但是不用怕,我们Web浏览器也不是全部都支持的,而且比较常用的就是元素和文本节点。
接下来就说一下节点的属性和方法。
共同属性:
nodeType、nodeName、nodeValue、
childNode、parentNode、
perviousSibling、nextSibling、
firstChild、lastChild、
ownerDocument
nodeType
nodeType 是用来查看节点类型的,我们可以这样做
if(nodeType === Node.ELEMENT_NODE){
console.log('Node is a element')
}
但是由于IE没有公开Node类型的构造函数,所以我们想跨浏览器兼容的话,就得这样做
// 所有浏览器适用
if(nodeType === 1){
console.log('Node is a element')
}
虽然写法更简单了,但是IE坑还是非常多的,珍爱秀发,远离IE。
nodeName和nodeValue
这两个属性的值取决于及节点的类型。
常用的元素节点(ndeoType == 1),它的nodeName就是标签名,而nodeValue是null。
其他类型的话,见到再说吧。
childNodes
在这个属性存放着一个NodeList类对象数组。
这里需要知道的两点:
1 NodeList是一个像数组却不是数组的对象,按顺序存放着一个节点的所有子元素;
2 NodeList是‘活’的,DOM结构变化它就跟着变。
我们可以像使用数组那样使用它
// 使用[]
const firstChild = some.childNodes[0]
// 使用item(n)
const secondChild = some.childNodes.item(1)
// 获取长度
const count = some.childNodes.length
parentNode
父节点,谁包着当前节点,parentNode就指向谁。
perviousSibling、nextSibling
perviousSibling:同级的前面一个节点
nextSibling: 同级的后面一个节点
当前节点是第一个或者最后一个时,perviousSibling或nextSibling为null
firstChild、lastChild
firstChild: 一个节点的第一个子节点
lastChild:一个节点的最后一个子节点
没有子节点firstChild和lastChild都为null
ownerDocument
该属性指向整个文档的文档节点。
共同方法:
适用于有子节点的节点:appendChild()、insertBefore()、replaceChild()、removeChild()
适用于所有节点:cloneChild()、normalize()
appendChild()
向父节点内最后的位置添加一个节点,返回值为插入后的该节点
const returnedNode = someNode.appendChild(someNode.firstChild)
alert(returnedNode === someNode.firstNode) // false
alert(returnedNode === someNode.lastNode) // true
insertBefore()
向父节点内的某个节点前插入一个节点。
第一个参数:要插入的节点,
第二个参数:被插入的节点(为null时,新节点插到最后)
返回值: 插入后的该节点
// 向第一个节点前插入一个节点
someNode.insertBefore(newNode,someNode.firstChild)
replaceChild()
替换一个节点
第一个参数:要插入的节点
第二个参数:被替换的节点
返回值: 替换后的该节点
// 替换第一个节点
someNode.replaceChild(newNode,someNode.firstChild)
removeChild()
移除一个节点
第一个参数:要移除的节点
第二个参数:被替换的节点
返回值: 移除后的该节点
// 移除第一个节点
someNode.removeChild(newNode,someNode.firstChild)
cloneChild()
复制一个节点
参数:true \ false
(true时是深复制,也就是包含该节点和内部的所有子节点;false时,只复制当前节点)
返回值: 节点列表
normalize()
操作文本节点
在某个节点调用该方法时,会去其后代节点中做两件事:
1 删除空文本节点
2 合并相邻文本节点
2、Document类型
前面说过,所有的节点类型都是是继承自Node类型,所以,Document类型节点以及本节后面所有的节点,都是儿子。
Document表示文档。在浏览器中,document对象是HTMLDocument(它又继承自Document类型,所以是孙子辈的)的一个实例,表示整个HTML页面。
而且,document对象又是window对象的一个属性,所以可以将其作为全局对象来访问。
特征:
1 nodeType 值为9
2 nodeName 值为 "#document"
3 nodeVlaue 值为null
4 parentNode 值为null
5 ownerDocument 值为null
6 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment
Document类型的属性
documentElement、body、doctype、title、
URL、domain、referrer、
anchors、applets、forms、images、links、
implementation
>1、documentElement、body、doctype、title
documentElement: 对<html>的引用
body: 对<body>的引用
doctype: 对<!DOCTYPE>的引用、
title: 对<title>的引用
>2、URL、domain、referrer
URL: 当前页面的网址
domain: 当前页面的域名
referrer: 来源页面的URL(当前页面是在哪打开的)
其中domain可以设置,但乱改是不行的,只能是当前URL的子域名。
>3、anchors、applets、forms、images、links
anchors:所有带name特性的<a>元素
applets:所有<applets>元素(不推荐使用,了解即可)
forms:所有<form>元素
images:所有<img>元素
links:所有带href特性的<a>元素
>4、implementation
这个属性是用来提供相关信息和功能的对象,来查看浏览器实现了对应的哪些功能。
它有一个特定的方法hasFeature(),接收两个参数
第一个参数:要检测的DOM功能的名称
第二个参数:要检测的DOM功能的版本号
使用方法如下
// 返回true或false
cosnt result = document.implementation.hasFeature('XML','1.0')
下面是可以检测的值,看看就好。
Document类型的方法
getElementById()、 getElementsByTagName()、getElementsByName()、
write()、writeln()、open()、close()
>1、getElementById()
根据节点id值获取节点,只能获取一个节点
这应该是大家最常用的方法了
>2、getElementsByTagName()
根据标签名获取节点列表(是一个HTMLCollection 类数组对象)
const images = document.getElementByTagName('img')
alert(images.length)
alert(images[0].src)
alert(images.item(0).src)
// 特有方法,根据name属性获取节点
images.namedItem('aName')
// 或者这样写
images['aName']
//-----------------------------------
// 获取全部
document.getElementByTagName('*')
我想聪明的你都能看懂。
>3、getElementsByName()
根据节点的name属性的值获取节点列表。(HTMLDocument类型特有方法)
>4、write()、writeln()、open()、close()
write(): 写入,接受一个字符串参数
writeln(): 在write的基础上在结尾加个 '\n'
open(): 打开网页输入流 // 不怎么用
close(): 关闭网页输入流 // 不怎么用
3、Element类型
又是一个常见的节点类型,Element类型常用于表示XML或HTML元素。
特征:
1 nodeType 值为1
2 nodeName 值为元素的标签名
3 nodeVlaue 值为null
4 parentNode 值可能为Document或Element
5 ownerDocument 值为null
6 其子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntryReference
Element类型的属性
nodeName、tagName、
id、title、lang、dir、className、
attributes
>1、nodeName、tagName
两个属性等价,返回值为元素的标签名。
>2、id、title、lang、dir、className
id:元素id属性
title:元素title属性(鼠标放上去显示的就是title属性)
lang:元素lang属性(语言代码,很少使用)
dir:元素dir属性 (文字排列方向,值为 'ltr'(left-to-right) 或 'rtl'(right-to-left))
className:元素class属性
举个例子
<div id="myDiv" class="bd" title="Body Text" lang="en" dir="ltr"></div>
当然除了获取,也可以给这几个属性直接赋值。
>3、attributes
这个属性厉害了,Element类型专属。
返回一个NameNodeMap类数组对象。其中包含着这个元素的所有属性和值的键值对。
同时,attributes还拥有下列的方法
getNamedItem(name): 返回属性为name的键值对
removeNamedItem(name): 移除属性为name的键值对
setNamedItem(node): 添加键值对,以node.nodeName属性为索引
item(pos): 返回位于数字pos位置处的节点
例
// 获取
const id = element.attributes.getNamedItem('id').nodeValue
// 或者这样访问
const id = element.attributes['id'].nodeValue
// 移除
element.attributes.removeNamedItem('id')
// 添加(不常用)
element.attributes.setNamedItem(newAttr)
attributes的作用
遍历元素的属性,当然不同浏览器遍历得到的属性顺序可能不同 (远离IE,尤其是低版本)
Element类型的方法
getAttributes()、setAttributes()、removeAttributes()
>1、getAttributes()
获取元素属性的方法,标签中属性名是啥,就传入对应的字符串当作参数。
不过有两个特殊的属性名:
1 style: 通过该方法访问返回字符串;通过属性名直接返回一个对象。
2 事件:像onclick这样的事件。通过方法访问得到代码字符串或null;通过属性名访问得到一个函数。
>2、setAttributes()
和getAttributes()对应,设置属性值
接收两个参数
1 属性名
2 属性值
例
div.setAttribute('id','someOtherId')
>3、removeAttributes()
移除属性,不常用
根据个人经验(菜鸡水平)来说,没用过。
一个来自document的方法
document.createment()
作用:创建一个新元素
接受参数:一个,为 标签名
使用:配合Element中给新元素添加属性,最后再用插入元素的方法(忘记的同学回到上面再看看)添加到想放的地方。
4、Text类型
就是文本节点
特征:
1 nodeType 值为3
2 nodeName 值为"#text"
3 nodeVlaue 值为节点所包含的文字
4 parentNode 值是一个Element
5 ownerDocument 值为null
6 没有子节点
Text类型的属性
data、length
>1、data
文本节点的内容,和nodeValue的值相同。
>2、length
字符的数目。
Text类型的方法
appendData()、deleteData()、insertData()、replaceData()、splitText()、subStringData()
>1、appendData(text)
将text添加到节点末尾
>2、deleteData(offset,count)
删除从offset位置开始的的count个字符。
>3、insertData(offset,text)
从offset位置开始插入text。
>4、replaceData(offset,count,text)
用text替换从从offset位置开始的的count个字符。
>5、splitText(offset)
从offset位置分割成两个文本节点
>6、subStringData(offset,count)
提取从offset开始的count个字符组成的文本
来自document的方法
document.createTextNode()
作用:创建新的文本节点
参数:传一个字符串
使用:配合插入节点的方法使用。
规范化文本节点
前面提到过document.normalize()
它的两个作用再提一遍:
1 删除空文本节点
2 合并相邻文本节点
5、Comment类型
注释节点类型。
特征:
1 nodeType 值为8
2 nodeName 值为"#comment"
3 nodeVlaue 值为注释的内容
4 parentNode 值可能是Document或Element
5 没有子节点
Commit类型和Text类型继承自相同的基类,它的方法除了splitText()之外和Text类型没有区别。所以参考上面即可。
6、CDATASection类型
针对XML的文档类型,表示CDATA区域,了解即可。
特征:
1 nodeType 值为4
2 nodeName 值为"#cdata-section"
3 nodeVlaue 值为CDATA区域中的内容
4 parentNode 值可能是Document或Element
5 没有子节点
7、DocumentType类型
不常用,甚至只有部分浏览器支持,了解即可。
特征:
1 nodeType 值为10
2 nodeName 值为doctype的名称
3 nodeVlaue 值为null
4 parentNode 值是Document
5 没有子节点
8、DocumentFragment类型
文档片段节点类型。
特征:
1 nodeType 值为11
2 nodeName 值为"#document-fragment"
3 nodeVlaue 值为null
4 parentNode 值是null
5 子节点可以是Element、ProcessingInstruction、Comment、Text、CDATASection、EntryReference
文档片段节点比较特殊,它就像整个文档掰碎了的碎片,能向文档节点一样操作其内的节点。
可以使用插入节点的方法将文档片段插入到文档中。
应用:
代替多次插入节点,先将所有的节点插入到文档片段中,再将文档片段插入到文档中。
9、Attr类型
元素特性类型。指HTML标签中属性。
特征:
1 nodeType 值为2
2 nodeName 值为特性的名称
3 nodeVlaue 值为特性的值
4 parentNode 值是null
5 子节点:在HTML中没有;在XML中可以是Text或在EntryReference
虽然被定义成节点,但Attr类型被嫌弃了,它不被认可是DOM文档树的一部分。
Attr类型的属性
name、value、specified
>1、name、value、specified
name: 属性名
value: 属性值
specified:布尔值,用来区分是特性是在代码中指定的还是默认的
来自document的方法
document.createAttribute()
作用:创建一个Attr类型节点
参数:属性名(字符串)
使用:使用.value方式赋值,配合setAttributeNode将Attr类型节点放到元素中。
建议
使用Element节点的getAttribute()、setAttribute()、removeAttribute()代替使用Attr节点的属性。
因为更方便,不用再创建Attr类型了。
二、DOM操作技术
"DOM操作是简单的,但IE是坑的。"
所以这里我们抛开IE,拥抱主流。
1、动态脚本
有多种方法能够向页面插入js代码
1 <script></script>内部包含js代码
2 <script> 的src属性引入外部js
3 使用DOM操作生成<script>标签和js代码
动态脚本讲的就是第三种方法。
我们可以这样操作:
var script = document.createElement('script')
script.type = 'text/javascript'
// client.js是一个外部js文件,这里假装有一个
script.src= 'client.js'
document.body.appendChild(script)
这就动态添加了一个脚本。
甚至可以将以上代码封装到一个函数中,在合适的时机进行调用。
2、动态样式
将css包含到页面的方法也有多种
1 使用<link>标签引入外部css文件
2 使用<style>包含css代码
3 使用DOM操作动态生成css代码
举例:
var link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = 'styles.css'
var head = document.getElementsByTagName('head')[0]
head.appendChild(link)
// 或者
var style= document.createElement('style')
style.type = 'text/css'
style.appendChild(document.createElement('body{background-color:red}'))
var head = document.getElementsByTagName('head')[0]
head.appendChild(style)
三、操作表格
首先我要赞美一下各大UI框架,让我们摆脱了又臭又长的表格操作。
首先我们来看看一个基本的表格是什么样的
<table>
<tbody>
<tr>
<td>Cell 1,1</td>
<td>Cell 2,1</td>
</tr>
<tr>
<td>Cell 1,2</td>
<td>Cell 2,2</td>
</tr>
</tbody>
</table>
想一想,每个元素都要使用DOM.createment()方法生成,然后再添加到其父元素中,是不是要疯了!
官方也这么觉得,所以专门为<table>、<tbody>、<tr>元素添加了一些属性和方法:
<table>元素的属性和方法:
1. caption:返回表格的caption元素节点,没有则返回null;
2. tHead, tBodies, tFoot: 返回表格<thead>, <tbody>, <tfoot>元素;
3. rows: 返回元素所有行<tr>元素的HTMLCollection;
4. createTHead(), createTFoot(), createCaption(): 创建<thead>, <tfoot>, <caption>空元素,
将其放到表格中,返回创建的<thead>, <tfoot>, <caption>元素节点;
5. deleteTHead(), deleteTFoot(), deleteCaption(): 删除<thead>, <tfoot>, <caption>空元素,
无返回值(或返回值为undefined)
6. deleteRow(pos): 删除指定位置(注意参数不是索引,而是从0开始的位置)的行,返回undefined;
7. insertRow(pos): 向rows集合中的指定位置(不是索引)插入一行;
<tbody>元素的属性和方法:
1. rows: 返回<tbody>元素下所有行<tr>元素的HTMLCollection;
2. deleteRow(pos): 删除指定位置(注意参数不是索引,而是从0开始的位置)的行,返回undefined;
3. insertRow(pos):向rows集合中的指定位置(不是索引)插入一行;
<tr>元素的属性和方法:
1. cells: 返回<tr>元素中单元格的HTMLCollection;
2. deleteCell(pos): 删除指定位置(不是索引)的单元格;
3. insertCell(pos): 向cells集合中的指定位置(不是索引)插入一个单元格,返回对新插入单元格的引用;
虽然简化了表格操作,但还是很麻烦,大伙留个印象即可,用到的时候再来看看。