红宝书系列读书笔记(四)

DOM操作技术

1.动态脚本
创建动态脚本的方式:插入外部文件和直接插入javascript代码
在一般浏览器中,可以使用DOM操作生成<script>标签以及通过createTextNode()方法创建其子节点,但IE中,将<script>视为一个特殊元素,不允许DOM访问其子节点,但可以使用<script>标签的text属性来指定javascript代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);
//动态添加脚本的通用函数
function loadScriptString(code) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    try {
        script.appendChild(document.createTextNode(code));
    }catch(ex) {
        script.text =code;
    }
    document.body.appendChild(script);
}

2.动态样式
创建动态样式的方式:通过<link>标签引入外部css文件和通过<style>标签来嵌入CSS代码
动态创建<link>标签时,必须要注意的是,要将<link>标签添加到<head>中而不是<body>
IE中,也将<style>视为一个特殊元素,不允许DOM访问其子节点。
解决这个问题的方法,就是访问元素的styleSheet属性,该属性又有一个cssText属性,可以接受css代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var style = document.createElement("style");
style.type = "text/css";
try{
    style.appendChild(document.createTextNode("body{background-color: red}"));
}catch(ex){
    style.styleSheet.cssText = "body{background-color: red}";
}
var head =document.getElementsByTagName("head")[0];
head.appendChild(style);
//动态添加样式的通用函数
function loadStyleString(css){
    var style = document.createElement("style");
    style.type = "text/css";
    try{
        style.appendChild(document.createTextNode(css));
    }catch(ex){
       style.styleSheet.cssText = css;
    }   
    var head =document.getElementsByTagName("head")[0];
    head.appendChild(style);
}

3.操作表格
<table>元素是HTML中最复杂的结构之一。为了方便构建表格,HTML DOM还为<table>、
<tbody>和<tr>元素添加了一些属性和方法。
详情:http://www.w3school.com.cn/jsref/dom_obj_table.asp

4.使用NodeList
应该尽量减少访问NodeList的次数

DOM扩展

对DOM的两个主要扩展是Selectors API(选择符API)和HTML5。此外,还有一个不那么引人注目的Element Traversal(元素遍历)规范和一些DOM的专有扩展。

众多Javascript库中最常用的一项功能,就是根据CSS选择符选择与某个模式匹配的DOM元素。
jQuery的核心就是通过CSS选择符查询DOM文档取得元素的引用,从而抛开了getElementById()和getElementsByTagName()。

选择符API
Selectors API Level 1的核心是两个方法:querySelector()和querySelectorAll()。(IE8以下浏览器不支持)

querySelector()接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。

1
2
3
4
var body = document.querySelector("body");
var myDiv = document.querySelector("#myDiv");
var selected = document.querySelector(".selected");
var img = document.body.querySelector("img.button");

querySelectorAll()方法接收的参数与querySelector()一样,都是一个CSS选择符,但返回的是所有匹配的元素而不仅仅一个元素,返回的是一个NodeList对象,如果没有找到匹配元素,NodeList就是空。

Selectors API Level 2规范为Element类型新增了一个方法matchesSelector()。
这个方法接收一个参数,即CSS选择符,如果调用元素与该选择符匹配,则返回true,否则,返回false.

1
2
3
if(document.body.matchesSelector("body.page1")){    //true
    //do something
}

该方法还未达成统一标准:IE9+通过msMatchesSelector()支持,Firefox 3.6+通过mozMatchesSelector()支持,
Safari 5+和Chrome通过webkitMatchesSelector()支持。
编写一个兼容的包装函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function matchesSelector(element,selector){
     if (element.matchesSelector){
          return element.matchesSelector(selector);
     }else if(element.msMatchesSelector){
          return element.msMatchesSelector(selector)
     }else if(element.mozMatchesSelector){
          return element.mozMatchesSelector(selector);
     }else if(element.webkitMatchesSelector){
          return element.webkitMatchesSelector(selector);
     }else{
          throw new Error("Not supported.");
     }
}

元素遍历
Element Traversal API为DOM元素添加了以下5个属性:
childElementCount:返回子元素(不包括文本节点和注释)的个数;
firstElementChild:指向第一个子元素;firstChild的元素版。
lastElementChild:指向最后一个子元素;lastChild的元素版。
previousElementSibling:指向前一个同辈元素;previousSibling的元素版。
nextElementSibling:指向后一个同辈元素;nextSibling的元素版。

