DOM知识梳理

时间:2023-03-09 04:09:25
DOM知识梳理

DOM

我们知道,JavaScript是由ECMAScript + DOM + BOM组成的。ECMAScript是JS中的一些语法,而BOM主要是浏览器对象(window)对象的一些相关知识的集合。而DOM,则是文档对象相关的知识的集合。

我们知道,HTML和JS之间的交互是通过事件实现的。而DOM是针对HTML(XML)文档的一个API。因此,如果我们想实现与用户的交互,那么就需要使用DOM提供的API,获取HTML元素,然后在该元素上绑定相应的事件,实现与用户的交互。所以,对DOM的理解和掌握就显得相当重要。

本文章主要基于《JavaScript高级程序设计(三)》中的DOM相关章节,对DOM的主要知识作出一个梳理,并穿插我个人的一些理解。

节点层次

写过HTML代码的地球人应该都知道,我们需要给每一个元素添加缩进,然后在书写相关的HTMl标签和内容,最后显示在网页上。因此这种嵌套的HTML代码和内容就构成了节点层次。

对ECMAScript理解的地球人应该都知道,JS中的每一个对象都是基于一个引用类型创建的,而引用类型可以是JS原生提供的引用类型(Array、Function、RegExp、Object等),也可以是自定义的引用类型(通过new关键字调用引用类型(也可以叫构造函数))。而所有对象都是Object的实例对象,都可以继承Object.prototype上的属性和方法

而在DOM中,也同样有这样类似的机制。在DOM中,最顶层的类型是Node类型,其他所有节点都可以继承Node类型下的属性和方法。而Node类型实际上就相当于JS中的Object构造函数。

既然如此,那就线看看Node类型下有哪些属性和方法

Node类型

  • 属性(在某个特定的节点通过继承的方式调用以下属性)

    • nodeType
    • nodeName
    • nodeValue
    • ·············
    • childNodes(指针,指向NodeList对象)
    • parentNode
    • nextSibling
    • previousSibling
    • firstChild
    • lastChild
    • ownDocument(每个节点都只能属于一个Document节点)
  • 方法(在某个特定的节点通过继承的方式调用以下方法)

    • ··· 查找节点 ···
    • 查找元素的方法位于Document类型中
    • ························
    • ··· 插入节点 ···
    • appendChild(ele)
    • insertBefore(ele, target): 如果target为null,则规则同appendChild
    • ························
    • ··· 删除节点 ···
    • removeChild(ele)
    • ························
    • ··· 替换节点 ···
    • replaceChild(ele, target)
    • ························
    • ··· 复制节点 ···
    • cloneNode(boolean) true: 表示深复制, false: 表示浅复制
    • ························
    • ··· 处理文档节点 ··· 很少用~
    • normalize()

Node类型上的属性和方法也就那么多了,再啰嗦一次,所有的其他节点都可以继承Node类型上的属性和方法

Document类型

