前端-JS学习笔记-DOM

认识DOM,Node,Document,Element,获取元素/删除元素/创建元素/克隆元素,获取元素属性并操纵属性,元素的大小和滚动

1. 深入理解DOM

  • 浏览器会对编写的HTML、CSS进行渲染,同时它又要考虑可能会通过JavaScript来对其进行操作

    • 浏览器将编写在HTML中的每一个元素(Element)都抽象成了一个个对象
    • 所有这些对象都可以通过JavaScript来对其进行访问,可以通过JavaScript来操作页面
    • 将这个抽象过程称之为文档对象模型(Document Object Model)
  • 整个文档被抽象到 document 对象中

    • 比如document.documentElement对应的是html元素
    • 比如document.body对应的是body元素
    • 比如document.head对应的是head元素
  • 下面的一行代码可以让整个页面变成红色

    1
    document.body.style.backgroundColor = "red";
  • 学习DOM,就是在学习如何通过JavaScript对文档进行操作

1.1. DOM Tree

  • 一个页面不只是有html、head、body元素,也包括很多的子元素
    • 在html结构中,最终会形成一个树结构
    • 在抽象成DOM对象的时候,它们也会形成一个树结构,称之为DOM Tree
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Title</title>
</head>
<body>
<h1>A Heading</h1>
<a href="#">Link Text</a>
</body>
</html>

1.2. DOM的继承关系图

  • DOM相当于是JavaScript和HTML、CSS之间的桥梁

    • 通过浏览器提供的DOM API,可以对元素以及其中的内容做任何事情
  • 类型之间有如下的继承关系

2. Node

2.1. 节点之间的导航

  • 获取到一个节点(Node)后,可以根据这个节点去获取其他的节点,称之为节点之间的导航(navigator)
1
2
3
4
5
6
<div class="box">
<h1 class="title">标题</h1>
<div class="container">div元素</div>
<div class="desc">段落</div>
文本
</div>

2.1.1. parentNode

  • parentNode 属性返回节点的最近的一个父节点
  • 指定的结点没有父节点则返回null
1
2
3
var h1 = document.querySelector(".title");
// div.box元素
console.log(h1.parentNode);

2.1.2. previousSibling

  • 返回上一个兄弟节点,可能会出现文本节点换行符
  • 指定的结点没有前兄弟节点则返回null
1
2
// #text 返回的是文本节点 换行符
console.log(h1.previousSibling);

2.1.3. nextSibling

  • 返回下一个兄弟节点,可能会出现文本节点换行符
  • 指定的结点没有后兄弟节点则返回null
1
2
3
4
// #text 返回的是文本节点 换行符
console.log(h1.nextSibling);
// div.container元素
console.log(h1.nextSibling.nextSibling);

2.1.4. firstChild

  • 第一个子节点,包括文本节点

2.1.5. lastChild

  • 最后一个子节点,包括文本节点

2.1.6. childNodes

  • 所有子节点,包括元素节点,文本节点等
    • 文本节点nodeType - 3
    • 元素节点nodeType - 1
1
2
3
4
5
6
<ul>
<li><span>1</span></li>
<li><span>2</span></li>
<li><span>3</span></li>
<li><span>4</span></li>
</ul>

会得到9个子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const ul = document.querySelector("ul");

// 所有的子节点
const nodes = ul.childNodes;
console.log(nodes);
// 9
console.log(nodes.length);

// 获取ul下所有元素节点
var ul = document.querySelector("ul");
for(var i = 0 ; i < ul.childNodes.length ; i++){
if(ul.childNodes[i].nodeType === 1){
console.log(ul.childNodes[i]);
}
}

结果

2.2. 节点的属性

  • 节点中有哪些常见的属性
    • 当然,不同的节点类型有可能有不同的属性
    • 主要讨论节点共有的属性

2.2.1. nodeType

  • nodeType 属性提供了一种获取节点类型的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="box">
<h1 class="title">标题</h1>
<div class="container">div元素</div>
<div class="desc">段落</div>
文本
</div>

