操作文档对象模型(DOM)

长文预警!!

一. 什么是文档对象模型

文档对象模型(DOM)是一个对 HTML 布局结构的映射模型,换句话说就是一个描述 HTML 的模型。它使得我们可以通过 JavaScript 代码访问文档,完成修改页面内容,插入数据或修改某个实例的任务。

DOM 是通过来描述HTML 文档的,相信学过数据结构的同学对树一定不陌生,就是一种组织数据的方式而已。

二. 节点

如果是一个树的话,就肯定有节点(node),每个节点可以有父节点、子节点、兄弟节点。

下图是通过从上到下遍历一个简单的 HTML 文档创建出的表示节点间上下级关系的结构图。


从上图中也可以看出来,DOM 中的节点分为几种不同的类型。表示 HTML 元素的叫做元素节点,表示文本的叫做文本节点,表示属性的叫做属性节点。所有的节点都是上图顶部的那个文档节点的下级节点(这个文档节点很容易被忽视)。了解这几种节点之间的区别是很重要的,因为我们在使用 JS 遍历 DOM 的时候需要以不同的方式处理他们。

1. 元素节点

元素节点其实就是文档标签所对应的节点,这些节点定义了文档的结构,并且我们选哟与之进行交互并对其进行修改的大部分内容在他们之中,它们是遍历文档的基础所在。

2. 文本节点

简单点来说,文本节点存在于标签之间,此外文本节点不能有子节点,

访问文本节点与访问元素节点所使用的 JS 方法是一样的。

3. 属性节点

与文档节点一样,我们在构建 DOM 的时候很容易忽视属性节点,因为它看上去像是元素节点的一部分,实际上却是与之完全不同并且极为重要的一类节点。处理他们的方式也与元素节点和文本节点不同。

与文本节点一样,属性节点也不能拥有自己的子节点,而且对于他们的外围元素来说,属性节点也不是其子节点。这听起来有点奇怪,因为这些节点依附于元素节点而存在,但是他们却不是其子节点,原因是我们需要采用另外一套方式访问其内容,我们可以使用普通的 JS 方法接近这类节点,但是要想访问其中的内容,则需要特有的方法。
我们经常需要增加或删除元素节点的 class 属性,所以会频繁的用到属性节点。

三. 处理元素节点

了解了这些节点之后,我们接下开看看如何访问这些节点。我们可以肯定的说,凡是 DOM 模型里面的东西,我们就有办法将他抓取出来,并使用 JS 对其进行储存修改。

我们接下来将会看到有很多种方式访问节点,在实际中我们应该选择效率最高的一种。

1. 根据 ID 定位元素

HTML 标准要求元素的 id 属性值在文档内必须是唯一的,因此元素的 id 是最理想的定位方式,我们先来看看具体的例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>id</title>
</head>
<body>
    <div id="header">
        <h1><a href="/" id='homelink'>title of your site</a></h1>
    </div>
</body>
</html>
js
a = document.getElementById('header');
b = document.getElementById('homelink');
console.info(a)
console.info(b)

这样 console 就显示出了我们访问到的内容:


正如我们之前所说的,获取任何节点都要通过 document 对象才行。当我们定义到节点之后我们就可以使用大量的 JS 内置方法对其进行 储存、修改、获取信息。我们之后再说这些方法。

2. 根据标签名定位元素

由于 id 在文档中是唯一的,所以 getElementById() 只能返回一个元素,有的时候我们需要的元素不止一个,甚至可能是一组。那么我们就可以使用 getElementsByTagName() 方法。

有一个常见的错误就是忘记写 Elements 后面的 s 。

这种方式既可以找到一组元素也可以获取一个元素。

下面是一个使用这种方法获取标签的例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>TagName</title>
    <meta charset="utf-8">
</head>
<body>
    <h1>Using getElementByTagName</h1>
    <p>Content Paragraph</p>
    <p>Content Paragraph</p>
    <p>Content Paragraph</p>
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
js
a = document.getElementsByTagName('p');
console.info(a)

输出为:

