DOM模型
文档对象模型是HTML和XML文件的一个API,是一个层次化的节点树模型。DOM有1-3级规范。不需要了解规范的具体定义,但是目前来学的DOM模型,都是1级的内容,也就是最基础和最标准,得到所有浏览器支持的DOM模型,也是今后DOM2,3级新增的API操作的基础。 学习JS的目的,就是为了操作DOM模型,到了DOM模型,才逐渐接触到JS的核心。节点 node
DOM对象又浏览器在装载完页面的所有HTML元素之后生成,用来表示当前的这个HTML文档。DOM对象在JS里是document对象,这是一个全局对象,也是window对象的属性。 DOM模型就是由节点组成的,每一个节点对应文档中的一部分信息,可以看做是一个对象,有各自的特点、数据和方法。几乎所有的获得节点对象的方法,都是从document对象开始进行操作的。 一共有12种节点类型,每一个节点都必然是12种节点的一种。这12种节点的类型都继承自Node类型,因此也拥有一些共同的方法和属性。每个节点都有一个nodetype
属性,表示节点的类型,是一个数值,对应着Node类型中定义的12个类型常量。
节点类型 | |
---|---|
节点名称 | 值 |
Node.ELEMENT_NODE | 1 |
Node.ATTRIBUTE_NODE | 2 |
Node.TEXT_NODE | 3 |
Node.CDATA_SELECTION_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 |
nodeName,nodeType 和 nodeValue
三个属性。对于元素节点,nodeName是HTML标签的名字,nodeValue始终为null。
先来看一些节点的共通属性和方法,之后再看一些常用节点的独特之处。
通用节点关系与操作
节点之间关系主要是父节点与子节点,节点与后代之间的关系。在实际操作中,主要关心父节点与子节点的关系。下边的方法和属性是所有节点都具备的,注意,属性全部都是只读的,即操作节点无法通过修改下面的属性来实现:节点关系 | |
---|---|
属性或方法 | 解释 |
Node.childNodes | 这个属性表示节点的所有子元素(直接的下层元素),返回一个NodeList对象,类似于一个数组,可以通过下标索引,.item(index)的方法取元素,还可以.length来获得其中的元素个数。也可以遍历其中元素进行操作。这个属性是动态的,每次执行的时候返回当时的所有子元素。 |
Node.parentNode | 这个属性指向父元素,由于父元素只有一个,所以这个属性返回一个节点对象。 |
Node.previousSibling | 前一个同胞元素,如果是第一个节点,则返回null。注意,有可能返回的不是元素节点,看这里 |
Node.nextSibling | 下一个同胞元素,如果是最后一个节点,返回null。 |
Node.firstChild | 第一个子节点,注意有可能不是元素节点。没有则返回null。 |
Node.lastChild | 最后一个子节点,注意同样可能不是元素节点。没有则返回null。 |
Node.hasChildNodes() | 直接以布尔值返回是否有子节点,比自己判断要快,推荐使用该方法。 |
Node.ownerDocument | 这个属性所有节点都有,直接定位到整个文档的文档节点 |
节点操作 | |
---|---|
属性或方法 | 解释 |
Node.appendChild(Node) | 向节点的.childNodes列表的末尾添加参数节点。添加之后所有关系指针都会更新。这个方法返回新增的节点。 |
Node.insertBefore(newNode,indexNode) | 将新节点插入到Node.childNodes里的参照节点的前边,作为参照节点的同级节点。返回被插入的节点。如果参照节点是null,效果等于appendChild方法。 |
Node.repalceChild(newNode,targetNode) | 替换Node的目标节点为新节点。所有的关系指针都会更新,返回要替换的节点。如果用变量接收要替换的节点,该节点依然存在内存空间中,但不在文档的显示流里。 |
Node.removeChild(node) | 移除一个子节点。返回被移除的子节点。上边四个方法都必须先取得父节点然后操作子节点。 |
Node.cloneNode(Boolean) | 复制节点,如果为true表示深复制,复制节点及其下的整个节点树,如果为false表示浅复制,只复制节点本身(如果是一个元素节点,其中的text部分也不会复制,只有一个空白的标签。这个方法返回复制的节点,复制返回的节点并不在文档显示流内,需要手工插入文档。 |
Node.normalize() | 对当前节点的所有后代节点查找两种情况,一是文本节点不含文本(含有换行等特殊字符也算文本),二是有连续的文本节点。对于情况1将其删除,对于情况2将其合并。 |
各种类型的节点
前边介绍了有12种类型的节点,其中有部分与XML文档相关。实际在操作DOM的时候,常用的节点有document, element, text, comment, documentType, documenFragment
类型以及要了解的attr
类型。这其中最重要的是document 和 element
类型。
Document 类型
一个文档内,只有一个document类型的节点,就是文档对象document对象。document是HTMLDocument的一个实例,还是window对象的属性,因此可以作为全局对象来访问。
document节点的特点:nodeType
的值为9nodeName
的值为#documentnodeValue
的值为nullparentNode的值为null
,因为这是最顶层节点ownerDocument
的值是null,要特别注意,并不是document对象。
document的子节点可能是一个DocumentType节点(最多一个,就是文档开始的声明),一个元素节点(最多一个,就是<HTML>元素),ProcessingInstruction或Comment节点。
document对象的属性与方法 | ||
---|---|---|
种类 | 属性或方法 | 解释 |
常用子节点 | .documentElement | 指向页面内的HTML元素,比通过间接方法获取要快。 |
.body | 指向页面内的BODY元素,比间接获得要快。 | |
.doctype | 指向DocumentType子节点,也比间接获得要快。 | |
.title | 浏览器窗口的标题。该属性虽然等于页面内title元素的文本内容,但修改该属性只会改变浏览器标签里的页面标题,并不会改变title元素的文本内容。(在Chrome里也修改了title元素的文本内容) | |
文档信息 | .URL | 取得当前页面完整的URL。 |
.domain | 取得当前页面的主机名。可以设置该属性,但不能超出当前页面的域。 | |
.referrer | 取得来源页面的URL,比如当前页面A是从某页面B跳转而来,就会取得B的URL。这三个文档信息都来自HTTP请求头。 | |
查找元素 | .getElementById(id) | 用过一万次的方法,通过ID查找元素,由于页面中ID唯一,所以返回的就是一个element节点对象。找不到就返回null。注意查找id的时候区分大小写,需要严格匹配 |
.getElementsByTagName(tagname) | 第二个用过一万次的方法,根据HTML标签名查找元素,返回的对象是一个类似数组的HTMLCollection对象,可以通过索引拿其中元素,.length获得其中元素数量。参数传入"*"表示获得所有元素。 | |
.getElementsByName(name) | 按照HTML标签中的name属性来获取元素。常用在选择单选按钮上。 | |
特殊查找元素 | .anchors | 返回文档中所有有 name 属性的 a 元素。 |
forms | 返回文档中所有的 form 元素,便于快捷选中表单 | |
images | 返回文档中所有的 img 元素,用于快捷定位图片 | |
.links | 返回所有带href属性的 a 元素 | |
检测DOM一致性 | .implementation.hasFeature(name,version) | DOM分为很多功能模块与版本,用该方法如果返回true,表示浏览器支持DOM的某个功能。详细可看高程三。 |
文档写入 | .write(string) | 执行到该位置的write方法时,将字符串写入当前HTML文档流,用于动态的添加内容。如果在文档载入完毕才执行该方法,则string会直接替代整个页面。 |
.writeln(string) | 与write相同,会在字符串末尾增加换行符 | |
.open() | 无参数,用于打开页面的输出流。 | |
.close() | 无参数,用于关闭页面的输出流。写入方法不是必要的时候,不要使用。通过操作元素来改变页面内容。 |
Element 类型
除了Document之外,Element 类型是最常使用的节点对象。简单的说,Element 对象就是HTML的元素标签,通过element,可以访问一个HTML元素的属性,文字内容,子和后代等等所有内容,是用JS操作页面具体表现形式的核心节点对象。Element 节点的特点:
.nodeType
的值为1.nodeName
的值是标签名.nodeValue
的值是null.parentNode
的值要么是document对象,要么也是一个element 节点- 子节点可能是element, text, comment和其他一些不重要的节点
类别 | 方法或属性名 | 解释 |
---|---|---|
HTML属性 | .tagName | 获得元素的HTML标签名称,虽然有.nodeName这个属性,但更常用这个属性直接获得。注意,这个属性输出的都是大写,因为按照HTML规范标签都是大写,实践中常转换成小写字母进行比较。该属性只读,无法设置。 |
.id | 返回id的值,就是id的原始值,区分大小写,该属性可以设置。 | |
.title | 标签的title属性的内容,附加说明信息,该属性可以设置,设置之后鼠标移动到上边就有提示信息。 | |
lang | 元素内容的语言代码,基本没什么用。 | |
dir | 语言的方法,值为"ltr"从左至右或者"rtl",从右至左。基本没什么用。 | |
className | 元素的CSS类,可设置,非常常用的属性。HTML属性除了标签不能更改之外,其他属性设置后都会立刻生效。 | |
标签属性(特性) | .getAttribute(attrName) | 取得HTML标签里属性的值,传入属性的名称。特性名称不区分大小写,属性操作都很常用。需要注意的是,对于style属性和onclick属性,用该方法返回的是字符串,而用.style 和 .onclick属性返回的是CSS对象和JS函数,根据需要使用。 |
.setAttribute(attrName, value) | 设置属性和值。如果属性存在会替换,不存在则会创建。 | |
.removeAttribute(attrName) | 删除指定的属性。 | |
.attributes | 返回该元素节点所有的属性构成的一个类似数组的NamedNodeMap对象,保存了所有attribute类型的节点。这其实是操作属性节点。该节点也有一些增删改查方法。但是不推荐操作属性节点,而是采用上边的方法。 | |
创建元素节点 | document.createElement(tagname) | 这个是document节点的方法,用于创建一个元素节点对象。创建的节点存在于内存中,但未插入文档流,必须手动加入到文档流。 |
元素的子节点与后代节点 | .getElementsByClassName(name) | 除了像.childNodes这种通用的属性查找子元素之外,元素节点也支持这个方法,用于查询当前元素所有后代中符合条件的元素节点。 |
.getElementsByTagName(name) | 元素节点也支持这个方法,用于查询当前元素所有后代中符合条件的元素节点。 |
Text 类型
Text 节点的特点:
.nodeType
的值为3.nodeName
的值是#text.nodeValue
的值是其中的文本内容.parentNode
必定是一个Element 节点- 不支持子节点
类别 | 方法或属性名 | 解释 |
---|---|---|
操作文本内容 | .date | 获得text节点的文本内容。和.nodeValue结果一样,可以设置。 |
.appendDate(string) | 向文本的末尾添加内容。 | |
.deleteDate(index,count) | 从文本的索引处开始(包括索引位置)删除count个数的字符。 | |
.insertDate(index,string) | 从索引的位置插入内容。 | |
.repalce(idnex,count,text) | 替换内容 | |
.split(index) | 从index部分将当前节点分成两个文本节点,提取元素的时候常用此操作。 | |
.substringData(index,count) | 提取从index开始的count个数的字符串。 | |
.length | 字符串长度 | |
创建text 节点 | document.createTextNode(string) | 创建text 节点也是document对象的方法。直接用参数内容建立text 节点。 |
规范化text 节点 | .normalize() | 在之前已经介绍过,必须用于text节点的父元素上。 |
Comment 类型
Comment 节点的特点:
.nodeType
的值为8.nodeName
的值是#comment.nodeValue
的值是注释内容.parentNode
是Element或者Document节点- 不支持子节点
DocumentType 类型
DocumentType 节点的特点:
.nodeType
的值为10.nodeName
的值是doctype的名称.nodeValue
的值是null.parentNode
是Document节点- 不支持子节点
DocumentFragment 类型
DocumentFragment 节点的特点:
.nodeType
的值为11.nodeName
的值是#document-fragment.nodeValue
的值是null.parentNode
是null- 子节点可以是element, comment, text等
document.createDocumentFragment()
新建一个片段,然后将一系列元素在其中组织好以后,一次性插入文档,可以避免反复插入小节点造成屏幕反复刷新。
Attr 类型
Attr 节点可以理解为每一个HTML标签里的属性,就是一个attr节点。但是一般不会把标签属性认为是DOM的一部分,而认为是element 节点的属性。所以了解即可。Attr 节点的特点:
Attr.nodeType
的值为2.nodeName
的值是属性的名称.nodeValue
的值是属性值.parentNode
是null- 没有子节点
DOM操作技术
动态脚本
所谓动态脚本,是指网页加载完毕,没有进行任何事件的时候还不存在的脚本。创建动态脚本有两种方式:外部加载JS文件和直接插入JS代码。 外部加载JS文件的方法就是使用script标签+src属性,只要动态的将该标签加入到body的底部就可以了,加入的JS文件会立刻执行。可以使用下边的函数来封装:function loadScript(url) { let script = document.createElement("script"); script.type = "text/javascript"; script.src = url; document.body.appendChild(script); }直接插入代码的实现与此类似,只要将代码写在text节点内传入即可,考虑到转义以及浏览器支持,推荐采用动态加载外部JS文件的方式。
动态样式
动态加载样式的逻辑与动态脚本一致,就是通过向页面的head标签内增加link标签来实现。常用的做法也是封装到一个函数中:function loadStyles(url) { let link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = "url"; let head = document.getElementsByTagName("head")[0]; head.appendChild(link); }动态加载的样式也会立刻生效。
操作表格
table元素由于HTML中比较繁杂,所以对应的操作也比较麻烦。如果正常情况下操作表格,至少需要按行建立元素tr,然后给元素添加td,之后定位行然后插入。涉及的代码比较复杂,因此DOM 1级对于表格相关的元素做了一些快捷的API:标签 | 方法或属性名 | 解释 |
---|---|---|
table | .caption | 指向表格内的caption节点。 |
.tBodies | 保存着tbody元素的HTMLCollection对象 | |
.tFoot | 指向tFoo元素 | |
.tHead | 指向thead元素。 | |
.rows | 返回表格内所有row(tr标签)组成的HTMLCollection | |
.createTHead() | 创建并将thead元素放到表格里,然后返回这个thead元素。 | |
.createTFoot() | 创建并将tfoot元素放到表格里,然后返回这个tfoot元素。 | |
.createCaption() | 同上 | |
.deleteTHead() | 删除表格内的Thead元素。 | |
.deleteTFoot() | 删除tfoot元素 | |
.deleteCaption() | 删除caption元素 | |
.deleleRow(index) | 删除指定位置的行,索引从0开始 | |
.insertRow(index) | 向指定的索引插入一行,这个执行之后是插入一个内部空白的tr元素。 | |
tbody | .rows | 返回tbody中的rows组成的数组,注意table的rows也包含tbody之外的tr |
.deleteRow(index) | 删除行,索引从0开始 | |
.insertRow(index) | 插入一对空白的tr标签到索引的位置。 | |
tr | .cells | 返回当前行里边的所有单元格(td元素)组成的数组 |
.deleteCell(index) | 删除索引对应的td元素。 | |
.insertCell(index) | 向指定位置插入一对空白td元素,返回插入的元素 |
table.tBodies[0].rows[1].cells[3]
这样的方式来快速找到第二行第四个单元格。
使用查询结果
使用DOM的方法经常返回三个对象:nodeList
NamedNodeMap
HTMLCollection