利用这些元素就可以不必担心空白文本节点,从而更方便地查找DOM元素。

HTML5

与类相关的扩充
1.getElementsByClassName()方法
接收一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多个类名时,类名的先后顺序不重要。
2.classList属性(仅Firefox3.6+和Chrome支持)
classList属性是新集合类型DOMTokenList的实例。
新类型有如下方法:
add(value):将给定的字符串值添加到列表中。如果值已存在,就不添加了。
contains(value):表示列表中是否存在给定的值,如果存在返回true,反之,false。
remove(value):从列表中删除给定的字符串。
toggle(value):如果列表中已经存在给定值,删除它,如果列表中没有给定值,则添加它。

1
2
3
<div class="bd user disabled"></div>
//移除“user”类
div.classList.remove("user");

焦点管理
HTML5添加了辅助管理DOM焦点功能。
document.activeElement属性,这个属性始终会引用DOM中当前获得焦点的元素。

元素获得焦点的方式有页面加载、用户输入和代码中调用focus()方法。
默认情况下,页面刚刚加载完成时,document.activeElement中保存的是document.body元素的引用。文档加载期间,document.activeElement的值为null

document.hasFocus()方法,用于确定文档是否获得了焦点。
通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。
查询文档获知哪个元素获得了焦点,以及确定文档是否获得了焦点,这两个功能最重要的用途是提高web应用的无障碍性。

