JavaScirpt-DOM 概念及常用 API

更多知识点整理:https://github.com/ChenMingK/WebKnowledges-Notes

DOM(文档对象模型)

根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:

  • 整个文档是一个文档节点
  • 每个 HTML 元素是元素节点
  • HTML 元素内的文本是文本节点
  • 每个 HTML 属性是属性节点
  • 注释是注释节点

节点类型概述

Document 类型表示整个文档,是一组分层节点的根节点。在 JavaScript 中,document 对象是
Document 的一个实例。使用 document 对象,有很多种方式可以查询和取得节点。
Element 节点表示文档中的所有 HTML 或 XML 元素,可以用来操作这些元素的内容和特性。
另外还有一些节点类型,分别表示文本内容、注释、文档类型、CDATA 区域和文档片段。

Element 类型

Element 类型可以说是 Web 编程中最常用的类型,
Element 节点具有以下特征:

  • nodeType 的值为 1;
  • nodeName 的值为元素的标签名;
  • nodeValue 的值为 null;
  • parentNode 可能是 Document 或 Element;
  • 其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。

要访问元素的标签名,可以使用 nodeName 属性,也可以使用 tagName 属性;这两个属性会返回相同的值(使用后者主要是为了清晰起见)。
需要注意的是 div.tagName 实际上输出的是 ‘DIV’ 而非 ‘div’,所以最好是这么比较

// 这样最好(适用于任何文档)
if (element.tagName.toLowerCase() == "div"){ 
 // do something...
} 

1.HTML元素

所有 HTML 元素都由 HTMLElement 类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement 类型直接继承自 Element 并添加了一些属性。每个 HTML 元素中都存在的下列标准特性:

  • id,元素在文档中的唯一标识符。
  • title,有关元素的附加说明信息,一般通过工具提示条显示出来。
  • lang,元素内容的语言代码,很少使用。
  • dir,语言的方向,值为"ltr"(left-to-right,从左至右)或"rtl"(right-to-left,从右至左),
    也很少使用。
  • className,与元素的 class 特性对应,即为元素指定的 CSS 类。没有将这个属性命名为 class,是因为 class 是 ECMAScript 的保留字
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div> 

var div = document.getElementById("myDiv")

alert(div.id)        // "myDiv""
alert(div.className) // "bd"
alert(div.title)     // "Body text"
alert(div.lang)      // "en"
alert(div.dir)       // "ltr" 

所有 HTML 元素都是由 HTMLElement 或者其更具体的子类型来表示的,每一种类型都有与之相关的特性和方法,
比如 A 元素和 IMG 元素它们的特性和对应的方法是不完全相同的。

2.取得特性

每个元素都有一或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。操作特性的 DOM 方法主要有三个,
分别是 getAttribute()、setAttribute() 和 removeAttribute(),
这三个方法可以针对任何特性使用。

getAttribute():由于 div.getAttribute('id')div.id 的效果是一样的,所以一般不使用,直接使用后者来访问元素节点的特性(属性)即可

关于自定义特性(一般用 data- 表示)可以通过 dataset 属性来访问(当然用 getAttribute 方法也行),比如

<img id="test" src="" class="image-item" lazyload="true" data-original="https://xxx.webp" alt="" />


// document.getElementById('test').dataset.original 可以访问到 data-original 的值

参考:https://blog.csdn.net/qq_39579242/article/details/81779170

3.设置和移除特性

setAttribute():这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute() 会以指定的值替换现有的值;
如果特性不存在,setAttribute() 则创建该属性并设置相应的值。
用法如下:

div.setAttribute("id", "someOtherId")
div.setAttribute("class", "ft")
div.setAttribute("title", "Some other text")

通过这个方法设置的特性名会被统一转换为小写形式,即"ID"最终会变成"id"。

removeAttribute():这个方法用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性

div.removeAttribute("class");

4.attributes 属性

Element 类型是使用 attributes 属性的唯一一个 DOM 节点类型。attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似。
虽然我们可以用它做以上三个方法的操作,但是不是很方便(额外的操作 API),一般在需要遍历元素的特性时可以使用这个属性