从上面的输出中可以看出来,document.getElementsByTagName('p')返回了一个 NodeList 的 DOM 对象,NodeList 对象是一个节点的集合,各节点的顺序与其在 HTML 文档中出现的顺序一致。这个对象有一个 length 属性,所以我们可以通过document.getElementsByTagName('p').length 的方法获取这个列表的长度。在 python 中我们是通过对象的方法来获取其长度,而 js 是通过它的一个属性。

当我们定位好一组元素,检查好其长度之后我们就可能需要将每个节点抽取出来单个处理 :

  • 使用 item 方法
  • 使用数组下标

js 中数组的下标起始也是 0。

js
// 在访问前检查其长度是一个好习惯
if (document.getElementsByTagName('p').length > 0){
    // 使用 item 方法访问第一个元素
    a = document.getElementsByTagName('p').item(0);
    console.info(a);

    // 使用数组下标访问第一个元素
    b = document.getElementsByTagName('p')[0];
    console.info(b);
}   

输出为:


3. 根据 class 属性定位元素

getElementsByClassName()可以通过 class 属性来定位元素,它也同样返回一个 NodeList 对象供开发者访问,

我们还可以结合其他的定位元素的方法结合起来使用,可以实现一些有趣的元素选择功能。

我们直接看看它的例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>ClassName</title>
    <meta charset="utf-8">
</head>
<body>
    <h1>Using getElementByTagName</h1>
    <p class="dropcap huge">Content Paragraph</p>
    <p id="e" class="dropcap">Content <span class="huge">Paragraph</span> </p>
    <p class="dropcap">Content Paragraph</p>
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
js
// 返回所有 class 为 dropcap 的元素节点
document.getElementsByClassName('dropcap');

// 返回所有 class 同时带有 dropcap 和 huge 的元素节点
document.getElementsByClassName('dropcap huge');

// 返回所有在 id 为 e 的元素节点下的 class 为 huge 的元素
document.getElementById('e').getElementsByClassName('huge') 

4. 在 JavaScript 中使用 CSS 选择器来定位节点

CSS 选择器参考手册

你大概也已经发现了,我们定位元素的过程实际上与 CSS 选择器很相似,所以人们就希望可以通过便于记忆的 CSS 选择器来定位节点。所以 querySelector()querySeletorAll() 就产生了。

这两个方法都接受 CSS格式的选择器作为其参数,二者的区别是 querySeletorAll() 返回的是 NodeList 对象,而 querySelector() 只返回匹配的第一个元素。

我们还是看一段代码:

html
<!DOCTYPE html>
<html>
<head>
    <title>ClassName</title>
    <meta charset="utf-8">
</head>
<body>
    <h1>Using getElementByTagName</h1>
    <p class="dropcap huge">Content Paragraph</p>
    <p id="e" class="dropcap">Content <span class="huge">Paragraph</span> </p>
    <p class="dropcap">Content Paragraph</p>
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
js
// 返回 id 值为 header 的元素
document.querySelector('#header')

// 返回 class 是 dropcap 的第一个元素
document.querySelector('.dropcap')

// 返回所有class 是 dropcap 的元素
document.querySelectorAll('.dropcap')

// 返回所有 属性是 dropcap 和 huge 的元素
document.querySelectorAll('.dropcap', '.huge')

// 返回所有带有 class 的 p 标签
document.querySelectorAll('p[class]') 

四. 处理属性节点

上一节说了好多处理元素节点的方法,我们先回顾一下:通过 id 、通过 class 、通过标签名、通过 CSS 选择器。
接下来我们就可以操纵他们之中的属性节点啦。

前面说过,属性节点虽然位于元素节点的内部,但是并不被当作其子节点。所以如果我们想要获取、修改或移除其中的信息,那么我们就需要一些特殊的方法,他们是:

  • getAttribute()
  • setAttribute()
  • removeAttribute()
  • hasAttribute()

以上的各个方法都是通过属性名称来定位属性值的。比如说 class = "visible” 为例,根据属性名 class 就可以定位到值为 "visible” 的属性。

许多最为常见的交互效果,比如隐藏/显示,使用 CSS 来做效率会更高,我们只需要简单的添加或者删除某个 class 就好啦。

1. 获取属性

上文中也提到过,在获取属性之前,我们需要先深入到 DOM 中找到元素节点(使用上面讲的那些方法),然后才能访问该元素的属性。