JS通过Document类型表示文档。document对象是HTMLDocument的一个实例,表示整个HTML页面。同时,document对象也是window对象下的一个属性,因此可以将其作为全局对象来访问。

  • 属性
    • document.documentElement (表示HTML元素),同时可以通过document.childNodes[1]获取HTML元素
    • document.body (表示body元素)
    • document.head (表示head元素) ---HTML5新增
    • document.compatMode (表示浏览器采用哪种渲染方式,'CSS1Compat'表示标准模式, 'BackCompat'表示混杂模式) ---HTML5新增
    • document.charset (表示文档中实际使用的字符集,也可用来指定新字符集) ---HTML5新增
    • document.dataset (表示通过dataset访问自定义属性,如document.dataset.myname) ---HTML5新增
    • document.docType (表示 <!DOCTYPE>元素), 存在浏览器兼容性问题 ---HTML5新增
    • document.title (表示 < title > 元素)
    • ··· 网页请求 ···
    • document.URL (获取URL地址)
    • document.domain (获取URL中的域名,pathname)
    • document.attributes (获取某个节点的属性,返回NamedNodeMap对象,与NodeList类似)
    • ··· 焦点管理,无障碍性访问 ···
    • document.activeElement: 获取页面上获得焦点的元素,通过tab键或者focus函数获得焦点 ---HTML5新增
    • ··· 判断文档是否加载完成 ···
    • document.readyState:存在两个值,一是'loading',二是'complete'。如果document.readyState === 'complete',表明文档已经加载完成。即在DOMContentLoaded事件之后。 ---HTML5新增
  • 方法
    • ··· 查找元素 ···
    • document.getElementById(id) 返回该元素
    • document.getElementsByTagName(classname) 返回包含零个或多个元素的HTMLCollection对象,与NodeList对象相似
    • document.getElementsByName(ele)返回带有给定name属性的元素,同样返回HTMLCollection对象
    • document.getElementsByClassName(className) 返回所有匹配的NodeList对象 (可在Document类型、Element类型上调用该方法)
    • document.querySelector(selector) selector表示CSS选择符 返回与该模式匹配的第一个元素,如果没有找到,返回null (Document类型, DocumentFragment类型, Element类型都可以调用此方法)
    • document.querySelectorAll(selector) selector表示CSS选择符 返回一个匹配成功的NodeList对象 (Document类型, DocumentFragment类型, Element类型都可以调用此方法)
    • ··· 创建元素 ···
    • document.createElement() (创建好的元素处于游离状态,需要通过appendChild插入)
    • ··· 创建文本节点 ···
    • document.createTextNode() (创建好的元素处于游离状态,需要通过appendChild插入)
    • ··· 确定元素大小 ···
    • document.getBoundingClientRect()
    • ··· 焦点管理,无障碍性访问 ···
    • document.hasFocus():用于判断页面上是否获得焦点,获得返回true;否则返回false

Element类型

  • 属性
    • id
    • title
    • lang
    • className
  • 方法
    • getAttribute(ele) 获取某个属性
    • setAttribute(name, value) 设置某个属性
    • removeAttribute(ele) 移除某个属性
    • getElementsByTagName(ele) 获取标签名为ele的元素
插入标记

动态插入DOM节点有以下方法:使用诸如 createElement()和 appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。

在使用innerHTML, outerHTML, inertAdjacentHTML方法时,需要注意一点的是,由于这三个方法都是直接替换指定元素下的DOM节点,因此当被替换的元素存在事件处理程序时,被替换的事件处理程序并未被一同替换,而是仍然存在内存中。所以,在替换前,需要手动接触被替换元素的事件处理程序。这属于DOM方面的前端性能优化问题。

div.removeEventListener('click', fn, false)
或者是
div.onclick = null
  • 属性
    • innerHTML属性:用于替换当前元素下的所有子元素。如document.body.innertHTML = '< h1 > 哈哈哈 < /h1 >',此时就用h1标签替换掉了body元素下的所有子元素。IE8+以上浏览器支持
    • outerHTML属性:用于替换自身及其所有子元素。IE9+以上浏览器支持
  • 方法
    • insertAdjacentHTML方法:这个方法可以配合innerHTML属性一起使用,代替appendChild, insertBefore方法。IE6+以上的浏览器都支持这方法。此方法需要两个参数,第一个参数为给定的四个字符串。第二个参数是需要插入的DOM节点。使用方法非常简单。
      • 'beforebegin': 在当前元素前插入一个兄弟元素
      • 'beforeend': 在当前元素元素之下插入一个新的子元素或者在第一个子元素之前插入一个新的子元素
      • 'afterbegin': 在当前元素之下插入一个新的子元素或在最后一个子元素之后插入一个新的子元素
      • 'afterend': 在当前元素之后插入一个紧邻的同辈元素
<div class="inner">
<div class="child">1</div>
</div> let inner = document.querySelector('.inner')
let element = '< div class="element" >new element< /div >'
inner.insertAdjacentHTML('beforebegin', element) // 作为.inner前一个兄弟元素存在
inner.insertAdjacentHTML('beforeend', element) // 作为.inner第一个子元素
inner.insertAdjacentHTML('beforebegin', element) // 作为.inner后一个兄弟元素存在
inner.insertAdjacentHTML('beforebegin', element) // 作为.inner最后一个子元素存在
DOM元素节点遍历