<script>
var h1 = document.querySelector("h1");
// 1
console.log(h1.nodeType);
// 1
console.log(h1.parentNode.nodeType);
// 换行符 3
console.log(h1.previousSibling.nodeType);
</script>
  • 它有一个数值型值(numeric value)

  • 常见的节点类型有如下

    常量 描述
    Node.ELEMENT_NODE 1 一个元素节点,例如 p 和 div
    Node.ATTRIBUTE_NODE 2 元素的耦合属性
    Node.TEXT_NODE 3 Element 或者 Attr中实际的文字
    Node.CDATA_SECTION_NODE 4 一个 CDATASection,例如 <!CDATA[[ … ]]>
    Node.PROCESSING_INSTRUCTION_NODE 7 一个用于 XML 文档的ProcessingInstruction,例如 <?xml-stylesheet ... ?> 声明
    Node.COMMENT_NODE 8 一个 Comment 节点
    Node.DOCUMENT_NODE 9 一个 Document 节点
    Node.DOCUMENT_TYPE_NODE 10 描述文档类型的 DocumentType节点
    例如 <!DOCTYPE html> 就是用于 HTML5 的
    Node.DOCUMENT_FRAGMENT_NODE 11 一个DocumentFragment节点
  • 其他类型可以 查看MDN文档

2.2.2. nodeName

  • 获取node节点的名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div class="box">
    <h1 class="title">标题</h1>
    <div class="container">div元素</div>
    <div class="desc">段落</div>
    文本
    </div>

    <script>
    var h1 = document.querySelector("h1");
    // H1
    console.log(h1.nodeName);
    // DIV
    console.log(h1.parentNode.nodeName);
    </script>
  • 换行符的节点名字是 #text

    1
    2
    // #text
    console.log(h1.previousSibling.nodeName);

2.2.3. tagName

  • 获取元素的标签名词

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <div class="box">
    <h1 class="title">标题</h1>
    <div class="container">div元素</div>
    <div class="desc">段落</div>
    文本
    </div>

    <script>
    var h1 = document.querySelector("h1");
    // H1
    console.log(h1.tagName);
    // DIV
    console.log(h1.parentNode.tagName);
    // undefined
    console.log(h1.previousSibling.tagName);
    </script>

2.2.4. nodeName vs tagName

  • tagName 与 nodeName 不同点
    • tagName 属性仅适用于 Element 节点
    • nodeName 是为任意 Node 定义的
      • 对于元素,它的意义与 tagName 相同,所以使用哪一个都是可以的
      • 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串

2.2.5. innerHTML

  • 将元素中的 HTML 获取为字符串形式

    1
    console.log(node.innerHTML);
  • 设置元素中的内容

    1
    node.innerHTML = "<h1>Hello</h1>";

2.2.6. textContent

  • 仅仅获取元素中的文本内容

2.2.7. innerHTML vs textContent

  • 使用 innerHTML,将其“作为 HTML”插入,带有所有 HTML 标签
  • 使用 textContent,将其“作为文本”插入,所有符号(symbol)均按字面意义处理

2.2.8. outerHTML

  • 包含了元素的完整 HTML
    • innerHTML + 元素本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="box">
<h1 class="title">标题</h1>
<div class="container">div元素</div>
<div class="desc">段落</div>
文本
</div>

<script>
var h1 = document.querySelector("h1");
// <h1 class="title">标题</h1>
console.log(h1.outerHTML);
// 标题
console.log(h1.outerText);
</script>

2.2.9. nodeValue/data

  • 用于获取非元素节点的文本内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 123 -->
<div class="box">div</div>

<script>
var box = document.querySelector(".box");
var comment = box.previousSibling.previousSibling;
// 123
console.log(comment.data);
// 123
console.log(comment.nodeValue);
// undefined
console.log(box.data);
// null
console.log(box.nodeValue);
</script>

2.2.10. hidden

  • 全局属性,可以用于设置元素隐藏

