js高程读书笔记 第十章 DOM

[TOC]

本章内容

  • 理解包含不同层次节点的DOM
  • 使用不同的节点类型
  • 克服浏览器兼容性问题及各种陷阱

节点层次

节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点的树形结构。

文档节点是每个文档的根节点。

Node类型

节点类型由Node类型中定义数值常量表示,一般用的较多的是

  • Node.ELEMENT_NODE(1);
  • Node.TEXT_NODE(3);

对于元素节点,nodeName中保存的始终都是元素的标签名,而nodeValue的值则始终为null。

每个节点都有一个childNodes属性,保存着一个NodeList对象。NodeList是一种类数组对象,用于保存一组有序的节点,但它不是Array实例,是基于DOM结构动态执行查询的结果,因此DOM的结构变化能够自动反应在NodeList对象中。

以下代码将NodeList转化为数组

1
2
3
4
5
6
7
8
9
10
11
function convertToArray(nodes){
var array = null;
try{
array = Array.prototype.slice.call(nodes, 0);//对非IE浏览器有效
} catch (ex) {
array = new Array();
for(var i = 0, len = nodes.length;i < len; i++){
array.push(nodes[i]);
}
}
}

每一个节点都有一个parentNode属性,该属性指向文档树中的父节点。

previousSibling和nextSibling属性可以访问同胞节点

firstChild和lastChild的值始终等于someNode.childNodes[0]和someNode.childNodes[someNode.childNodes.length - 1]

hasChildNodes()方法在节点包含一或多个子节点的情况下返回true;

所有节点都有最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。

appendChild()方法用于想childNodes列表的末尾添加一个节点。如果传入到appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置。

如果要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,那么可以使用insetBefore()方法。这个方法接收两个参数:要插入的节点和作为参照的节点。如果参照的节点是null,那么就会有和appendChild()一样的操作

1
2
3
4
5
6
7
8
9
10
11
//插入后成为最后一个子节点
returnedNode = someNode.insertBefore(newNode, null);
console.log(newNode == someNode.lastChild);//true

//插入后成为第一个子节点
returnedNode = someNode.insertBefore(newNode,someNode.firstChild);
console.log(newNode == someNode.firstChild);//true

//插入最后一个子节点前面
returnedNode = someNode.insetBefore(newNode, someNode.lastChild);
console.log(newNode == someNode.childNodes[someNode.length - 2]);

replaceChild()方法接收两个参数:要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。

1
2
3
4
5
//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);

//替换最后一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.lastChild);

removeNode()方法接收一个参数,即要移除的节点

1
2
3
4
5
//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);

//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);

cloneNode()接收一个布尔值参数表示是否执行深复制。参数为true的情况下执行深复制,也就是复制节点及其整个子节点数;

Document类型 9

Document对象是window对象的一个属性。

以下三种方式可以获得<html>元素

1
2
3
4
5
6
7
var html = document.documentElement;//取得对<html>的引用
console.log(html === document.childNodes[1]);//true
console.log(html === document.firstElementChild);//true

var body = document.body;//取得对body的引用

var doctype = document.doctype;//取得对doctype的引用

文档信息

通过title这个属性可以取得当前页面的标题也可以修改当前页面的标题。修改title属性的值不会改变<title>元素

1
2
3
4
5
//取得文档标题
var originalTitle = document.title;

//设置文档标题
document.title = "New page title";

URL、domain和referrer

1
2
3
4
5
6
7
8
//获取完整的URL
var url = document.URL;

//取域名
var domain = document.domain;

//取得来源页面的URL
var referrer = document.referrer;

这三个属性中,只有domain是可以设置的,但是不能将这个属性设置为URL中不包含的域。

如果域名一开始是松散的(loose),那么不能将它再设置为紧绷的(tight)。

1
2
3
//假设页面来自于p2p.wrox.com域
document.domain = "wrox.com";//成功
document.domain = "pap.wrox.com"//出错

查找元素

gerElementById();接收一个参数:要取得的元素的ID,返回文档中第一次出现的元素

getElementsByTagName();接收一个参数:要取得元素的标签名,返回包含零或多个元素的NodeList。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个“动态”集合。
HTMLCollection对象还有一个方法,叫做namedItem(),这个方法可以通过元素的name特性取得集合中的项。

1
<img src="myimage.gif" name="myImage">

这样就可以用下如下方式取得这个<img>元素

1
2
var images = document.getElementsByTagName("img");
var myImage = images.namedItem("myImage");

要取得文档中的所有元素

1
var allElements = document.getElementsByTagName("*");

getElementsByName()方法,如果对单选按钮radio使用,namedItem()方法只会取得第一项。

特殊方法

  • document.anchors 包含文档中所有带name特性的<a>元素
  • document.forms 包含文档中所有<form>元素,与document.getElementsByTagName("form")效果相同
  • document.images 包含文档中所有<img>元素,与document.getElementsByTagName("img")效果相同
  • document.links 包含文档中所有带href特性的<a>元素

dom一致性检测

1
var hasXmlDom = document.implementation.hasFeature("XML", "1.0");

文档写入

document.write()会原样写入。document.writeln()会在字符串的末尾添加一个换行符(\n)

Element类型 1

要访问元素的标签名,可以使用nodeName属性也可以使用tagName属性。后者更清晰。

1
<div id="myDiv"></div>

1
2
3
var div = document.getElementById("myDiv");
console.log(div.tagName);
console.log(div.tagName == div.nodeName);

在HTML中,标签名始终都以全部大写显示;在XML中会与源代码中的保持一致。

1
element.tagName.toLowerCase()

通过这样转化一下比较好

HTML元素

取得特性

  • getAttribute()
  • setAttribute()
  • removeAttribute()