HTMLDocument的变化
1.readyState属性(document.ready
该属性有两个值:loading(正在加载文档)和complete(已经加载完文档)
2.兼容模式(document.compatMode
自从IE6开始区分渲染页面的模式是标准的还是混杂的,检测页面的兼容模式就成为了浏览器的必要功能。
document.compatMode,这个属性就是为了告诉开发人员浏览器采用了哪种渲染模式。
在标准模式下:document.compatMode == “CSS1Compat”
在混杂模式下:document.compatMode == “BackCompat”
3.head属性(仅Chrome和Safari5+支持)
HTML5新增了document.head属性,引用文档的<head>元素

1
var head =document.head||document.getElementsByTagName("head")[0];

字符集属性
document.charset,可以通过这个属性获取或修改charset属性的值。

自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。
可以通过dataset属性来访问自定义属性的值。
dataset属性的值是DOMStringMap的一个实例,也就是一个名值对儿的映射。

1
2
3
4
5
6
7
8
9
<div id="myDiv" data-appId="12345" data-myname="Peter"></div>
var div = document.getElementById("myDiv");
//取得自定义属性的值
var appId = div.dataset.appId;
var myname=div.dataset.myname;
//设置值
div.dataset.appId=23456;
div.dataset.myname="Bob";

插入标记
1.innerHTML属性
在读模式下,innerHTML属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。
在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点
2.outerHTML属性
在读模式下,outerHTML属性返回调用它的元素及所有子节点的HTML标记。
在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
3.insertAdjacentHTML()方法
这个方法最早出现在IE,它接收两个参数:插入位置和要插入的HTML文本。第一个参数必须是下列的值:
beforebegin、afterbegin、beforeend、afterend
4.内存和性能问题
替换节点可能会导致浏览器的内存占用问题。

scrollIntoView()方法(Chrome不支持)
该方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。

专有扩展(浏览器各自专有的扩展,并未写进标准)
1.文档模式,IE8引入的,主要有四种:
IE5:混杂模式;IE7:以IE7标准模式渲染页面;IE8:以IE8标准模式渲染页面;IE9(最高级):以IE9标准模式渲染页面;
2.children属性
这个属性是HTMLCollection的实例,只包含元素中同样还是元素的子节点。
3.contains()方法
判断某个节点是不是另一个节点的后代。
4.插入文本
innerText和outerText
5.滚动
scrollIntoViewIfNeeded(alignCenter)、scrollByLines(lineCount)、scrollByPages(pageCount)

DOM2和DOM3
DOM1级主要定义的是HTML和XML文档的底层结构。DOM2和DOM3级则在这个结构的基础上引入了更多的交互能力,也支持了更高级的XML特性。
DOM2级的主要模块:

DOM2级核心:在1级核心基础上构建,为节点添加了更多方法和属性。
DOM2级视图:为文档定义了基于样式信息的不同视图。
DOM2级事件:说明了如何使用事件与DOM文档交互。
DOM2级样式:定义了如何以编程方式来访问和改变CSS样式信息。
DOM2级遍历和范围:引入了遍历DOM文档和选择其特定部分的新接口。
DOM2级HTML:在1级HTML基础上构建,添加了更多属性、方法和新接口。

DOM变化
DOM2级和3级的目的在于扩展DOM API,以满足操作XML的所有需求,同时提供更好的错误处理和特性检测能力。
DOM3级引入了两个辅助比较节点的方法:isSameNode()和isEqualNode(),这两个方法都接收一个节点参数。
DOM3级还针对DOM节点添加额外数据引入了新方法:setUserData()和getUserData()。
setUserData()接收三个参数

1
2
3
4
5
6
7
8
9
10
11
document.body.setUserData("name","Peter",function(){});
var div = document.createElement("div");
div.setUserData("name","Peter",function(operation,key,value,src,dest) {
    if(operation == 1) {
        dest.setUserData(key,value,function(){});
    }
});
var newDIiv = div.cloneNode(true);
alert(newDiv.getUserData("name"));    //Peter

框架的变化
框架和内嵌框架分别用HTMLFrameElement和HTMLIFrameElement表示,它们在DOM2级有个新属性,contentDocument。这个属性包含一个指针,指向表示框架内容的文档对象。

1
2
var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument;(IE8之前版本不支持)

IE8之前版本支持contentWindow的属性,该属性返回框架的window对象,而这个window对象又有一个document属性。
因此,要想在所有浏览器中访问内嵌框架的文档对象,可以使用以下代码:

1
2
var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

样式

在HTML中定义样式有三种方法:通过<link/>元素包含外部样式表文件、使用<style/>元素定义嵌入式样式,
以及使用style特性针对特定元素的样式。
检测浏览器是否支持DOM2级定义的CSS能力:

1
2
var supportDOM2CSS = document.implementation.hasFeature("CSS","2.0");
var supportDOM2CSS = document.implementation.hasFeature("CSS2","2.0");

访问元素的样式
任何支持style特性的HTML元素在javascript中都有一个对应的style属性。
通过style属性设置或获取元素的样式。

1.DOM样式属性和方法
”DOM2级样式“规范为style对象定义了如下的属性和方法:
cssText:通过它能够访问到style特性中的CSS代码
length:应用给元素的CSS属性的数量
parentRule:表示CSSRule对象。
getPropertyCSSValue(propertyName):返回给定属性值的CSSValue对象,该对象包含两个属性:cssText和cssValueType。cssValueType属性是一个数值常量,表示值的类型:0表示继承的值,1表示基本的值,2表示值列表,3表示自定义的值。

getPropertyPriority(propertyName):如果给定的属性使用了!important设置,则返回”important”;否则,返回空字符串。
getPropertyValue(propertyName):返回给定属性的字符串值。
item(index):返回给定位置的CSS属性的名称。
removeProperty(propertyName):从样式中删除给定属性。
setProperty(propertyName,value,priority):将给定属性设置为相应的值,并加上优先权标志(“important”或者空字符串)。

2.操作样式表
CSSStyleSheet类型表示的是样式表,包括通过<link>元素包含的样式表和<style>中定义的样式表。
CSSStyle继承自StyleSheet,有如下的属性:
disabled:表示样式是否被禁用的布尔值。·,,
href:如果样式表通过<link>包含的,则是样式表的URL;否则,是null。
meida:当前样式表所支持的所有媒体类型的集合。
ownerNode:指向拥有当前样式表的节点的指针,样式可能是在HTML中通过<link>或<style>引入的。如果当前样式表是其他样式表通过@import导入的,这个属性值为null。IE不支持这个属性。
parentStyleSheet:在当前样式表是通过@import导入的情况下,这个属性是一个指向导入它的样式表的指针。
title:ownerNode中的title属性的值。
type:表示样式表类型的字符串。通常是“type/css”
cssRules:样式表中包含的样式规则的集合。IE不支持
ownerRule:如果样式表是通过@import导入的,这个属性就是一个指针,指向表示导入的规则,否则,值为null。IE不支持
deleteRule(index):删除cssRules集合中指定位置的规则。IE不支持
inserRule(rule,index):向cssRules集合中指定的位置插入rule字符串。IE不支持

3.元素大小
1)偏移量
offsetHeight:元素在垂直方向上占用的空间大小。
offsetWidth:元素在水平方向上占用的空间大小。
offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。

2)客户区大小
clientWidth:元素内容区宽度加上左右内边距宽度;
clientHeight:元素内容区高度加上上下内边距宽度;