3. Document

  • Document节点表示的整个载入的网页,它的实例是全局的document对象

    • 对DOM的所有操作都是从 document 对象开始的
    • 它是DOM的入口点,可以从document开始去访问任何节点元素
  • MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/Document

  • 对于最顶层的html、head、body元素,可以直接在document对象中获取到

3.1. 文档标题

  • Document.title

3.2. body元素

  • Document.body

3.3. head元素

  • Document.head 只读

3.4. html元素

  • Document.children只读:返回当前文档的子元素,即html

    1
    2
    // [ HTMLCollection [html] ]
    console.log(document.children);
  • document.documentElement

    1
    var element = document.documentElement;

3.5. 文档URI

  • Document.location 只读
1
2
// true
console.log(window.location === document.location);

3.6. 文档声明

  • Document.doctype

4. 获取元素

  • 当元素彼此靠近或者相邻时,DOM 导航属性(navigation property)非常有用

    • 但是,在实际开发中,希望可以任意的获取到某一个元素应该如何操作呢?
  • DOM提供了获取元素的方法

    方法名 搜索方式 可以在元素上调用? 实时的?
    querySelector CSS-selector -
    querySelectorAll CSS-selector -
    getElementById id - -
    getElementsByName name -
    getElementsByTagName tag or ‘*’
    getElementsByClassName class
  • 开发中如何选择呢?

    • 目前最常用的是querySelector和querySelectAll
    • getElementById偶尔也会使用或者在适配一些低版本浏览器时

4.1. getElementById()

返回一个匹配特定 id 的元素. 由于元素的 ID 在大部分情况下要求是独一无二的,这个方法自然而然地成为了一个高效查找特定元素的方法

1
var element = document.getElementById(id);

参数

  • **element**是一个 Element 对象
  • **id**是大小写敏感的字符串,代表了所要查找的元素的唯一ID.

返回一个匹配到 ID 的 DOM Element对象

1
2
// <p id="para">Some text here</p>
var elem = document.getElementById('para');

4.2. getElementsByClassName()

返回具有给定类名的元素列表

1
2
3
4
5
6
7
8
9
10
/**
<ul>
<li class="tip"><span>1</span></li>
<li class="tip"><span>2</span></li>
<li class="tip"><span>3</span></li>
<li class="tip"><span>4</span></li>
</ul>
*/
const tips = document.getElementsByClassName("tip");
console.log(tips);

4.3. getElementsByTagName()

返回具有给定标签名的元素列表

1
2
// <p id="para">Some text here</p>
var elem = document.getElementsByTagName('p');

4.4. querySelector()

  • 返回文档中与指定选择器或选择器组匹配的第一个Element对象
  • 如果找不到匹配项,则返回null
1
2
3
document.querySelector("#id");
document.querySelector(".class");
document.querySelector("div");

4.5. querySelectorAll()

  • 返回与指定的选择器组匹配的文档中的元素列表
  • 使用深度优先的先序遍历文档的节点
  • 返回的对象是NodeList
    • 是可迭代对象。可使用for-of遍历

5. 创建元素

5.1. write()

  • 使用document.write 方法写入一个元素
    • 直接将内容写入页面的内容流,会导致页面全部重绘
    • 这种方式写起来非常便捷,但是对于复杂的内容、元素关系拼接并不方便
    • 它是在早期没有DOM的时候使用的方案,目前依然被保留了下来
1
document.write('<div>123</div>');

5.2. createElement(tag)

  • 那么目前想要插入一个元素,通常会按照如下步骤
    • 步骤一:创建一个元素
    • 步骤二:插入元素到DOM的某一个位置
  • 创建多个元素效率低
1
2
3
4
5
6
7
8
//1.创建节点元素节点
var li = document.createElement("li");
//2.添加节点
var ul = document.querySelector("ul");
//追加元素
ul.appendChild(li);
//头部添加 insertBefore(child,指定元素)
ul.insertBefore(li,ul.children[0]);