类似于我们检查 NodeList 的长度那样,获取某个属性之前,先检查一下看看元素中有没有我们想要的属性是一个好主意:我们可以通过 hasAttribute() 方法来做,比如下面这个例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>attibute node</title>
    <meta charset="utf-8">
</head>
<body>
    <img src="./img.jpg" class="show" alt="placehoder image" id="pic">
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
js
if(document.getElementById('pic').hasAttribute('class')){
    a = document.getElementById('pic').getAttribute('class')
    console.info(a)
}

最后结果打印出来 show ~~

2. 设置属性

设置属性通过方法 setAttribute() 来实现,

这个方法内置了 if 语句,所以我们不需要预先使用条件判断语句,

这个方法需要两个参数,一个是需要设置的属性名称,第二个是将要设置的属性值。
我们还是看一个例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>attibute node</title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="./located_by_id.css">
</head>
<body>
    <img src="./img.jpg" class="visible" alt="placehoder image" id="pic" >
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
css
.visible {
    position: static;
    width: 300px;
}

.hidden {
    position: absolute;
    top: -9999px;
    left: -9999px;
}
js
document.getElementById('pic').setAttribute('class', 'hidden')

document.getElementById('pic').setAttribute('title', 'we moved this element off screen')

在这个例子中我们将 class 属性从 visible 变为 hidden 我们还增加了一个 title 属性,由于 class 属性本来就有所以新的 class 值取代了旧的,但是我们之前没有 title 属性所以 setAttribute() 方法会自动创建一个。

这意味着我们可以在 DOM 中动态的创建了一个自己的节点。

3. 移除属性

与获取属性一样,先定位到它所在的元素节点,然后使用 removeattribute() 方法移除,同样的我们一般先使用 hasattribute 方法判断一下待移除的属性是否存在。

还是用上面的例子来举例:

if(document.getElementById('pic').hasAttribute('class')) {
    document.getElementById('pic').removeAttribute('class')
}

五. 处理文本节点并修改其内容

在处理文本节点的时候我们主要是修改其中的内容,如果想要变更或修改一大块内容我们可以不用层层解析 DOM 直接通过 innerHTML 属性将其一次性设置到位。
直接上例子:

html
<!DOCTYPE html>
<html>
<head>
    <title>text node</title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="./located_by_id.css">
</head>
<body>
    <div id="target-area">
        <p>This is our text.</p>
    </div>
    <script type="text/javascript" src="./located_by_id.js"></script>
</body>
</html>
js
a = document.getElementById('target-area').innerHTML;

console.info(a)
document.getElementById('target-area').innerHTML = '<p>hello world</p>'

输出的结果为:


六、 遍历 DOM

我们在前面已经知道了我们可以通过 Id class 标签名或者 NodeList 中特定的位置等手段直接跳进 DOM 对象当中。但是有的时候,我们不会在 HTMl 文档中加入大量的 class 与 Id 属性,所以使用上述手段只能达到一定是深度,之后我们就只能通过一些 JavaScript 原生方法来在其中上下游走。他们是:

  • parentNode
  • previousSibling
  • nextSibling
  • firstChild
  • lastChild

1. 在DOM 中动态的添加和移除节点

到目前为止我们所访问的都是 DOM 中已经存在的节点。有时我们需要对DOM 进行一些动态的操作,诸如插入节点、创建新元素、向其中添加内容、将他们移动到文档中的其他位置。JavaScript已经内置了此功能:

  • createElement()
  • createTextNode()
  • appendChild()
  • removeChild()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  •   DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API(应用程序编程接口)。   DOM 描绘...
    霜天晓阅读 3,644评论 0 7
  •   DOM 1 级主要定义的是 HTML 和 XML 文档的底层结构。   DOM2 和 DOM3 级则在这个结构...
    霜天晓阅读 1,440评论 1 3
  • DOM树是Web页面的模型 当浏览器加载一个Web页面时,它会创建这个页面的模型,称为DOM树。主要由4类主要节点...
    陈老板_阅读 670评论 0 1
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,124评论 0 21
  • 参考书:《JavaScript高级程序设计》 知识点前提:什么是节点 Node类型 DOM1级定义了一个Node接...
    DHFE阅读 405评论 0 0