在写HTML时,我们会用tab或者回车键来对各个标签进行排版布局。当我们使用DOM中的childNodes或者firstChild、nextSibling方法时,会获取到换行之后的空文本节点(nodeType === 3)。如

    <p>previous</p>
<div class="inner">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
</div>
<p>next</p> let div = document.querySelector('.inner')
console.log(div.childNodes)
// 返回 (9) [div.child, text, div.child, text, div.child, text, div.child, text, div.child]
// 这种返回值是不符合我们的预期的。

因此,存在以下方法可以用来获取元素节点,排除不必要的文本节点。这几种属性和方法是扩展在Element.prototype原型对象上的。支持的浏览器有IE 9+、Firefox 3.5+、 Safari 4+、Chrome ֖、Opera 10+。

Element.prototype.childElementCount // 子元素节点数量
Element.prototype.firstElementChild // 第一个子元素节点
Element.prototype.lastElementChild // 最后一个子元素节点
Element.prototype.previousElementSibling // 上一个兄弟元素节点
Element.prototype.previousElementSibling // 下一个兄弟元素节点 div.childElementCount // 5
div.firstElementChild // <div class="child">1</div>
div.firstChild // <div class="child">1</div>
div.lastElementChild // ... 5 ...
div.lastElementChild // ... 5 ...
div.previousElementSibling // <p>previous</p>
div.previousSibling // '' 注意区别 previousSibling返回空文本节点
div.nextElementSibling // <p>next</p>
div.nextSibling // '' 注意区别 nextSibling返回空文本节点

当然,也可以使用children属性来代替childNodes。children属性会返回子元素同样是元素节点的NodeList集合,而忽略注释和空白节点。即div.children.length === div.childElementCount

classList属性

HTML5在Element.prototype上新增了classList属性,用于对某个元素的类型进行操作。以往通常是是用className属性对类型进行操作,由于className返回字符串,所以想删除某个类名时需要进行一些繁琐的操作。

<div class="outer happy fun">...</div>
// 删除happy
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(" ");

使用classList属性可以避免以上的操作。classList返回类数组对象,存在以下方法

  1. add(value): 增加类名
  2. remove(value): 移除类名
  3. contains(value): 是否包含类名,包含则返回true;否则返回false
  4. toggle(value): 如果存在value类,则删除该类名;如果不存在value类,则添加该类名

因此,可以使用classList属性简化上面的操作

let div = document.querySelector('.outer')
div.classList.remove('happy')
contains方法

此方法用于确认某个节点下是否包含另一个DOM节点,如果存在则返回true;否则返回false;需要传入一个DOM节点作为参数。IE6+以上浏览器都支持。

document.documentElement.contains(document.body) // true
document.body.contains(document.head) // false

Text类型

  • 属性
    • nodeValue | data (访问Text节点中的文本)

DocumentFragment类型

用途:离线操作DOM元素,避免DOM节点大量的重排和重绘,造成性能问题

  • 方法
    • document.createDocumentFragment() (表示创建文档片段)

NodeList对象

理解 NodeList 及其“近亲”NamedNodeMap 和 HTMLCollection,是从整体上透彻理解 DOM 的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。因此,它们始终都会保存着最新、最准确的信息。从本质上说,所有NodeList 对象都是在访问 DOM 文档时实时运行的查询。

元素大小

偏移量(offset dimension)

要想知道某个元素在页面上的偏移量,将这个元素的 offsetLeft 和 offsetTop 与其 offsetParent的相同属性相加,如此循环直至根元素,就可以得到一个基本准确的值。以下两个函数就可以用于分别取得元素的左和上偏移量。

function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
} function getElementTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current. offsetTop;
current = current.offsetParent;
}
return actualTop;
}

客户区大小(client dimension)

要确定浏览器视口大小,可以使用 document.documentElement 或 document.body(在IE7 之前的版本中)的clientWidth 和 clientHeight。

function getViewport(){
if (document.compatMode == "BackCompat"){
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
}

滚动大小(scroll dimension)

·················

确定元素大小

document.getBoundingClientRect()方法, 返回一个矩形对象。包含4个属性:left、top、right和bottom。这些属性给出了元素在页面中相对于视口的位置。