5.3. innerHTML

  • 内容写入某个DOM节点,不会导致页面全部重绘
  • 创建多个元素效率更高,不要拼接字符串,采用数组的join()方法拼接
  • 不同浏览器下,innerHTML效率高于createElement
1
document.innerHTML = "<div>123</div>";

6. 插入元素

  • node.append(…nodes or strings):在 node 末尾插入节点或字符串
  • node.prepend(…nodes or strings):在 node 开头插入节点或字符串
  • node.before(…nodes or strings):在 node 前面插入节点或字符串
  • node.after(…nodes or strings):在 node 后面插入节点或字符串
  • node.replaceWith(…nodes or strings):将 node 替换为给定的节点或字符串

7. 移除和克隆元素

7.1. remove()

  • 移除元素可以调用元素本身的remove方法

7.2. cloneNode()

  • 如果想要复制一个现有的元素,可以通过cloneNode方法
1
var deepNode = node.cloneNode(deep);
  • deep:是否采用深度克隆
    • 如果为 true,则该节点的所有后代节点也都会被克隆
    • 如果为 false,则只克隆该节点本身

8. 旧的元素操作方法

  • parentElem.appendChild(node)
    • 在parentElem的父元素最后位置添加一个子元素
  • parentElem.insertBefore(node, nextSibling)
    • 在parentElem的nextSibling前面插入一个子元素
  • parentElem.replaceChild(node, oldChild):
    • 在parentElem中,新元素替换之前的oldChild元素
  • parentElem.removeChild(node):
    • 在parentElem中,移除某一个元素

9. Element

9.1. 元素之间的导航

  • 获取到一个元素(Element)后,可以根据这个元素去获取其他的元素,称之为元素之间的导航(navigator)
1
2
3
4
5
6
<div class="box">
<h1 class="title">标题</h1>
<div class="container">div元素</div>
<div class="desc">段落</div>
文本
</div>

9.1.1. parentElement

  • 返回元素的最近的一个父元素
  • 指定的结点没有父元素则返回null
1
2
3
var h1 = document.querySelector(".title");
// div.box元素
console.log(h1.parentElement);

9.1.2. previousElementSibling

  • 上一个兄弟元素,不包括换行符
1
2
3
4
5
6
// null
console.log(h1.previousElementSibling);

var container = document.querySelector(".container");
// h1.title元素
console.log(container.previousElementSibling);

9.1.3. nextElementSibling

  • 下一个兄弟元素,不包括换行符
1
2
// div.container元素
console.log(h1.nextElementSibling);

9.1.4. children

  • 只读属性。返回所有子元素
  • 只返回子元素,其余不返回
1
2
3
4
5
6
7
8
/
HTMLCollection(3) [h1.title, div.container, div.desc]
0: h1.title
1: div.container
2: div.desc
length: 3
*/
console.log(box.children);

9.1.5. firstElementChild

  • 第一个子元素节点

9.1.6. lastElementChild

  • 最后一个子元素节点

9.2. table元素的导航

  • table元素

    • as HTMLTableElement 在浏览器中会报错
    • 但是可以在vscode中暂时使用,可以有table属性提示
    • 最后还是要删除 as HTMLTableElement,在浏览器中运行
    1
    var table = document.body.firstElementChild as HTMLTableElement;
  • table元素支持以下这些属性

    • table.rows:tr元素的集合
    • table.caption/tHead/tFoot:引用元素 caption,thead,tfoot
    • table.tBodies:tbody元素的集合
  • thead,tfoot,tbody元素提供了 rows 属性

    • tbody.rows:表格内部tr元素的集合
  • tr

    • tr.cells:在给定tr中的td和th单元格的集合
    • tr.sectionRowIndex:给定的tr在封闭的thead/tbody/tfoot中的位置(索引)
    • tr.rowIndex:在整个表格中tr的编号(包括表格的所有行)
  • td和th

    • td.cellIndex:在封闭的tr中单元格的编号

