本章内容:介绍Selectors API、HTML5 DOM扩展、了解专有的DOM扩展
对DOM的主要扩展是 Selectors API(选择符API)和HTML5,这两个扩展都源自开发社区,而降某些常见做法及API标准化一直是众望所归
一、选择符API
Selectors API 是由W3C发起指定的一个标准。selectors API Level1 的核心是两个方法:querySelector() 和 querySelectorAll()。可以通过 Document 或 Element 实例调用它们。
目前已完全支持 Selectors API Level1 的浏览器由 IE8+、Firefox3.5+、Safari3.1+、Chrome 和 Opera10+
1.1、 querySelector( selector )
方式接收一个参数,即一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到则返回 null。
// 获取 body 元素
var body = document.querySelector('body')
// 获取 id为div 的元素
var div = body.querySelector('#div')
如果传入不支持的选择符,querySelector() 会抛出错误。
1.2、querySelectorAll( selector )
接收一个CSS选择器,返回匹配到的所有元素。这个方法返回的是一个 NodeList 实例。如果没有匹配的元素,NodeList为空。能够调用此方法的类型包括:Document、Element、ElementFragment。
返回的值实际上是带有所有属性和方法的NodeList,而其底层则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。
// 获取所有li 元素
document.querySelectorAll('li')
// 获取 id="div" 下面的 所有 class="link" 的标签
document.querySelector('#div').querySelectorAll('.link')
因为返回的是一个NodeList,所有 可以通过 item() 或 方括号 进行访问里面的元素。
document.querySelectorAll('li').item(0)
document.querySelectorAll('.link')[0]
与 querySelector()类似,如果传入了浏览器不支持的选择符或者选择符中有语法错误,会抛出异常。
1.3、matchesSelector( selector )
Selector API Level 2规范为 Element 类型 新增了一个方法 matchSelector()。这个方法接收一个CSS选择符。如果调用这个方法的元素与选择符匹配,则返回 true,反之返回 false。
if (document.body.matchesSelector('body')) {
// todo
}
IE9+通过 msMatchesSelector() 支持该方法;Firefox3.6+通过 mozMatchesSelector();Safari5+ 和 Chrome 通过 webKitMatchesSelector()
包装函数:
function matchesSelector(element, selector) {
if (element.matchesSelector) {
return element.matchesSelector(selector)
} else if (element.msMatchesSelector) { // IE
return element.msMatchesSelector(selector)
} else if (element.mozMatchesSelector) { // Firefox
return element.mozMatchesSelector(selector)
} else if (element.webkitMatchesSelector) { // Safari Chrome
return element.webkitMatchesSelector(selector)
} else {
throw new Error('Not supported')
}
}
二、元素遍历
对于元素间的空格,IE9及之前版本不会返回文本节点,其他所有浏览器都会返回文本节点。为了弥补这一差异,而同时又保持DOM规范不变,Element Travel规范定义了一组属性
Element Traversal API 为 DOM 元素添加了以下5个属性
- childElementCount:返回子元素(不包括文本节点和注释)的个数。
- firstElementChild:返回第一个子元素;firstChild的元素版。
- lastElementChild:指向最后一个子元素;lastChild的元素版。
- previousElementSibling:指向前一个同辈元素;previousSibling的元素版。
遍历元素
var ul = document.getElementsByTagName('ul')[0]
var child = ul.firstElementChild
while(child != null) { // 最后一个元素 的 nextElementSibling 为 null
console.log(child)
child = child.nextElementSibling
}
支持Element Traversal 规范的浏览器有IE9+、Firefox3.5+、Safari 4+、Chrome 和 Opera10+
三、HTML5
对传统HTML而言,HTML5是一个叛逆,HTML5规范围绕如何使用新增标记定义大量 JavaScript API。其中一些API 与 DOM 重叠,定义了浏览器应该支持的DOM扩展。
3.1、与类相关的扩充
HTML4 在Web开发领域得到广泛采用后导致了一个很大的变化,即class属性用得越来越多,一方面可以通过它为元素添加样式,另一方面还可以用它表示元素的语义。HTML5新增了很多API,致力于简化CSS类的用法。
3.1.1. getElementsByClassName( className )
接受一个参数,一个包含一或多个类名的字符串,返回带有指定类的所有元素的 HTMLCollection
var links = document.getElementsByClassName('link')
使用这个方法与使用 getElementByTagName()一起其他返回集合的DOM方法具有相同的性能问题。
getElementsByClassName() 方法的浏览器有IE9+、Firefox3+、Safari3.1+、Chrome 和 Opera 9.5+
3.1.2、classList 属性
在操作类名时,需要通过className属性添加、删除和替换类名。因为className是一个字符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值。
HTML5新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所以元素添加classList属性。这个classList属性时新集合类型 DOMTokenList的实例。与其它集合类似具有表示自己包含多少元素的length属性。取得某一项可以通过 item 或 中括号。
这个类型定义了如下方法:
- add(value):将给定的字符串值添加到列表中,如果值以及存在,就不添加了。
- contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回false。
- remove(value):从列表中删除给定的字符串
- toggle(value):如果列表中以及存在给定的值,删除它;如果列表中没有给定的值,添加它。
<div id="div" class="bg container disable readonly"></div>
var div = document.getElementById('div')
console.log(div.className) // bg container disable readonly
div.classList.add('violet') // 添加
div.classList.remove('container') // 移除
div.classList.toggle('bg') // toggle
div.classList.toggle('readonly') // toggle
// 验证是否存在
console.log(div.classList.contains('container')) // false
console.log(div.classList.contains('violet')) // false
console.log(div.className) // violet disable
兼容:Firefox3.6+、Chrome8+、IE10+、Safari5.1+、Opera11.5+
3.2、焦点管理
HTML5也添加了辅助管理DOM焦点的功能。documentt.activeElement属性,这个属性始终会引用DOM中当前获得焦点的元素。元素获得焦点的方式有 页面加载、用户输入(通常是按 tab 键)、调用focus()方法。
默认情况下(文档刚加载完成),document.activeElement中保存的是 document.body 元素的引用。文档加载期间,document.activeElement 的值为 null。
var button = document.getElementById('btn')
btn.focus()
console.log(document.activeElement == button) // true
另外新增 document.hasFocus() 方法,这个方法用于确定文档是否获得焦点。
if (document.hasFocus()) {
// todo
}
通过检测文档是否获得焦点,可以知道用户是不是正在跟页面交互。
查询文档获知哪个元素获得焦点,这两个功能最重要的用途是提高 Web 应用的无障碍性。无障碍 Web应用的一个主要标志就是恰当的焦点管理,而确切的知道哪个元素获得了焦点是一个极大的进步。
兼容:IE4+、Firefox3+、Safari4+、Chrome、Opera8+
3.3、 HTMLDocument 的变化
HTML5扩展了HTMLDocument,增加了新的功能。
3.3.1、 readyState 属性
IE4 最早为 document 对象引入了 readyState 属性。readyState 属性有两个 可能的值‘
- loading:正在记载文档
- complete:以及加载完文档
使用 document.readyState的最恰当方式,就是通过它来实现一个指示文档已经加载完成的指示器。这个属性之前,在实现这种功能,必须借助 onload 事件
if (document.readyState == 'complete') {
// todo
}
兼容:IE4+、Firefox3.6+、Safari、Chrome、Opera9+
3.3.2、 兼容模式
IE6开始区分渲染页面是标准的还是混杂的,检测页面兼容模式成为浏览器的必要功能。IE给document添加了 compatMode 属性。标准模式下这个属性的值为“CSS1Compat”;在混杂模式下,这个属性的值为“BackCompat”
if (document.compatMode == 'CSS1Compat') { // 标准模式
// todo
} else { // 混杂模式
// todo
}
陆续实现这个属性的浏览器有:Firefox、Safari3.1+、Opera、Chrome。最终,HTML5也把这个属性纳入标准,对其实现做出了明确规定。
3.3.3、 head属性
document.body 引用文档的 <body> 元素的补充,HTML5新增了 document.head 属性,作为文档的 <head> 元素。
var head = document.head || document.getElementByTagName('head')[0] // 兼容低版本浏览器
实现该属性的浏览器包括:Chrome、Safari5、IE9+、Firefox4+、Opera11.5+
3.4、字符集属性。
HTML新增几个与文档字符有关的属性
characterSet
该属性表示文档中实际使用的字符集,也可以用来指定新的字符集。默认值(文档未设置编码)视浏览器而定
console.log(document.characterSet)
document.charset = 'UTF-8'
3.5、自定义数据属性
HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。
<div id="div" data-appid="7758"></div>
添加自定义属性之后,可以哦通过元素的 dataset 属性进行访问。dataset属性的值是 DOMStringMap 的一个实例,也就是一个键值对的映射(也可以使用 getAttribute())
var div = document.getElementById('div')
console.log(div.dataset.appid)
dataset存在将所有字符转换成小写的问题。支持的浏览器有:IE11、Firefox6+、Chrome7+、Safari5.1+、Opera11.5+
3.6、插入标记
DOM为操作节点提供了细致入微的控制手段,但在需要给文档插入大量新HTML 标记的情况下,使用插入标记的技术,直接插入HTML不久 更简单,速度也更快。
3.6.1、 innerHTML 属性
读取,innerHTML 返回与调用元素的所有子节点(包括元素、注释、文本节点)对应的HTML标记。
写入,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
var ul = document.getElementById('ul')
// 读取
console.log(ul.innerHTML)
/*
<li>111</li>
<li>222</li>
<li>333</li>
*/
不同浏览器返回的文本格式会有所不同。IE8- 和 Opera 会将所有标签转换为大写形式,而Safari、Chrome、Firefox 则会原原本本地按照原先文档中的格式返回HTML。
// 写入
ul.innerHTML = 'Hello World'
在写模式下,innerHTML 的值会被解析为DOM子树,替换调用元素原来的所有子节点。
使用innerHTML 属性也有一些限制。大多数浏览器中,innerHTML 插入 <script> 不会执行其中的脚本。IE8及更早版本中是唯一能在这种情况下执行脚本的浏览器,但必须满足一些条件:
- 必须为 <script> 元素 指定 defer 属性
- <script> 元素必须位于“有作用域元素(scoped element)”之后。<script>元素被认为是“无作用域元素(NoScoped element)”
div.innerHTML = '<script defer>alert('gg')</script>' // 无效
上面这段代码不会有预期的效果,必须在前面添加一个“有作用域的元素”,可以是一个文本节点,也可以是一个没有结束标签的元素如<input>
div.innerHTML = '_<script defer>alert("hi")</script>'
div.innerHTML = '<div> </div><script defer>alert("hi")</script>'
div.inenrHTML = '<input type="hidden"><script defer>alert("hi")</script>'
这三种方式都可以达到预期的效果,相比之下<input>最佳,不会影响到布局,因此这种方式在大多数情况下都是首选。
大多浏览器都支持以直观的方式通过 innerHTML 插入<style>元素:
div.innerHTML = '<style type="text/css">body { background: cyan }</style>'
IE8 及更早版本中,<style>也是一个”没有作用域元素“,因此与 <script> 类似
div.innerHTML = '_<style type="text/css">body { background: cyan }</style>'
div.removeChild(div.firstChild) // 移除 _
3.6.2、 outerHTML 属性
读模式下,outerHTML返回调用它的元素及其子节点的HTML标签。
写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换元素
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
在 <ul> 元素上 调用 outerHTML 会返回与上面相同的代码。
使用outerHTML属性以下面这种方式设置值:
ul.outerHTML = '<p>This is a paragraph</p>'
上面代码的操作与下面的DOM脚本一样。
var p = document.createElement('p').appendChild(document.createTextNode('This is a paragraph'))
ul.parentNode.replaceChild(p, ul)
兼容:IE4+、Safari4+、Chrome、Opera8+、Firefox8+
3.6.3、inertAdjacentHTML()
插入标记的最后一个新增方式是,insertAdjacentHTML()。最早在IE中出现,接收两个参数:插入位置、要出入的HTML文本。
第一个参数必须是下列值之一
:
- beforebegin:在当前元素之前插入一个紧邻的同辈元素
- afterbegin:在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素
- beforeend:再当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素
- afterend:在当前元素之后插入一个紧邻的同辈元素
// 作为前一个同辈元素插入
element.insertAdjacentHTML('beforrebegin', '<p>Hello World</p>')
// 作为第一个子元素插入
element.insertAdjacentHTML('afterbegin', '<p>Hello World</p>')
// 作为最后一个子元素插入
element.insertAdjacentHTML('beforeend', '<p>Hello World</p>')
// 作为后一个同辈元素
element.insertAdjacentHTML('afterend', '<p>Hello World</p>')
支持 insertAdjacentHTML() 方法的有:IE、Firefox8+、Safari、Opera、Chrome
3.6.4、内存与性能问题
使用方法替换子节点会导致浏览器的内存占用问题,尤其在IE中,删除带有事件处理程序或引用了其他JavaScript对象子树时,有可能导致内存占用问题。假设一个事件处理程序(或者引用一个JavaScript对象作为属性),从文档树中删除后,元素与事件处理程序(或JavaScript对象)之间的绑定关系在内存中并没有以并删除。这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用innerHTML、outerHTML、insertAdjacentHTML() 方法时,最好先手工删除要替换的元素的所有事件处理程序和JavaScript对象属性。
不过,使用这几个属性——特别是使用innerHTML,与通过多次DOM操作先创建节点再指定它们的关系相比,效率要高得多。
3.7 scrollIntoView() 方法
HTML5 提供 scrollIntoView() 作为标准方法
scrollInterView() 可以再所有 HTML 元素上调用,通过让浏览器窗口或某个容器元素滚动,使调用元素出现在视口中。
这个方法接收一个bool参数:
true(默认):窗口滚动之后会让调用元素的顶部与视口尽可能平齐。
false:调用元素尽可能全部出现在视口中(可能的话,调用元素的底部会与视口底部平齐)。
// 让元素可见
document.form[0].scrollIntoView()
四、专有扩展
浏览器开发商在发现某些功能缺少的时候,会向DOM中添加专有扩展,以弥补功能上的不足。表面上看,这种各行其是的做法似乎不太好,但实际上专有扩展为Web开发领域提供了很多重要的功能,这些功能大多在HTML5规范中得到了标准化。
4.1、 文档模式
IE8引进一个新的概念叫”文档模式(document mode)“,决定了可以使用上面功能。文档模式决定了你可以使用哪个级别的CSS,可以在JavaScript中使用哪些API,如何对待文档类型(doctype)。到IE9,总共有4种文档模式:
- IE5:以混杂模式渲染页面(IE5的默认模式时混杂模式)。IE8及更高版本中的新功能都无法使用。
- IE7:以IE7标准模式渲染页面。IE8及更高版本中的新功能都无法使用
- IE8:以IE8标准模式渲染页面。IE8中的新功能都可以使用,因此可以使用SelectorsAPI、更多 CSS2级选择符和某些CSS3功能,还有一些HTML5的功能。不过IE9中的新功能无法使用
- IE9:以IE9标准模式渲染页面。IE9中的新功能都可以使用,比如ECMAScript5、完整的CSS3以及HTML5功能。这个文档模式是最高级的模式。
要强制浏览器以某种模式渲染也i按,可以使用HTTP头部信息X-UA-Compatible, 或通过 <meta> 标签来设置
<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
这里IE的版本(IEVersion)有以下一些不同值,这些值并不一定与上述4种文档模式对应。
- Edge:始终以最新的文档模式来渲染页面。忽略文档类型的声明。对于IE8,始终保持以IE8标准模式渲染页面。对于IE9,则以IE9模式渲染页面。
- EmulateIE9:如果有文档类型声明,则以IE9标准模式渲染页面,否则将文档模式设置为IE5。
- EmulateIE8:如果有文档类型声明,则以IE8标准模式渲染页面,否则将文档模式设置为IE5。
- EmulateIE7:如果有文档类型声明,则以IE7标准模式渲染页面,否则将文档模式设置为IE5。
- 9:强制以IE9标准模式渲染页面,忽略文档类型声明。
- 8:强制以IE8标准模式渲染页面,忽略文档类型声明。
- 7:强制以IE7标准模式渲染页面,忽略文档类型声明。
- 5:强制将文档模式设置为IE5,忽略文档声明。
让文档模式像在IE7种一样
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7">
没有规定说必须在页面设置 X-UA-Compatible。默认情况下,浏览器会通过文档类型声明来确定是使用最佳的可用文档模式,还是使用混杂模式。(通过 document.documentMode 获取当前页面的模式)
4.2、children 属性
IE之前的版本在处理文本节点中的空白字符时有差异,因此就出现了 children属性。这个属性时 HTMLCollection 的实例,只包含元素中同样还是元素的子节点,除此之外 children 和 childNodes 没有声明区别。
兼容:IE5、Firefox3.5、Safari2(有bug)、Safari3(完全支持)、Opera8、Chrome。IE8及更早版本的 children 属性中也会包含注释节点,但 IE9 之后的版本则只返回元素节点。
4.3、 contains() 方法
在实际开中,可能会遇到这种需求:需要知道某个系欸但是不是另外一个节点的后代。
调用contains() 方法的应该时祖先节点,也就是搜索开始的节点。
这个方法接收一个参数
:
即要检测的后代节点,返回 true 或者 false
console.log(document.documentElement.contains(document.body)) // true
兼容:IE、Firefox9+、Safari、Opera、Chrome
DOM Leval 3 中 compareDocumentPosition() 也能够确定节点间的关系。这个方法用于确定两个节点间的关系,返回一个表示该关系的位掩码( bitmask )。
掩码 | 节点关系 |
---|---|
1 | 无关(给定的节点不再当前文档中) |
2 | 居前(给定的节点在DOM数中位于参考节点之前) |
4 | 居后(给定的节点在DOM树种位于参考节点之后) |
8 | 包含(给定的节点是参考节点的祖先) |
16 | 被包含(给定的节点是参考节点的后代) |
为模仿 contains() 方法,应该关注的是 掩码 16。可以对结果进行按位与计算
var result = document.documentElement.compareDocumentPosition(document.body) // 20
console.log(!!(result & 16)) // true
结果20(表示”居后“的4 加上 表示”被包含“的16)。对掩码16执行按位操作会返回一个非零数值,而两个逻辑非操作符会将该数值转换成布尔值
兼容:IE9+、Firefox、Safari、Opera9.5+、Chrome
4.4、 插入文本
前面提到,IE专有的插入标记的属性 innerHTML 和 outerHTML 以及被HTML5 纳入规范。但另外两个插入文本的专有属性则没有这么号的运气。它们分别是 innerText 和 outerText。
4.4.1、innerText 属性
innerText属性可以操作元素中包含的所有文本内容,包括子文档树的文本,
读取值时:它会按照由浅入深的顺寻,将子文档树种的所有文本拼接起来
写入值时:结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
以下面的HTML 代码实例:
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
对这个 <ul> 元素而言,其 innerText 会返回下列字符串。
111
222
333
使用 innerText 属性设置这个 <ul> 元素的内容,会移除先前存在的所有子节点,完全改变DOM子树
document.getElementById('ul').innerText = 1
innerText 永远只会生成当前节点的一个文本节点,利用这一点,可以通过innerText属性 过滤掉 HTML 标签。如下:
var ul = document.getElementById('ul')
ul.innerText = ul.innerText
兼容:IE4+、Safari3+、Opera8+、Chrome
Firefox 不支持这个属性,但却支持作用类似 的 textContent属性,这个属性时DOM Level 3规定的一个属性,支持这个属性的还有 IE9+、Safari3+、Opera10+、Chrome。
为了确保跨浏览器兼容,有必要编写如下兼容代码:
function getInnerText(element) {
return (typeof element.textContent == 'String') ? element.textContent : element.innerText
}
function setInnerText(element, text) {
if (element.textContent) {
element.textContent = text
} else {
element.innerText = text
}
}
4.4.2、outerText属性
读取文本值时,outerText 与 innerText 的结果完全一致。
写模式下,outerText不只替换调用它的元素的子节点,而是会替换整个元素(包括子节点)。
div.outerText = 'Hello World'
相当于下面的代码
var text = document.createTextNode('Hello World')
div.parentNode.replaceChild(text, div)
新的文本节点会完全调用 outerText 的元素。改元素就从文档中华被删除、无法访问。
兼容:IE4+、Safari3+、Opera8+、Chrome。由于这个属性会导致调用它的元素不存在,因此并不常用。
4.5、滚动
HTML5在将 scrollIntoView() 收纳入规范之后,任何还有其他几个专有反复可以在不同浏览器种使用,这些方法都是对 HTMLElement的扩展。
- scrollIntoViewIfNeeded(alignCenter)
- scrollBy(x-coord, y-coordoptions)、scrollBy(options)
由于 scrollIntoView() 是唯一一个所有浏览器都支持的方法,因此还是这个方法最常用,上面的方法仅供了解。
小结:
本文接收的三个方面的规范如下:
- Selectors API,定义了两个方法,让开发人员能够基于 CSS 选择器 从 DOM中取得元素,这两个方法分别是 querySelector() 和 querySelectorAll()
- Element Traversal, 为 DOM 元素定义了额外的属性,让开发人员能够更方便地从一个元素跳到另一个元素。之所以会出现这个扩展,是因为浏览器处理DOM元素间空白符的方式不一样。
- HTML5,为标准的DOM定义了很多扩展功能。其中包括在innerHTML属性这样的事实标准基础上提供的标准定义,以及管理焦点、设置字符集、滚动页面而规定的扩展API。
网友评论