根据THML5规范,自定义特性应该加上data-前缀以便验证。

有两类特殊的特性,虽然有对应的属性名,但属性的值与通过getAttribute()返回的值并不相同。

style通过getAttribute()返回的是css文本,而通过属性来访问则会返回一个对象。

onlick事件处理程序用getAttribute()访问,会返回相应代码的字符串,用onlick属性访问时,会返回一个JavaScript函数。

attributes属性

包含一个NamedNodeMap,与NodeList类似是一个动态的集合。

attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。要取得元素的id特性

1
2
3
var id = element.attributes.getNamedItem("id").nodeValue;
//也可以使用
var id = element.attributes["id"].nodeValue;

removeAttribute()与attributes属性中的removeNamedItem()方法唯一的区别就在于后者会返回被删除的Attr节点

创建元素

可以在createElement()中指定完整的HTML标签来解决IE7及更早版本的问题

1
2
3
4
5
6
7
8
9
10
11
if(client.browser.ie && client.rowser.ie <=7){
//创建一个带name特性的iframe元素
var iframe = document.createElement("<iframe name=\"myframe\"><iframe>");
//创建input元素
var input = document.createElement("<input type=\"checkbox\">");
//创建button元素
var button = document.createElement("<button type=\"reset\"></button>");
//创建单选按钮
var radio1 = document = createElement("<input type=\"radio\" name=\"choice\" value=\"1\"");
var radio2 = document = createElement("<input type=\"radio\" name=\"choice\" value=\"2\"");
}

元素的子节点

元素可以有任意数目的子节点和后代节点,因为元素可以是其他元素的子节点。在IE中,对下列代买解析:

1
2
3
4
5
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>

会获得3个<li>元素和4个文本节点(空白符)

意味着在执行某项操作之前,通常都要先检查一下nodeType属性。

1
2
3
4
5
for(var i = 0, len = element.childNodes.length; i < len ;i++){
if(element.childNodes[i].nodeType == 1){
//执行某些操作
}
}

text类型 3

修改文本节点的内容时,内容会被html编码

创建文本节点

document.createTextNode()创建新文本节点。

规范化文本节点

normalize()方法可以将所有文本节点合并成一个节点

分割文本节点

splitText()方法可以将文本分成两部分,原来的文本节点将会包含从开始到指定位置之前的内容,新文本节点将包含剩下的文本。

1
2
3
4
5
6
7
8
9
10
11
12
var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);

document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);
console.log(element.firstChild.nodeValue);//Hello
console.log(newNode.nodeValue);// world!
console.log(element.childNodes.length);//2

comment类型 8

CDATASection 4

DocumentType 10

document.doctype中 DOM1级描述了三个属性:name、entities和notations;分别代表文档类型的名称;entities是由文档类型描述的实体NamedNodeMap对象;notations是由文档类型描述的符号的NamedNodeMap对象。

DocumentFragment类型 11

可以包含和控制节点,把它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点。

1
<ul id="myList"></ul>

假设为这个<ul>元素添加3个列表项。如果逐个地添加列表项,将会导致浏览器反复渲染新信息。

1
2
3
4
5
6
7
8
9
10
11
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;

for (var i = 0; i < 3 ; i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li);
}

ul.appendChild(fragment);

attr类型 2

attr对象有3个属性:name、value和specified

其中specified用于区别特定是代码中指定的还是默认的。

1
2
3
4
5
6
var attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr);
console.log(element.attributes["align"].value));
console.log(element.getAttributeNode("align").value);
console.log(element.getAttribute("align"));

DOM操作技术

动态脚本

1
2
3
4
5
6
7
8
9
var script = document.createElement("script");
script.type = "text/javascript";
var code = "function sayHi(){alert('hi');}";
try{
script.appendChild(document.createTextNode("code"));
} catch (ex) {
script.text = "code";//在IE中,script元素是一个特殊对象,不允许DOM访问子节点,不过可以使用text的属性来指定
}
document.body.appendChild(script);

动态样式

1
2
3
4
5
6
7
8
function loadStyles(url){
var link = document.createElement("link");
link.href = url;
link.rel = "stylesheet";
link.type = "text/css";
var head = document.getElementByTagName("head")[0];
head.appendChild(link);
}

操作表格 p283

使用NodeList

NodeList和NamedNodeMap和HTMLCollection都是动态的,每当文档结构发生变化时,它们都会得到更新。

1
2
3
4
5
6
7
var divs = document.getElementsByTagName("div";),
i,
div;
for(i = 0 ; i < divs.length; i++){
div = document.createElement("div");
document.body.appendChild(div);
}

以上代码会导致无限循环,循环体每次都会增加一个div元素到文档中,而文档的长度每次循环后都会增加,结果i永远不能达到divs.length

所以如果要想迭代一个NodeList,最好是使用length属性初始化第二个变量,然后将迭代器与该变量进行比较。

1
2
3
4
5
6
7
var divs = document.getElementsByTagName("div";),
i,
div;
for(i = 0 , len = divs.length; i < len; i++){
div = document.createElement("div");
document.body.appendChild(div);
}

小结

DOM由各种节点构成

  • 最基本的节点类型是Node,用于抽象地表示文档中一个独立的部分;所有其他类型都继承自Node
  • Document类型表示整个文档。在JavaScript中,document是Document的一个实例。
  • Element节点表示文档中所有HTML或XML元素,可以用来操作这些元素的内容和特性
  • 还有一些节点类型,分别表示文本内容,注释、文档类型、CDATA区域和文档片段

理解DOM的关键就是理解DOM对性能的影响。DOM操作往往是JavaScript程序中开销最大的部分,因而访问NodeList导致的问题最多。最好的办法是减少DOM操作。