前端开发 JS 07 DOM基础

前端开发 JS 07 DOM基础

DOM模型 文档对象模型是HTML和XML文件的一个API,是一个层次化的节点树模型。DOM有1-3级规范。不需要了解规范的具体定义,但是目前来学的DOM模型,都是1级的内容,也就是最基础和最标准,得到所有浏览器支持的DOM模型,也是今后DOM2,3级新增的API操作的基础。 学习JS的目的,就是为

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
在取得节点后,为了确定节点的类型,可以用节点的nodetype值与常量1-12进行比较。 这里的所有节点是由DOM1标准规定的,并非所有的浏览器都全部支持12种Node类型。 所有的节点都有 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的值为9
  • nodeName的值为#document
  • nodeValue的值为null
  • parentNode的值为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 节点的特点:

  1. .nodeType的值为1
  2. .nodeName的值是标签名
  3. .nodeValue的值是null
  4. .parentNode的值要么是document对象,要么也是一个element 节点
  5. 子节点可能是element, text, comment和其他一些不重要的节点
所有的Element 节点也都继承自HTMLElement类型,具体有哪些标签可以查看高程三的263页。HTML内所有的标签在DOM模型内都有一些共通的方法和属性:
类别 方法或属性名 解释
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) 元素节点也支持这个方法,用于查询当前元素所有后代中符合条件的元素节点。
Element 节点对象是最常用的对象,当前学习的是DOM1级,在2和3级中还有很多操作元素的方法,也要熟记。JS的事件也基本都基于Element 节点对象。

Text 类型

Text 节点的特点:

  1. .nodeType的值为3
  2. .nodeName的值是#text
  3. .nodeValue的值是其中的文本内容
  4. .parentNode必定是一个Element 节点
  5. 不支持子节点
文本标签保存的是纯文本,不能包含HTML代码。一般操作text节点都是需要精确操作文本的时候,不然只要通过设置DOM2,3级里边元素的一些文本属性就可以了。
类别 方法或属性名 解释
操作文本内容 .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节点的父元素上。
Text 节点的具体操作比较多,在实践中一般不到如此细微的程度,只在某些特定场合需要操作字符,常见于爬虫等程序中。

Comment 类型

Comment 节点的特点:

  1. .nodeType的值为8
  2. .nodeName的值是#comment
  3. .nodeValue的值是注释内容
  4. .parentNode是Element或者Document节点
  5. 不支持子节点
comment节点与text节点继承自相同的基类,所以拥有除了.splitText()之外的所有方法。了解即可

DocumentType 类型

DocumentType 节点的特点:

  1. .nodeType的值为10
  2. .nodeName的值是doctype的名称
  3. .nodeValue的值是null
  4. .parentNode是Document节点
  5. 不支持子节点
这个节点就是HTML的声明,在HTML5之后,声明都统一了,所以了解即可。

DocumentFragment 类型

DocumentFragment 节点的特点:

  1. .nodeType的值为11
  2. .nodeName的值是#document-fragment
  3. .nodeValue的值是null
  4. .parentNode是null
  5. 子节点可以是element, comment, text等
这个节点相当于是一个储藏室,存放暂时不在页面文档流的元素。不能把这个节点插入到文档流中,但可以把这个节点的子节点和后代等插入。所以当新建一系列元素的时候,可以通过 document.createDocumentFragment()新建一个片段,然后将一系列元素在其中组织好以后,一次性插入文档,可以避免反复插入小节点造成屏幕反复刷新。

Attr 类型

Attr 节点可以理解为每一个HTML标签里的属性,就是一个attr节点。但是一般不会把标签属性认为是DOM的一部分,而认为是element 节点的属性。所以了解即可。

Attr 节点的特点:

Attr
  1. .nodeType的值为2
  2. .nodeName的值是属性的名称
  3. .nodeValue的值是属性值
  4. .parentNode是null
  5. 没有子节点
操作attr节点一般不会通过自有方法。而是采用element 节点的方法直接操作属性。   上边介绍完了全部的DOM节点和DOM1级中对应的操作方法,这些内容其实是一系列提供给JS进行操作的API,虽然数量很多,但常用的就是document和element 节点的操作。下边来看一看用JS操作DOM的一些基本技巧。  

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:
表格元素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的方法经常返回三个对象:
  1. nodeList
  2. NamedNodeMap
  3. HTMLCollection
每次查询得到这三个结果的时候,都是当前查询的结果,在一对DOM进行操作的时候,再次查询这些内容的值就会变化。即使是将查询结果赋给一个变量,这个变量也是保存的引用,只要内容变化,变量的内容也会变化。因此在每次操作DOM的时候,都必须先运行查询,在确保没有被其他程序动态修改的基础上再做操作,否则很可能出错。也正是因为每次操作DOM都要先查询,所以要提高页面的效率,就要合理的编排HTML,减少查询DOM的次数。
LICENSED UNDER CC BY-NC-SA 4.0
Comment