第十五章:DOM拓展
尽管DOM API已经很不错了,但是仍然不断有新的标准或专有的拓展出现,以此来支持更多的功能;W3C正在着手于指定相关的规范。
在此期间,诞生了几个比较受欢迎的DOM拓展:Selectors、HTML5;另外还有较小的Element Traversal规范。
15.1 Selectors API
Selectors API 是 W3C推荐标准,其规定了浏览器原生支持CSS查询的API;
Selectors API Level 1有两个核心方法:querySelector()
和querySelectorAll()
Selectors API Level 2在Element类型上,新增了更多方法,如:matches()
、find()
、findAll()
;但并未被浏览器声明许可;
通过浏览器原生支持API,解析和遍历DOM树可以通过底层编译语言实现,使得性能有了数量级的提升
15.1.1 querySelector()
querySelector()
方法,接收一个参数:
- CSS选择符
返回匹配该模式的第一个后代元素;如果不匹配,返回null。
// 获取body元素
let body = document.querySelector('body');
// 获取body元素下的类名为myClass的第一个元素
let myDiv = body.querSelector('.myClass');
// 获取类名为button的图片
let img = document.body.querySelector('img.button');
显然,如果在document上使用,则会从文档开始搜索;如果在元素上使用,则会在该元素的后代中开始搜索。
注意:如果选择符有错误,该方法则会抛出错误;
15.1.2 querySelectorAll()
使用和querySelector()
一样,但是返回值有所不同:
- 如果匹配成功,返回一个
NodeList
的静态实例;反之返回一个空的NodeList
的静态实例。
注意:这里的NodeList
实例并不是"实时的",只是当前状态下,查询到结果的一个*“快照”***
与
querySelector()
一样,也会因为选择符错误而抛出错误
15.1.3 matches()
matches()
接收一个参数,用于判断是否包含带有该CSS选择符的元素。
- CSS选择符
如果匹配,则返回true;反之,返回false。
15.2 元素遍历
由于有些浏览器,或不同版本的浏览器,会把元素间空格当成空白节点;
W3C通过新的Element Traversal规范,给DOM元素定义一组新的属性:
childElmentCount
,返回子元素数量(不包括文本节点和注释);firstElementChild
,指向第一个Element类型的子元素(Element版的firstChild
);lastElementChild
, 指向最后一个Element类型的子元素(Element版lastChild
) ;previousElementSibling
, 指向前一个Element类型的同胞元素(Element版previousSibling
) ;nextElementSibling
, 指向后一个Element类型的同胞元素(Element版nextSibling
) 。
如果不适用Element Traversal规范,遍历子元素的话,可以这样做:
let parentElement = document.getElementById('parent');
let currentChildNode = parentElement.firstChild;
// 一直遍历子元素,直至为null
while( currentChildNode ) {
if( currentChildNode.nodeType === 1){ // 判断是否为Element类型
// 若果是,则进行元素操作
}
if( currentChildNode === parentElement.lastChild ) { // 如果是最后一个元素,就退出遍历
break;
}
currentChildNode = currentChildNode.nextSibling;
}
可以使用Element Traversal规范改写
let parentElement = document.getElementById('parent');
let currentChildNode = parentElement.firstChildElement;
// 一直遍历子元素,直至为null
while( currentChildNode ) {
// 直接对元素进程操作
// ...
// 判断是否为最后一个元素
if( currentChildNode === parentElement.lastChildElement ) {
break;
}
currentChildNode = currentChildNode.nextElementSibling;
}
15.3 HTML5
HTML5代表着与以往HTML截然不同的方向;在以往的HTML中,从来没有出现过描述JavaScript接口的情形。关于JavaScript绑定的事,一律交给DOM规范去定义。
在HTML5规范中,包含了与标记相关的大量JavaScript API定义。
15.3.1 CSS类拓展
自HTML4依赖,页面中最大的变化就是class属性用得越来越多。所以HTML增加了方便使用CSS类的API。
-
getElementsByClassName()
方法接收一个参数:- 包含一个或多个类名的字符串
该方法返回类名中,包含相应类的元素的
NodeList
(这个NodeList
实例是实时的)let aim = document.getElemenstByClassName('current active'); // 获取文档中,所有类名中,包含‘current’和‘active’的元素;
注意:必须是类名中,既有current 也有 active的元素。
-
classList
属性如果要操作类名,可以通过
classList
属性来实现添加、删除和替换;由于
classList
属性是一个字符串,所以每次操作后,重新全部设值。(会显得有点麻烦)// <div class="current active username"></div> // 需要删除username类名 let targetClass = 'username'; let classNames = div.className.split(/\s+/); // 按照任何空白符号分割 // 获取需要删除元素的下标 let index = classNames.indexOf(targetClass); if( index > -1) { classNames.splice(index,1); //删除该下标的元素 } // 重新给div设置类名 div.className = classNames.join(" ");
与此同时,
classList
是一个新的集合类型:DOMTokenList
的实例,也拥有length
、item()
、[]
的方法,此外还新增了如下方法:- add(value), 向类名列表中添加指定的字符串值value。 如果这个值已经存在, 则什么也不做。
- contains(value), 返回布尔值, 表示给定的value是否存在。
- remove(value), 从类名列表中删除指定的字符串值value。
- toggle(value), 如果类名列表中已经存在指定的value, 则删除; 如果不存在, 则添加。
显然,多了如上方法后,删除类名就方便多了。
div.remove('username');
15.3.2 焦点管理
HTML5新增了辅助DOM焦点管理的功能;
document.activeElement
:始终指向当前拥有焦点的DOM元素。- 在页面完成加载之后,指向
document.body
- 在页面加载完成之前,指向
null
- 在页面完成加载之后,指向
element.hasFocus()
:判断是否获取了焦点- 返回布尔值,true代表获取了焦点
let button = document.getElementById('myButton');
button.focus();
console.log(document.activeElement === button); // true
console.log(button.hasFocus()); // true
15.3. 3 HTMLDocument
拓展
-
document.readyState
属性,可能有如下两个值:- loading:表示文档正在加载。
- complete:表示文档加载完成。
if( document.readyState == 'complete' ){ // do something }
在这个属性之前,开发者通常使用
window.onload
事件处理程序设置一个标记。 -
compatMode
属性,有如下两个值:CSS1Compat
:标准模式渲染;BackCompat
:混杂模式渲染。
-
document.head
属性,始终指向<head>
元素。
15.3.4 字符集属性
使用characterSet
属性,可以获取字符集属性。
15.3.5 自定义数据属性
在HTML5中,允许自定义非标准的属性,但是需要使用前缀data-
告诉浏览器。
<div data-name="div1"
data-myName="div2"
data-my-name="div3">
hello world
</div>
定义了自定义数据后,可以通过元素的dataset
属性访问;
dataset
时一个DOMStringMap
实例,包含一组键值对的映射。
可以直接通过data-
后面的名字来访问:
/*
<div
id="mydiv"
data-name="div1"
data-myName="div2"
data-my-name="div3">
hello world
</div>
*/
let div = document.getElementById('mydiv');
console.log(div);
console.log(div.dataset)
console.log(div.dataset.name); // div1
console.log(div.dataset.myname); // div2
console.log(div.dataset.myName); // div3
注意:第一个除外,只要被—
分割,开头就必需大写!
自定义数据属性非常适合需要给元素附加某些数据的场景, 比如链接追踪和在聚合应用程序中标识页面的不同部分。 另外, 单页应用程序框架也非常多地使用了自定义数据属性。
15.3.6 插入标记
DOM虽然提供了很多操作节点的API,但是一次性插入大量HTML还是比较麻烦的;
HTML5新增了一些插入标记,简化了其操作;
-
innerHTML属性
在读取
innerHTML
属性时,会返回元素所有后代的HTML字符串,包括元素、注释和文本节点;在写入
innerHTML
属性时,会根据提供的字符串,以新的DOM子树代替元素中的所有节点。/* <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> */ let div = document.getElementById('content'); console.log(div.innerHTML); /* 会输出如下内容 <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> */ div.innerHTML = '<p>This is a <strong>paragraph</strong> with a list following it.</p>' /* 会变成这样 <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> </div> */
读取
innerHTML
的值,可能会因为浏览器的不同而有所不同。 -
innerHTML的注意点:
在所有现代浏览器中,通过
innerHTML
插入<script>
都是不会执行的;在IE8及之前的版本中,通过
innerHTML
插入非可控标签(<script>
、<style>
、注释
)都是不会执行的;需要在非可控标签前,添加可控标签。div.innerHTML = " _ <script defer><\/script>"; // 字符串充当可控标签 div.innerHTML = "<div> </div><script defer><\/script>"; // 充当可控标签
注意:script或style都需要额外添加defer属性
-
outerHTML属性:
与
innerHTML
类似,但是outerHTML
会返回包含自身在内的HTML字符串;/* <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> */ let div = document.getElementById('content'); console.log(div.innerHTML); /* 会输出如下内容 <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> */ div.innerHTML = '<p>This is a <strong>paragraph</strong> with a list following it.</p>' /* 会变成这样 <p>This is a <strong>paragraph</strong> with a list following it.</p> */
-
insertAdjacentHTML()
HTML5新增的方法,接收两个参数
- 要插入标记的位置
- 插入的HTML字符串
而且,第一个参数必需是如下值中的一个:
beforebegin
, 插入当前元素前面, 作为前一个同胞节点;afterbegin
, 插入当前元素内部, 作为新的子节点或放在第一个子节点前面;beforeend
, 插入当前元素内部, 作为新的子节点或放在最后一个子节点后面;afterend
, 插入当前元素后面, 作为下一个同胞节点。
注意:假设为
<p>Hello World</p>
;beforebegin
中的begin指的是:<p>
beforeend
中的end指的是:</p>
-
其他方法
除了上述的三个方法外,其实还有与它们类似的三个方法,只需要把HTML替换成Text即可;
用法与上述三个方法一致,只是传参的字符串,不会被解析为HTML,只会解析为纯文本。
-
内存与性能问题:
上述介绍的方法,达到替换子节点的功能;但是会存在一定的问题:
- 原来绑定元素引用的JavaScript对象、事件处理程序还停留在内存中
如果这种替换操作频繁发生,则会导致内存占用攀升;
最好手动删除被替换元素上的关联程序、对象。
-
跨站点脚本
网站攻击者,容易通过
innerHTML
注入数据,造成XSS攻击!
15.3.7 scrollIntoView()
该方法存在于HTML
元素上,可以滚动浏览器窗口或容器元素,以便包含元素进入视口。
其接收如下参数:
alignToTop
是一个布尔值。- true: 窗口滚动后元素的顶部与视口顶部对齐。
- false: 窗口滚动后元素的底部与视口底部对齐。
scrollIntoViewOptions
是一个可选选项对象。- behavior: 定义过渡动画, 可取的值为"smooth"和"auto", 默认为"auto"。
- block: 定义垂直方向的对齐, 可取的值为"start"、 “center”、 “end"和"nearest”, 默认为"start"。
- inline: 定义水平方向的对齐, 可取的值为"start"、 “center”、 “end"和"nearest”, 默认为"nearest"。
- 不传参数等同于
alignToTop
为true
。
15.4 专有拓展
尽管所有浏览器都知道遵循标准的重要性,但是不可避免的,它们也有一些专属的拓展,用于弥补自身功能的缺失。
这里就不详细介绍了,有需要可以自行查阅。
15.5 小结
- Selectors API为基于CSS选择符获取DOM元素定义了几个方法:
querySelector()
、querySelectorAll()
和matches()
。 Element Traversal
在DOM元素上定义了额外的属性, 以方便对DOM元素进行遍历。 这个需求是因浏览器处理元素间空格的差异而产生的。- HTML5为标准DOM提供了大量扩展。 其中包括对
innerHTML
属性等事实标准进行了标准化, 还有焦点管理、 字符集、 滚动等特性。