以下代码展示了如何迭代元素的每一个特性,然后将它们构造成 name=“value” 这样的字符串格式

function outputAttributes (element){
 var pairs = new Array(),
 attrName,
 attrValue,
 i,
 len;
 for (i = 0, len = element.attributes.length; i < len; i++) {
   attrName = element.attributes[i].nodeName;
   attrValue = element.attributes[i].nodeValue;
   pairs.push(attrName + "=\"" + attrValue + "\"");
 }
 return pairs.join(" ");
} 

5.classList 属性(DOM 扩展)

<div class="bd user disabled">...</div>

这个<div>元素一共有三个类名。要从中删除一个类名,需要把这三个类名拆开,删除不想要的那个,然后再把其他类名拼成一个新字符串。请看下面的例子。

// 删除"user"类
// 首先,取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/)
// 找到要删的类名
var pos = -1,
    i,
    len

for (i = 0, len = classNames.length; i < len; i++) {
 if (classNames[i] == "user") {
   pos = i
   break
 }
}
// 删除类名
classNames.splice(i,1)
// 把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" "); 

HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加 classList 属性。这个 classList 属性是新集合类型 DOMTokenList 的实例。与其他 DOM 集合类似 DOMTokenList 有一个表示自己包含多少元素的 length 属性,而要取得每个元素可以使用 item() 方法,也可以使用方括号语法。此外,这个新类型还定义如下方法。

  • add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
  • contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。
  • remove(value):从列表中删除给定的字符串。
  • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。

这样,前面那么多行代码用下面这一行代码就可以代替了

div.classList.remove("user")
// 删除 "disabled" 类
div.classList.remove("disabled")

// 添加 "current" 类
div.classList.add("current")

// 切换 "user" 类
div.classList.toggle("user")

// 确定元素中是否包含既定的类名
if (div.classList.contains("bd") && !div.classList.contains("disabled")) {
  // do something...
)

// 迭代类名
for (var i = 0, len = div.classList.length; i < len; i++) {
 doSomething(div.classList[i])
}

有了 classList 属性,除非你需要全部删除所有类名,或者完全重写元素的 class 属性,否则也就用不到 className 属性了

节点关系

注意关系指针都是只读的,见下表

关系指针 描述
childNodes 每个节点都有一个 childNodes 属性,其中保存着一个 NodeList 对象,NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过下标来访问这些节点。
parentNode 指向该节点在文档树中的父节点
previousSibling 上一个兄弟,没有则为 null
nextSibling 下一个兄弟,没有则为 null
firstChild 第一个子节点
lastChild 最后一个子节点
ownerDocument 该属性指向表示整个文档的文档节点

contains() 方法用于判断某个节点是否为另一个节点的后代,调用 contains 方法的应该是祖先节点,也就是搜索开始的节点,
这个方法接收一个参数,即需要检测的节点。

console.log(document.documentElement.contains(document.body))  // true

这个例子检测了<body>元素是不是<html>元素的后代

需要注意的是,用上面的方式访问节点返回的不一定是元素节点(Element 类型),而我们往往需要操作的只是元素节点,所以就有了以下的 DOM 扩展
Element Traversal API 为 DOM 元素添加了以下 5 个属性:

  • childElementCount:返回子元素(不包括文本节点和注释)的个数。
  • firstElementChild:指向第一个子元素;firstChild 的元素版。
  • lastElementChild:指向最后一个子元素;lastChild 的元素版。
  • previousElementSibling:指向前一个同辈元素;previousSibling 的元素版。
  • nextElementSibling:指向后一个同辈元素;nextSibling 的元素版。

支持的浏览器为 DOM 元素添加了这些属性,利用这些元素不必担心空白文本节点,从而可以更方便地查找 DOM 元素了。
下面来看一个例子。过去,要跨浏览器遍历某元素的所有子元素,需要像下面这样写代码。

var i,
    len,
    child = element.firstChild;

while (child != element.lastChild) {
 if (child.nodeType === 1) { // 检查是不是元素
   processChild(child)
 }
 child = child.nextSibling
}

而使用 Element Traversal 新增的元素,代码会更简洁:

var i,
    len,
    child = element.firstElementChild