9.3. Form元素的导航

  • form元素可以直接通过document来获取:document.forms

    1
    var form = document.forms[0];
  • form元素中的内容可以通过elements来获取:form.elements

    1
    var elements = form.elements;
  • 设置表单子元素的name来获取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <form action="#">
    <input type="text" name="username">
    <select name="fruits" selected>
    <option value="apple">苹果</option>
    <option value="orange">橘子</option>
    </select>
    </form>

    <script>
    var form = document.forms[0];
    var elements = form.elements;
    console.log(elements.username.value);
    console.log(elements.fruits.value);
    </script>

9.4. 元素的属性和特性

  • 一个元素除了有开始标签、结束标签、内容之外,还有很多的特性(attribute)
  • 浏览器在解析HTML元素时,会将对应的attribute也创建出来放到对应的元素对象上
    • 比如id、class就是全局的attribute
    • 比如href是针对a元素的,type、value是针对input元素的

9.4.1. attribute的分类

  • attribute的分类
    • 标准的attribute:某些attribute是标准的,比如id、class、href、type、value等
    • 非标准的attribute:某些attribute是自定义的,比如abc、age、height等

9.4.2. attribute的操作

  • 对于所有的attribute访问都支持如下的方法

    • elem.hasAttribute(name):检查特性是否存在
    • elem.getAttribute(name):获取这个特性值
    • elem.setAttribute(name, value):设置这个特性值
    • elem.removeAttribute(name):移除这个特性
    • elem.attributes:attr对象的集合,具有name、value属性
  • attribute具备以下特征

    • 名字是大小写不敏感的(id 与 ID 相同)
    • 值总是字符串类型

9.4.3. 元素的属性(property)

  • 元素中的属性称之为attribute,对象中的属性称之为property

  • 对于标准的attribute,会在DOM对象上创建与其对应的property

    • 返回值是有类型的:boolean,Number,String等
    1
    2
    3
    4
    5
    // <div id="box" class="main"></div>
    // box main
    console.log(ele.id, ele.className);
    // undefined
    console.log(ele.abc, ele.age, ele.height);
  • 在大多数情况下,它们是相互作用的

    • 改变property,通过attribute获取的值,会随着改变
    • 通过attribute操作修改,property的值会随着改变
  • 除非特别情况,大多数情况下,设置、获取attribute,推荐使用property的方式

    • 这是因为它默认情况下是有类型的
    1
    2
    3
    btn.onClick = function(){
    input.checked = !input.checked;
    }

9.4.4. HTML5的data-*自定义属性

  • HTML5的data-*自定义属性也是可以在dataset属性中获取到的
    • element.getAttribute(“data-*”)
    • element.dataset.*
    • element.dataset[“*”]
1
2
3
4
5
6
7
<div class="box" data-name="why" data-age="18"></div>

<script>
var boxEl = document.querySelector(".box");
console.log(boxEl.dataset.name);
console.log(boxEl.dataset.age);
</script>

9.5. 改变元素内容

  • 不识别html标签,非官方,去掉空格和换行
1
element.innerText = '';
  • 识别html标签,保留空格和换行
1
element.innerHTML = '';

9.6. 动态修改样式

  • 选择一:在CSS中编写好对应的样式,动态的添加class

  • 选择二:动态的修改style属性

  • 开发中如何选择呢?

    • 在大多数情况下,如果可以动态修改class完成某个功能,更推荐使用动态class
    • 如果对于某些情况,无法通过动态修改class(比如精准修改某个css属性的值),那么就可以修改style属性

9.6.1. 元素的className和classList

  • 元素的class attribute,对应的property并非叫class,而是className

    • 这是因为JavaScript早期是不允许使用class这种关键字来作为对象的属性,所以DOM规范使用了className
    • 虽然现在JavaScript已经没有这样的限制,但是并不推荐,并且依然在使用className这个名称
  • 对className进行赋值,它会替换整个class中的字符串

    1
    2
    3
    // <div class="box"></div>
    // 改变为 <div class="active"></div>
    ele.className = "active";
  • 如果需要添加或者移除单个的class,那么可以使用classList属性

  • elem.classList 是一个特殊的对象

    • classList.add (class):添加一个类
    • classList.remove(class):添加/移除类
    • classList.toggle(class):如果类不存在就添加类,存在就移除它
    • classList.contains(class):检查给定类,返回 true/false
  • classList是可迭代对象,可以通过for of进行遍历