3)滚动大小
scrollHeight:在没有滚动条的情况下,元素内容的总高度。
scrollWidth:在没有滚动条的情况下,元素内容的总宽度。
scrollLeft:被隐藏在内容区域左侧的像素数。通过设置该属性可以改变元素的滚动位置。
scrollTop:被隐藏在内容区域上方的像素数。通过设置该属性可以改变元素的滚动位置。

4.确定元素大小
getBoundingClientRect()方法,返回矩形对象,包含4个属性:left、top、right和bottom。
(跨浏览器的getBoundingClientRect()方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function getBoundingClientRect(element) {
var scrollTop = document.documentElement.scrollTop;
var scrollLeft = document.documentElement.scrollLeft;
       
if (element.getBoundingClientRect) {
if (typeof arguments.callee.offset != "number") {
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
   
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
   
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
} else {
var actualLeft = getElementLeft(element);
var actualTop = getElementTop(element);
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
bottom: actualTop + element.offsetHeight - scrollTop
}
}
}

遍历

“DOM2级遍历和范围”模块定义了两个用于辅助完成顺序遍历DOM结构的类型:NodeIterator和TreeWalker。
这两个类型能够基于给定的起点对DOM结构执行深度优先的遍历操作。

NodeIterator
使用document.createNodeIterator()方法创建实例,这个方法接收4个参数:
root:想要作为搜索起点的树中的节点。
whatToShow:表示要访问哪些节点的数字代码。
filter:是一个NodeFilter对象,或者表示应该接受还是拒绝某种特定节点的函数。
entityReferenceExpansion:布尔值,表示是否要扩展实体引用。这个参数在HTML页面没有用,因为其中的实体引用不能扩展。

NodeIterator的两个主要方法:nextNode()和previousNode(),两者遍历到DOM子树最后一个节点时,都返回null。

1
2
3
4
5
6
7
8
<div id="div1">
    <p><b>Hello</b>world!<p>
    <ul>
        <li>List item 1</li>
        <li>List item 2</li>
        <li>List item 3</li>
    </ul>
</div>

遍历<div>中的所有元素:

1
2
3
4
5
6
7
var div = document.getElementById("div1");
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
var node = iterator.nextNode();
while(node!=null){
    alert(node.tagName);
    node = iterator.nextNode;
}

如果只想返回遍历中遇到的<li>,那么只要使用一个过滤器即可:
var div = document.getElementById(“div1”);
var filter = function(node){
return node.tagName.toLowerCase()==”li”?
NodeFilter.FILTER_ACCEPT:
NodeFilter.FILTER_SKIP;
};

var iterator = document.createNodeIterator(div,NodeFilter.SHOW_ELEMENT,filter,false);

var node = iterator.nextNode();
while(node!=null){
alert(node.tagName);
node = iterator.nextNode;
}

TreeWalker
TreeWalker是NodeIterator的一个更高级版本。使用document.createTreeWalker()方法创建,接收4个与document.createNodeIterator()方法相同的参数。
除了包括nextNode()和previousNode()在内的相同的功能之外,这个类型还提供了下列用于在不同方向上遍历DOM结构的方法:
parentNode():遍历到当前节点的父节点。
firstChild():遍历到当前节点的第一个子节点。
lastChild():遍历到当前节点的最后一个子节点。
nextSibling():遍历到当前节点的下一个兄弟节点。
previousSibling():遍历到当前节点的上一个兄弟节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var div =document.getElementById("div1");
var filter  = function(node){
    return node.tagName.toLowerCase()=="li"?
            NodeFilter.FILTER_ACCEPT:
            NodeFilter.FILTER_SKIP;        
};
var walker = document.createTreeWalker(div,NodeFilter.SHOW_ELEMENT,filter,false);
var node = walker .nextNode();
while(node!=null){
    alert(node.tagName);
    node =  walker .nextNode;
}

TreeWalker还有一个属性,名叫currentNode,表示任何遍历方法在上一次遍历中返回的节点。
通过设置这个属性也可以修改遍历继续进行的起点:

1
2
3
var node = walker.nextNode();
alert(node === walker.currentNode);    //true
walker.currentNode = document.body;    //修改起点

范围

DOM2级在Document类型中定义了createRange()方法,让开发人员更方便地控制页面。
每个范围由一个Range类型的实例表示,该实例拥有以下的属性:
startContainer:包含范围起点的节点。
startOffset:范围在startContainer中起点的偏移量。
endContainer:包含范围终点的节点。
endOffset:范围在endContainer中终点的偏移量。
commonAncestorContainer:startContainer和endContainer共同的祖先节点在文档树中位置最深的那个。
1.用DOM范围实现简单选择
使用selectNode()和selectNodeContents()。这两个方法都接收衣蛾参数,即一个DOM节点,然后使用该节点中的信息来填充范围。
selectNode()方法选择整个节点,包括其子节点;而selectNodeContents()方法则只选择节点的子节点。
2.用DOM范围实现复杂选择
使用setStart()和setEnd()方法。这两个方法都接受两个参数:一个参照节点和一个偏移量值。

1
2
3
4
5
6
7
8
9
10
<p id="p1"><b>Hello</b>world!</p>
var p1=document.getElementById("p1");
var helloNode=p1.firstChild.firstChild,
      worldNode=p1.lastChild;
var range = document.createRange();
range.setStart(helloNode,2);
range.setEnd(worldNode,3);
红色部分则为已选择的范围:
<p id="p1"><b>Hello</b>world!</p>

3.操作DOM范围中的内容
deleteContents():从文档中删除范围所包含的内容。

1
2
3
4
5
6
7
8
9
10
var p1 = document.getElementById("p1");
var helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild;
var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
range.deleteContents();
// 结果为:
<p id="p1"><b>He</b>rld!</p>

由于范围选区在修改底层DOM结构时能够保证格式良好,因此即使内容被删除了,最终的DOM结构依旧是格式良好的。
与deleteContents()相似,extractContents()也会从文档中移除范围选区。区别在于extractContents()会返回范围的文档片段。

cloneContents()创建范围对象的一个副本,然后再文档的其他地方插入该副本。
4.插入DOM范围中的内容
insertNode()向范围选区的开始处插入一个节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<span style="color : red">Inserted Text</span>
var p1=document.getElementById("p1");
var helloNode=p1.firstChild.firstChild,
      worldNode=p1.lastChild;
var range = document.createRange();
range.setStart(helloNode,2);
range.setEnd(worldNode,3);
var span = document.createElement("span");
span.style.color="red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
// 结果为:
<p id="p1"><b>He<span style="color : red">Inserted Text</span>llo</b>world!</p>

除了向范围内部插入内容之外,还可以环绕范围插入内容,使用surroundContents()方法。这个方法接受一个参数,即环绕范围内容的节点。
在环绕范围插入内容时,后台执行下列步骤:
1)提取出范围中的内容
2)将给定节点插入到文档中原来范围所在的位置上
3)将文档片段的内容添加到给定节点中。