while (child !== element.lastElementChild) {
 processChild(child) // 已知其是元素
 child = child.nextElementSibling
} 

节点操作

创建节点

1.document.createElement(tagName)

创建一个元素节点,tagName 为HTML标签的名称(不区分大小写,如’div’),并将返回一个节点对象。

2.document.createTextNode(text)

创建一个文本节点,text 为文本节点的内容,并将返回一个节点对象。

3.document.createDocumentFragment()

创建一个文档片段文档片段用于保存将来可能添加到文档中的节点,
可以通过 appendChild() 或insertBefore() 方法将文档片段中的内容添加到文档中。
在将文档片段作为参数传给这两个方法时,实际上只有文档片段的所有子节点会被添加到文档树中,文档片段本身永远不会成为文档树中的一部分

var fragment = document.createDocumentFragment()
var ul = document.getElementById("myList")
var li = null
for (var i = 0; i < 3; i++){
  li = document.createElement("li")
  li.appendChild(document.createTextNode("Item " + (i+1)))
  fragment.appendChild(li)
}
ul.appendChild(fragment)

这样做的好处是:如果逐个地添加列表项,将会导致浏览器的反复渲染(呈现)新信息,使用文档片段可以一次性地将多个节点添加到文档树。

获取元素节点

带 s 的就是返回一个 NodeList…

1.document.getElementById():根据 id 获取节点。

2.document.getElementsByTagName():返回带有指定标签名的对象的集合。

3.document.getElementsByClassName():返回文档中所有指定类名的元素的集合,作为 NodeList 对象。

4.document.getElementsByName():查询的是元素的 name 属性。因为一个文档中的 name 属性可能不唯一(如 HTML 表单中的单选按钮通常具有相同的 name 属性),
所以 getElementsByName() 方法返回的是元素的 NodeList,而不是一个元素。

5.document.querySelector():该方法接受一个 CSS 选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素则返回 null。
选择符可以为 id、类、 类型、 属性、 属性值等;对于多个选择器,使用逗号隔开,返回一个匹配的元素。

// 取得 body 元素
var body = document.querySelector("body")

// 取得 ID 为 "myDiv" 的元素
var myDiv = document.querySelector("#myDiv")

// 取得类为 "selected" 的第一个元素
var selected = document.querySelector(".selected")

// 取得类为 "button" 的第一个图像元素
var img = document.body.querySelector("img.button")

// 获取文档中 class="example" 的第一个 <p> 元素:
document.querySelector("p.example")

// 获取文档中有 "target" 属性的第一个 <a> 元素
document.querySelector("a[target]")

注意:通过 Document 类型调用 querySelector()方法时,会在文档元素的范围内查找匹配的元素。
而通过 Element 类型调用 querySelector() 方法时,只会在该元素后代元素的范围内查找匹配的元素。
CSS 选择符可以简单也可以复杂,视情况而定。如果传入了不被支持的选择符,querySelector() 会抛出错误。

6.document.querySelectorAll():querySelectorAll() 方法接收的参数与 querySelector() 方法一样,都是一个 CSS 选择符,
但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个 NodeList 的实例,如果没有找到匹配的元素则 NodeList 为空。

// 取得某 <div> 中的所有 <em> 元素(类似于 getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em")

// 取得类为 "selected" 的所有元素
var selecteds = document.querySelectorAll(".selected")

// 取得所有<p>元素中的所有<strong>元素
var strongs = document.querySelectorAll("p strong")

改变节点关系

方法 描述
appendChild(newNode) 向 childNodes 列表末尾添加一个节点,添加节点后,childNodes 的新增节点、父节点以及最后一个节点的关系指针都会相应地得到更新。更新完成后,该方法返回新增的节点
insertBefore(newNode, 参照 Node) 该方法接收两个参数:要插入的节点和作为参照的节点。插入节点后,插入的节点会成为参照节点的 previousSibling,同时被方法返回。
replaceChild(要插入的节点,要替换的节点) 要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置
removeChild(要移除的节点) 被移除的节点将成为方法的返回值

这四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点(使用 parentNode 属性)。另外,并不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。

全部评论

相关推荐

头像
不愿透露姓名的神秘牛友
04-29 12:10
点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务