本文整理自《高级javascript程序设计》
DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)
DOM描述了一个层次化的节点树,允许开发人员添加、移除、和修改页面的某一部分。
1998年10月DOM1级规范成为W3C的推荐标准,为基本的文档结构及查询提供了接口
文档节点是每个文档的根节点。
文档元素是文档最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素
在html页面中,文档元素始终都是<html>元素
hasChildNodes()方法:这个方法在节点包含一个或多个子节点的情况下返回true
所有节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。通过这个属性,可以不必再节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点。
操作节点
-- someNode.appendChild(),向childNodes列表的末尾添加一个节点
-- someNode.insertBefore(newNode,参照节点),向特定位置插入一个节点。如果参照节点是null,作用和appendChild()相同
-- someNode.replaceNode(要插入的节点,要替换的节点),作用是替换节点
-- someNode.removeNode(要移除的节点),删除节点
-- someNode.cloneNode() 用于创建调用这个方法的节点的一个完全相同的副本。
--cloneNode()接受一个布尔值参数,表示是否执行深复制。true执行深复制,false执行签复制
深复制即复制节点及整个子节点树,浅复制即复制节点本身。克隆后的节点是个孤儿,要配合appendchild,insertBefore,replaceNode这样的方法插入节点树
--normalize(),这个方法唯一的作用就是处理文档树中的文本节点。后面会讨论
Document类型
Document类型可以表示HTML页面或者其他基于XML的文档。最常见的应用还是作为HTMLDocument实例的document对象。通过这个文档对象,可以取得与页面有关的信息,还能操作页面的外观及其底层结构。
所有浏览器都支持document.documentElement和document.body属性
document.documentElement指向<html>元素
document.body指向body元素
Document的另一个子节点DocumentType
var doctype = document.doctype; //取得对<!DOCTYPE>的引用
由于浏览器对document.doctype的支持不一致,因此这个属性的用处很有限
文档信息
document.title //取得文档标题,也可设置文档标题
document.URL //取得URL
document.domain //取得域名。。可设置
将页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的javascript对象。
查找元素
document.getElementById()
document.getElementsByTagName() //这个方法返回一个HTMLCollection对象,该对象类似NodeList,可以用[0],item(0)这样来访问对象中的项,也可以用length取得对象中元素的个数
HTMLCollection对象还有一个方法,叫做namedItem()用于通过元素的name属性取得集合中的项
<img src='...' name='myimage' />
var imgs = images.namedItem('myimage');
var imgs_ = images['myimages'];
document.getElementByName('');
IE8及更低版本不区分ID的大小写
IE7及更低版本中,name属性与给定ID匹配的表单元素也会被该方法返回
为了避免IE中存在的这个问题,最好的办法是不让表单字段的name特性与其他元素的ID相同
document.referrer //取得来源页面的URL
特殊集合
document.anchors 包含文档中所有带name属性的a元素
document.forms
document.images
document.links
DOM一致性检测
由于DOM分为多个级别,也包含多个部分,因此检测浏览器实现了DOM的哪些部分就十分必要
document.implementation属性为此提供相应信息和功能的对象,与浏览器与DOM的实现直接对应。
DOM1级只为document.implemetation提供了一个方法:hasFeature()
这个方法接受两个参数,要检测的DOM功能的名称和版本号,如果浏览器支持则返回true
var hasXmlDom = document.implementation.hasFeature('XML','1.0');
建议在使用DOM的某些特殊的功能之前,最好除了检测hasFeature()之外,还同时使用能力检测
文档写入
将输入流写入到网页中
write() 原样写入
writeln() 会在字符串的末尾添加一个换行符(\n)
open() 打开输入流,打开新页面
close() 关闭输入流
严格型XHTML文档不支持文档写入
Element类型
在HTML中,标签名始终都是以大写表示,在XML,XHTML中,标签名则始终会与源代码中的保持一致。
如果不确定js用在哪里,那么取得标签名后转换下toLowerCase
HTML元素
所有HTML元素都由HTMLElement类型表示,不是通过这个类型,也是通过它的子类型。
HTMLElement类型直接继承自Element并添加了一些属性
id
title
lang
dir 语言的方向,很少使用
className
<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'
取得属性
div.getAttribute('id');
div.getAttribute('class'); //注意区别于上面的className
设置特性
div.setAttribute('id','aaaa');
从元素中完全删除特性
div.removeAttribute('id');
创建元素
document.createElemennt()
var div = document.createElement('div');
div.id = 'myDiv';
div.className = 'divName';
var div_ = document.createElement('<div id=\"myDiv_\" class=\"divName2\"></div>');
Text类型
创建文本节点
document.createTextNode()
var element = document.createElement('div');
var textNode = document.createTextNode('this is a textNode');
element.appendChild(textNode);
document.body.appendChild(element);
规范化文本节点
DOM文档中出现相邻文本节点的情况不在少数,于是就催生了一个能够将相邻文本节点合并的方法。
这个方法由Node类型定义,名叫normalize()
--分割文本节点 splitText()
var element = document.createElement('div');
element.className = 'myDiv';
var textNode = document.createTextNode('Hello world!');
element.appendChild(textNode);
document.body.appengChild(element);
var newNode = element.firstChild.splitText(5);
alert(newNode.nodeValue); //world!
alert(element.childNodes.length); //2
Comment类型
注释在DOM中是通过Comment类型来表示的
nodeType是8
Comment类型和Text类型继承自相同的基类,它拥有除了splitText()之外的所有字符串操作方法
可以通过nodeValue或data属性来取得注释内容
CDATASection类型
DocumentType类型
不常用,只有FF,Safari,opera支持
DocumentFragment类型
Attr类型
元素的特性在DOM中以Attr类型来表示。
特性,attribute不被认为是文档树的一部分
Attr对象有三个属性:name,value,specified
specified是个布尔值,用以区别特性是在代码中指定的还是默认的。
document.createAttribute()并传入特性的名称可以创建新的特性节点
var attr = document.createAttribute('align');
attr.value = 'left';
element.setAttributeNode(attr);
alert(element.attributes['align].value);//'left'
alert(element.getAttribute('align').value); //'left'
不建议直接访问特性节点,使用getAttribute(),setAttribute(),removeAttribute()方法远比操作特性节点更为方便
DOM操作技术
动态脚本
动态加载脚本文件
function loadScript(url){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.body.appendChild(script);
}
动态加载行内脚本
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = "function sayHi(){alert('hi')}";
document.body.appendChild(script);
下面的这种写法,不兼容IE,因为IE奖<script>视为一个特殊的元素,不允许DOM访问其子节点
var script = document.createElement('script');
script.type = 'text/javascript';
var textNode = document.createTextNode("function sayHi(){alert('hi')}");
document.body.appendChild(script);
动态加载样式方法同动态加载脚本
DOM扩展
选择符API
querySelector()
querySelectorAll()
matchSelector()这个方法支持的浏览器少,要用的话要写一个包装函数
元素遍历
对于元素间的空格,IE9之前的版本不会返回文本节点,而其他所有浏览器都会返回文本节点,为了弥补这一差异,又保持DOM规范不变,Element Traversal规范新定义了一组属性
firstelementCount返回子元素的个数
firstElementChild:指向第一个子元素,firstChild的元素版
lastElementChild:指向最后一个子元素,lastChild的元素版
previousElementSibling:指向前一个兄弟节点,previousSibling的元素版
nextElementSibling:指向前一个兄弟节点,nextSibling的元素版
支持Element Traversal的浏览器IE9+,FF3.5+,Safari4+,Chrome,Opera10+
HTML5
新增
getElementsByClass()
classList属性
div.classList.remove('disabled');
焦点管理
HTML5添加了辅助管理DOM焦点的功能。
document.activeElement属性,这个属性始终会引用DOM中当前获得了焦点的元素。
元素获得焦点的方式:页面加载,用户输入(通常是通过按tab键),在代码中调用focus()方法
var button = document.getElementById('myButton');
button.focus();
alert(document.activeElement === button); //true
默认情况下,文档刚刚加载完时,document.activeElement中保存的是document.body元素的引用。文档加载期间,document.activeElement值为null
另外新增了document.hasFocus()方法,这个方法用于确定文档是否获得了焦点
var button = document.getElementById('myButton');
button.focus();
alert(document.hasFocus()); //true
类似于jquery的
$().on('focus',function(){
//....
})
HTMLDocument的变化
1、readyState属性(ie4+,firefox3.6+,safari,opera9+,chrome)
--loading,正在加载文档
--complete,已经加载完文档
if(document.readyState == 'complete'){
//...
}
2、兼容模式
document.compatMode属性(ie6+,firefox,safari3.1+,opera,chrome)
值等于:
--CSS1Compact,标准模式
--BackCompat,混杂模式
if(document.compatMode == 'CSS1Compat'){
alert('Standards mode');
}else{
alert('Quirks mode');
}
3、head属性
document.head //指向<head>元素,只chrome,Safari5支持
//所以
var head = document.head || document.getElementsByTagName('head')[0];
字符集属性
document.charset //默认是UTF-16
document.defaultCharset //表示根据默认浏览器及操作系统的设置(IE,Safari,chrome)
自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息或者提供语义信息
添加了自定义属性后,可以通过元素的dataset属性来访问自定义属性的值。
<div id='myDiv' data-appId='12345' data-myname='Nicholas'></div>
var div = div.getElementById('myDiv');
var appId = div.dataset.appId;
var myname = div.dataset.myname;
//设置值
div.dataset.myname = 'Michael';
插入标记
1、innerHTML属性
返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。
限制:
在大多浏览器中用innerHTML插入的script元素并不会执行其中的脚本
很多元素不支持innerHTML,<col>,<colgroup>,<frameset>,<head>,<html>,<style>,<table>,<tbody>,<thead>,<tfoot>,<tr>
阿列,以为跟table有关的都不支持呢,td竟然支持啊!!!
2、outerHTML
在读模式下,返回调用它的元素及所有子节点的HTML标签
在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
3、insertAdjacentHTML()
接受两个参数:插入位置和要插入的HTML文本
--beforebegin 在当前元素之前插入一个紧邻的同辈元素
--afterbegin 在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素
--beforeend 在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素
--afterend 在当前元素之后插入一个紧邻的同辈元素ML
这样理解,<div></div>对于这样一个元素来说。就是以div为参照的。
beforebegin就是在<div>的前面
afterbegin就是在<div>的后面
beforeend就是在</div>的前面
afterend就是在</div>的后面
//作为前一个同辈元素插入
element.insertAdjacentHT('beforebegin','<p>11111</p>');
//作为第一个子元素插入
element.insertAdjacentHT('afterbegin','<p>11111</p>');
//作为最后一个子元素插入
element.insertAdjacentHT('beforeend ','<p>11111</p>');
//作为后一个的同辈元素插入
element.insertAdjacentHT('afterend','<p>11111</p>');
4、内存与性能问题
以上替换子节点的方法可能会导致浏览器的内存占用问题,尤其在IE。
在使用以上属性及方法时,最好先手工删除要被替换的元素的所有事件处理程序和Javascript对象属性
在插入大量新HTML标记时,innerHTML属性与通过多次DOM操作先创建节点再指定他们之间的关系相比,效率高很多。
scrollIntoView()方法
这个方法接受一个参数,true或者false
true的时候,相当于#的锚点定位,和页面顶部对齐,如果下面的高度不够,就只是全部展示在页面。
当接受参数是false的时候,和页面底部对齐
<style>
.a,.b,.c,.d{height:500px;}
.d{height:1500px;}
.a{background:#f00;}
.b{background:#ddd;}
.c{background:#2e9900;}
.d{background:#fe0;}
</style>
<div class='a'></div>
<div class='b'></div>
<div class='c'></div>
<div class='d'></div>
<script>
window.onclick = function(){
document.getElementsByClassName('d')[0].scrollIntoView(true);
// document.getElementsByClassName('d')[0].scrollIntoView(false);
}
</script>
专有扩展
浏览器开发商在发现功能缺失的时候,向DOM中添加专有扩展,弥补功能不足。有些扩展最后可能会变成标准。但是仍有大量的DOM专有扩展无法标准化
文档模式
IE8引入了一个新的概念“文档模式”。文档模式决定了页面可以使用什么功能。比如,可以使用哪个级别的css,可以使用javascript中的哪些API,以及如何对待文档类型。
children属性
看下IE9之前浏览器在处理文本节点的空白符时的差异。
childNodes会把空白字符当做子节点,children不会。其他的和childNodes没什么区别。
IE8之前的children属性中会包含注释节点,IE9之后的只返回元素节点
contains()方法
祖先节点调用contains()方法,接受一个参数,即要检测的后代节点。如果被检测的节点是后代节点,该方法返回true,否则返回false。
alert(document.documentElement.contains(document.body));//true
插入文本
innerText属性和outerHTML属性还没有被HTML5纳入规范
outerText属性会导致调用它的元素不存在,建议尽可能不要用这个属性
滚动
scrollIntoViewIfNeeded(参数)
参数为true或者不传参数,元素滚动到视口垂直方向中部
参数为false,元素跟视口底部对齐,当元素不可见的时候这个方法才执行。这个方法只有safari和chrome支持。
scrollByLines(lineCount)将元素的内容滚动指定行数。这个方法只有safari和chrome支持。
element.scrollByLines(5);
scrollByPages(pageCount),将指定元素的内容滚动指定页数,这个方法只有safari和chrome支持。
elemenet.scrollByPages(2);