javascript文档_JavaScript从零开始——DOM操作(1)

10ba22776a884b6c9f5a0a5fb6995cf5.png

OK,终于到这一部分了,其实这是JavaScript的最初目的,就是控制网页元素,而为了能够真正的进行交互,JavaScript就需要使用这套接口:Document Object Model —— DOM;由于这一部分涉及到一些HTML的内容,可能需要对HTML有基本的了解才行,我后面可能会考虑重新把HTML5也做一个专栏出来,这样可以相互参考了(当然,w3school这类的在线教程站点,也是好的选择)。


1 概念

1.1 DOM

DOM JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。

浏览器会根据 DOM 模型,将结构化文档(比如 HTML XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。

DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOMJavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言。后面介绍的就是 JavaScript DOM 标准的实现和用法。

1.2 节点

DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子。

节点的类型有七种:

  • Document:整个文档树的顶层节点
  • DocumentTypedoctype标签(比如<!DOCTYPE html>
  • Element:网页的各种HTML标签(比如<body><a>等)
  • Attr:网页元素的属性(比如class="right"
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段

浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。

1.3 节点树

一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,又像一棵树。

浏览器原生提供document节点,代表整个文档:

3b7e850a135eca4811050235c0d2fc7b.png

文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>),第二个是 HTML 网页的顶层容器标签<html>。后者构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。

除了根节点,其他节点都有三种层级关系:

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括firstChild(第一个子节点)和lastChild(最后一个子节点)等属性,同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。

2 Node接口

2.1 属性

(1)Node.prototype.nodeType

nodeType属性返回一个整数值,表示节点的类型:

81e88d3d14a90eb5c111741c5f0775af.png

上面代码中,文档节点的类型值为9

Node 对象定义了几个常量,对应这些类型值:

0b00ef50f3e69f77168cc57a07fad336.png

上面代码中,文档节点的nodeType属性等于常量Node.DOCUMENT_NODE

不同节点的nodeType属性值和对应的常量如下:

  • 文档节点(document):9,对应常量Node.DOCUMENT_NODE
  • 元素节点(element):1,对应常量Node.ELEMENT_NODE
  • 属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
  • 文本节点(text):3,对应常量Node.TEXT_NODE
  • 文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE
  • 文档类型节点(DocumentType):10,对应常量Node.DOCUMENT_TYPE_NODE
  • 注释节点(Comment):8,对应常量Node.COMMENT_NODE

确定节点类型时,使用nodeType属性是常用方法:

e5b50aca4144f500af43586d6498d5f8.png

(2)Node.prototype.nodeName

首先有一个简单的HTML页面,如下:

b845cee04ded068bc0ec98328e530c55.png

其源码如下:

8ffa2201b70baa2ceef7508e917d5dc5.png

nodeName属性返回节点的名称:

012543b10ee8a39bb7969b8c79eb0222.png

上面代码中,元素节点<div>nodeName属性就是大写的标签名DIV

不同节点的nodeName属性值如下:

  • 文档节点(document):#document
  • 元素节点(element):大写的标签名
  • 属性节点(attr):属性的名称
  • 文本节点(text):#text
  • 文档片断节点(DocumentFragment):#document-fragment
  • 文档类型节点(DocumentType):文档的类型
  • 注释节点(Comment):#comment

(3)Node.prototype.nodeValue

nodeValue属性返回一个字符串,表示当前节点本身的文本值,该属性可读写。

只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此这三类节点的nodeValue可以返回结果,其他类型的节点一律返回null。同样的,也只有这三类节点可以设置nodeValue属性的值,其他类型的节点设置无效:

bc27456017cb25b568f89c9cfe326c67.png

上面代码中,div是元素节点,nodeValue属性返回nulldiv.firstChild是文本节点,所以可以返回文本值。

(4)Node.prototype.textContent

textContent属性返回当前节点和它的所有后代节点的文本内容:

983d89ff998468f1415dad0e3a0d38dc.png

textContent属性自动忽略当前节点内部的 HTML 标签,返回所有文本内容。

该属性是可读写的,设置该属性的值,会用一个新的文本节点,替换所有原来的子节点。它还有一个好处,就是自动对 HTML 标签转义。这很适合用于用户提供的内容:

ec1faa733e022eaa1b17a4966b5bfc7c.png

465ef7944a9ab52c357cf14079b42d63.png

上面代码在插入文本时,会将<p>标签解释为文本,而不会当作标签处理。

对于文本节点(text)、注释节点(comment)和属性节点(attr),textContent属性的值与nodeValue属性相同。对于其他类型的节点,该属性会将每个子节点(不包括注释节点)的内容连接在一起返回。如果一个节点没有子节点,则返回空字符串。

文档节点(document)和文档类型节点(doctype)的textContent属性为null。如果要读取整个文档的内容,可以使用document.documentElement.textContent

(5)Node.prototype.baseURI

baseURI属性返回一个字符串,表示当前网页的绝对路径。浏览器根据这个属性,计算网页上的相对路径的 URL。该属性为只读:

8529c2b335ebcf500d737f4b19ad05e1.png

d3855eeb3481c5753c16d7581232d517.png

如果无法读到网页的 URLbaseURI属性返回null

该属性的值一般由当前网址的 URL(即window.location属性)决定,但是可以使用 HTML <base>标签,改变该属性的值:

<

设置了以后,baseURI属性就返回<base>标签设置的值:

0e5b8de6dc0efeb5db027ee120e92b0d.png

(6)Node.prototype.ownerDocument

Node.ownerDocument属性返回当前节点所在的顶层文档对象,即document对象:

e571b010767fe0a907478d89d75dd16d.png

document对象本身的ownerDocument属性,返回null

(7)Node.prototype.nextSibling

这一次的HTML:

4f70a5330054752e0bcb5c709bc681e8.png

Node.nextSibling属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null

1c3f9565613737c64fd814e4d6141a30.png

上面代码中,d1.nextSibling就是紧跟在d1后面的同级节点d2

注意,该属性还包括文本节点和注释节点(<!-- comment -->)。因此如果当前节点后面有空格,该属性会返回一个文本节点,内容为空格。

修改原始HTML为:

dd343b61364b0f120fd0b7e9d5966cf6.png

此时d1的相邻节点:

99a34012536527d216ed990183b4f9a9.png

nextSibling属性可以用来遍历所有子节点。

修改HTML如下:

378910739ea6a4df1e41bb84ad39604e.png

遍历div的所有子节点:

4bf6896580a6642df7a1bc8a88df6efc.png

(8)Node.prototype.previousSibling

8a350ca8d27a1b711dc3e6a76e40e464.png

previousSibling属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null

8a7893cc7df57de7a5b2197805a69b83.png

上面代码中,d2.previousSibling就是d2前面的同级节点d1

注意,该属性还包括文本节点和注释节点。因此如果当前节点前面有空格,该属性会返回一个文本节点,内容为空格(不做演示了,可以参考上一节内容)。

(9)Node.prototype.parentNode

parentNode属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment):

if 

上面代码中,通过node.parentNode属性将node节点从文档里面移除。

文档节点(document)和文档片段节点(documentfragment)的父节点都是null。另外,对于那些生成后还没插入 DOM 树的节点,父节点也是null

(10)Node.prototype.parentElement

parentElement属性返回当前节点的父元素节点。如果当前节点没有父节点,或者父节点类型不是元素节点,则返回null

if 

上面代码中,父元素节点的样式设定了红色。

由于父节点只可能是三种类型:元素节点、文档节点(document)和文档片段节点(documentfragment)。parentElement属性相当于把后两种父节点都排除了。

(11)Node.prototype.firstChild,Node.prototype.lastChild

551592868afc78d5e65addb13ec31c5d.png

firstChild属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null

c259c81cac20ee4e30defdd7ddae8084.png

上面代码中,div元素的第一个子节点是span元素。

注意,firstChild返回的除了元素节点,还可能是文本节点或注释节点:

f8868541acc6ec198695bd3980abcbfd.png

67bc4fb071c42f1389f1ae3c90d9e61e.png

上面代码中,div元素与span元素之间有空白字符,还有“test”,这导致firstChild返回的是文本节点。

lastChild属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null。用法与firstChild属性相同。

(12)Node.prototype.childNodes

childNodes属性返回一个类似数组的对象(NodeList集合),成员包括当前节点的所有子节点(使用上一次的HTML):

8212e71ec5fc7db2efe16cf3a6226ef5.png

上面代码中,children就是div元素的所有子节点。

使用该属性,可以遍历某个节点的所有子节点:

let 

文档节点(document)就有两个子节点:文档类型节点(docType)和 HTML 根元素节点。

1f8b907b58c8d2bbbbe1d344bcbdbda6.png

上面代码中,文档节点的第一个子节点的类型是10(即文档类型节点),第二个子节点的类型是1(即元素节点)。

注意,除了元素节点,childNodes属性的返回值还包括文本节点和注释节点。如果当前节点不包括任何子节点,则返回一个空的NodeList集合。由于NodeList对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。

(12)Node.prototype.isConnected

isConnected属性返回一个布尔值,表示当前节点是否在文档之中:

4d85acf03057ab5a34f34ac67cbf66e5.png

上面代码中,p节点是脚本生成的节点,没有插入文档之前,isConnected属性返回false,插入之后返回true

2.2 方法

(1)Node.prototype.appendChild()

appendChild()方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点:

let 

上面代码新建一个<p>节点,将其插入document.body的尾部。

314410549ae6322d37aa2dcbb92621ae.png

e1e863ada77c95c5b282fdbe2012262f.png

如果参数节点是 DOM 已经存在的节点,appendChild()方法会将其从原来的位置,移动到新位置:

9e2f8d6b654a8c39ab4eb17896a1402f.png

406e9d5493f2bc7df601ae48a390ff74.png

上面代码中,插入的是一个已经存在的节点span,结果就是该节点会从原来的位置,移动到document.body的尾部。

如果appendChild()方法的参数是DocumentFragment节点,那么插入的是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值是一个空的DocumentFragment节点。

由于接下来的方法作用在页面上都非常直观,你可以自己试一下,这里我每次需要截四张图,实在是因为自己懒了点,大多数操作类的方法就不附图了。

(2)Node.prototype.hasChildNodes()

hasChildNodes方法返回一个布尔值,表示当前节点是否有子节点:

let 

上面代码表示,如果foo节点有子节点,就移除第一个子节点。

注意,子节点包括所有类型的节点,并不仅仅是元素节点。哪怕节点只包含一个空格,hasChildNodes方法也会返回true

判断一个节点有没有子节点,有许多种方法,下面是其中的三种:

  • node.hasChildNodes()
  • node.firstChild !== null
  • node.childNodes && node.childNodes.length > 0

hasChildNodes方法结合firstChild属性和nextSibling属性,可以遍历当前节点的所有后代节点:

function 

上面代码中,DOMComb函数的第一个参数是某个指定的节点,第二个参数是回调函数。这个回调函数会依次作用于指定节点,以及指定节点的所有后代节点。

(3)Node.prototype.cloneNode()

cloneNode方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否同时克隆子节点。它的返回值是一个克隆出来的新节点:

let 

该方法有一些使用注意点。

(1)克隆一个节点,会拷贝该节点的所有属性,但是会丧失addEventListener方法和on-属性(即node.onclick = fn),添加在这个节点上的事件回调函数。

(2)该方法返回的节点不在文档之中,即没有任何父节点,必须使用诸如Node.appendChild这样的方法添加到文档之中。

(3)克隆一个节点之后,DOM 有可能出现两个有相同id属性(即id="xxx")的网页元素,这时应该修改其中一个元素的id属性。如果原节点有name属性,可能也需要修改。

(4)Node.prototype.insertBefore()

insertBefore方法用于将某个节点插入父节点内部的指定位置:

let 

insertBefore方法接受两个参数,第一个参数是所要插入的节点newNode,第二个参数是父节点parentNode内部的一个子节点referenceNodenewNode将插在referenceNode这个子节点的前面。返回值是插入的新节点newNode

let 

上面代码中,新建一个<p>节点,插在document.body.firstChild的前面,也就是成为document.body的第一个子节点。

如果insertBefore方法的第二个参数为null,则新节点将插在当前节点内部的最后位置,即变成最后一个子节点:

let 

上面代码中,p将成为document.body的最后一个子节点。这也说明insertBefore的第二个参数不能省略。

注意,如果所要插入的节点是当前 DOM 现有的节点,则该节点将从原有的位置移除,插入新的位置。

由于不存在insertAfter方法,如果新节点要插在父节点的某个子节点后面,可以用insertBefore方法结合nextSibling属性模拟。

parent

上面代码中,parent是父节点,s1是一个全新的节点,s2是可以将s1节点,插在s2节点的后面。如果s2是当前节点的最后一个子节点,则s2.nextSibling返回null,这时s1节点会插在当前节点的最后,变成当前节点的最后一个子节点,等于紧跟在s2的后面。

如果要插入的节点是DocumentFragment类型,那么插入的将是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值将是一个空的DocumentFragment节点。

(5)Node.prototype.removeChild()

removeChild方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点:

let 

上面代码移除了divA节点。注意,这个方法是在divA的父节点上调用的,不是在divA上调用的。

下面是如何移除当前节点的所有子节点:

let 

被移除的节点依然存在于内存之中,但不再是 DOM 的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。

如果参数节点不是当前节点的子节点,removeChild方法将报错。

(6)Node.prototype.replaceChild()

replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点:

let 

上面代码中,replaceChild方法接受两个参数,第一个参数newChild是用来替换的新节点,第二个参数oldChild是将要替换走的子节点。返回值是替换走的那个节点oldChild

let 

上面代码是如何将指定节点divA替换走。

(7)Node.prototype.contains()

contains方法返回一个布尔值,表示参数节点是否满足以下三个条件之一:

  • 参数节点为当前节点。
  • 参数节点为当前节点的子节点。
  • 参数节点为当前节点的后代节点。
document

上面代码检查参数节点node,是否包含在当前文档之中。

注意,当前节点传入contains方法,返回true

8a63887df8f1d821b0d25fe2ebc74271.png

(8)Node.prototype.compareDocumentPosition()

compareDocumentPosition方法的用法,与contains方法完全一致,返回一个六个比特位的二进制值,表示参数节点与当前节点的关系。

88db37196cf7de0f6fcae4d787f5b5b1.png

cf100e510b2c793e4b17352dac60e15b.png

2f60cda6e6ad6a438035b843cb079abf.png

上面代码中,节点div包含节点input(二进制010000),而且节点input在节点div的后面(二进制000100),所以第一个compareDocumentPosition方法返回20(二进制010100,即010000 + 000100),第二个compareDocumentPosition方法返回10(二进制001010)。

由于compareDocumentPosition返回值的含义,定义在每一个比特位上,所以如果要检查某一种特定的含义,就需要使用比特位运算符:

let 

上面代码中,compareDocumentPosition的返回值与4(又称掩码)进行与运算(&),得到一个布尔值,表示<head>是否在<body>前面。

(9)Node.prototype.isEqualNode(),Node.prototype.isSameNode()

isEqualNode方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同:

eb27922805e7aed33afd48cdee727ad7.png

isSameNode方法返回一个布尔值,表示两个节点是否为同一个节点:

9d64582732cc5c808a7447aa5c10c6b3.png

(10)Node.prototype.normalize()

normalize方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点:

78c750e099a33a971282117c58234678.png

上面代码中,div节点原本有三个子节点,其中一个文本子节点;在使用normalize方法之前,div节点新增两个毗邻的文本子节点,即五个子节点。使用normalize方法之后,三个文本子节点被合并成一个,故而子节点数量还是三个。

该方法是Text.splitText的逆方法,后面到介绍Text节点方法的时候我们会看到。

(11)Node.prototype.getRootNode()

getRootNode()方法返回当前节点所在文档的根节点document,与ownerDocument属性的作用相同:

81e9e2fd35ae2a16110c9ff69c81d569.png

该方法可用于document节点自身,这一点与document.ownerDocument不同:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值