1
2
3
4
5
6
7
8
9
10
11
12
var p1=document.getElementById("p1");
var helloNode=p1.firstChild.firstChild,
      worldNode=p1.lastChild;
var range = document.createRange();
range.selectNode(helloNode);
var span = document.createElement("span");
span.style.backgroundColor="yellow";
range.surroundContents(span);
// 结果为:
<p id="p1"><b><span style="background-color : yellow">Hello</span></b>world!</p>

5.折叠DOM范围
所谓折叠范围,就是指范围中未选择文档的任何部分。
使用collapse()方法来折叠范围,这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪一端。true表示折叠到范围的起点,false表示折叠到范围的终点。
也可以通过检测某个范围是否处于折叠状态,来确定范围中的两个节点是否紧密相邻。
通过range.collapse判断
6.比较DOM范围
使用compareBoundaryPoints()方法确定多个范围是否有公共的边界(起点或终点)。
该方法接受两个参数:表示比较方式的常量值和要比较的范围。
返回值如下:如果第一个范围中的点位于第二个范围中的点之前,返回-1;如果两个点相等,返回0;如果第一个范围中的点位于第二个范围中的点之后,返回1。
7.复制DOM范围
cloneRange()方法

1
var newrange = range.cloneRange();

8.清理DOM范围
detach()方法

1
2
range.detach();
range=null;

坚持原创技术分享,您的支持将鼓励我继续创作!