9.6.2. 元素的style属性

  • 如果需要单独修改某一个CSS属性,那么可以通过style来操作

    • 对于多词(multi-word)属性,使用驼峰式 camelCase
    1
    2
    3
    ele.style.width = "100px";
    ele.style.height = "50px";
    ele.style.backgroudColor = "red";
  • 如果将值设置为空字符串,那么会使用CSS的默认样式

    1
    ele.style.display = "";
  • 多个样式的写法,需要使用cssText属性

    • 不推荐这种用法,因为它会替换整个字符串
    1
    2
    3
    4
    ele.style.cssText = `
    width:100px;
    height:100px;
    background-color: red;`;

9.6.3. getComputedStyle

  • 如果需要读取元素样式

    • 对于内联样式,是可以通过style.*的方式读取到的
    • 对于style、css独立文件中的样式,是读取不到的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <style>
    .box {
    width: 120px;
    }
    </style>
    <div class="box">div</div>

    <script>
    var box = document.querySelector(".box");
    // 空
    console.log(box.style.width);
    </script>
  • 可以通过getComputedStyle的全局函数来实现

    1
    2
    // 120px
    console.log(getComputedStyle(box).width);

10. 元素的大小和滚动

10.1. 元素滚动 scroll

  • 动态获取元素的大小,滚动距离
属性 作用
element.scrollTop 返回被卷去的上侧距离,返回数值不带单位
element.scrollLeft 返回被卷去的左侧距离,返回数值不带单位
element.scrollWidth 返回自身实际的宽度,不含边框,含padding,返回数值不带单位
element.scrollHeight 返回自身实际的高度,不含边框,含padding,返回数值不带单位

10.2. 元素可视区 client

  • 动态得到元素的边框大小,元素大小
属性 作用
element.clientTop 返回元素border-top上边框大小
element.clientLeft 返回元素border-left左边框大小
element.clientWidth 返回自身包括padding,内容区的宽度,不含边框,返回数值不带单位
element.clientHeight 返回自身包括padding,内容区的高度,不含边框,返回数值不带单位

10.3. 元素偏移量 offset

  • offset翻译过来就是偏移量,使用offset系列相关属性可以动态的得到该元素的位置,大小等

  • 获得元素距离带有定位父元素的位置

  • 获得元素自身的大小

  • 返回的数值不带单位

属性

属性 作用
element.offsetParent 返回作为该元素带有定位的父级元素,父级没定位则返回body
element.offsetTop 返回元素相对带有定位父元素上方的偏移
element.offsetLeft 返回元素相对带有定位父元素左边框的偏移
element.offsetWidth 返回元素自身包括padding,边框,内容区的宽度,返回数值不带单位
element.offsetHeight 返回自身包括padding,边框,内容区的高度,返回数值不带单位

10.4. offset vs style

Offset style
offset可以得到任意样式表的样式值 style只能得到行内样式表中的样式值
offset系列获取的数值是没有单位的 style.width获得的还是带有单位的字符串
offsetWidth包含padding+border+width style.width获得不包含padding和border的值
offsetWidth等属性是只读属性,只能获取不能赋值 style.width是可读写属性,可以获取也可以赋值
获取元素大小位置,用offset合适 给元素更改值,用style

10.5. offset vs client vs scroll

  • offset经常用于获得元素位置 offsetLeft,offsetTop
  • client经常用于获取元素大小 clientWidth,clientHeight
  • scroll经常用于获取滚动距离 scrollTop,scrollLeft
本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2019/02/09/前端-JS学习笔记-DOM/
  • 发布时间: 2019-02-09 19:51
  • 更新时间: 2024-04-23 00:34
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!