DOM——《JavaScript高级程序设计》笔记
DOM(文档对象模型)
@针对HTML和XML文档的一个API,为基本的文档结构及查询提供接口。
@IE中的DOM都是以COM对象的形式实现的,故IE中的DOM对象于原生JavaScript对象的行为或活动特点有差异。
10.1节点层次
@DOM能够将任何HTML或XML文档庙会成一个由多层节点构成的结构。
@每个文档只能有一个文档元素,在HTML页面中,文档元素为<html>元素。【根节点为Document,最外层元素为文档元素html】
@HTML元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点表示,注释通过注释节点表示。
10.1.1 Node类型
@DOM1级定义了Node接口,由DOM中的所有节点类型实现。除了IE外,JavaScript中的节点都是继承自Node类型。
@节点类型由Node类型定义的12个数值常量表示:
Node.ELEMENT_NODE(1);
Node.ELEMENT_NODE (1)
Node.ATTRIBUTE_NODE (2)
Node.TEXT_NODE (3)
Node.CDATA_SECTION_NODE (4)
Node.ENTITY_REFERENCE_NODE(5)
Node.ENTITY_NODE (6)
Node.PROCESSING_INSTRUCTION_NODE (7)
Node.COMMENT_NODE (8)
Node.DOCUMENT_NODE (9)
Node.DOCUMENT_TYPE_NODE (10)
Node.DOCUMENT_FRAGMENT_NODE (11)
Node.NOTATION_NODE (12)
@通过上面的常量能判断节点的类型,但由于IE没有公开Node类型的构造函数,故应该用数字值进行比较:
if (someNode.nodeType == Node.ELEMENT_NODE){ //在IE中无效 alert("元素节点"); } if (someNode.nodeType == 1){ //所有浏览器适应 alert("元素节点"); }
① nodeName 和 nodeValue
@对于元素节点,nodeName保存元素的标签名,nodeValue值为null。
② 节点关系
每个节点都有一个childNodes属性,保存着一个NodeList对象(一种类数组对象,保存一组有序的节点)。
@可通过方括号语法和item()方法访问NodeList的值,有length属性,但不是Array的实例,而是基于DOM结构动态查询的结果。
@在IE8-浏览器中,会为空白符创建节点,length属性值会包含空白符节点。
var firstChild = someNode.childNodes[0]; var secondChild = someNode.childNodes.item(1); var count = someNode.childNodes.length;
@length属性表示的是访问NodeList那一刻节点的数量。
@将NodeList对象转换为数组:使用Array.prototype.slice()【IE8及以下无效,需要手动枚举所有成员】
function toArray(){ var array = null; try { array = Array.prototype.slice.call(nodes, 0); //针对IE9+等浏览器 } catch(ex){ array = new Array(); for (var i = 0; i < nodes.length; i++) { //针对IE8- array.push(nodes[i]); }; } return array; }
每个节点都有一个parentNode属性。包含在NodeList列表中的所有节点具有相同的父节点,他们的parentNode指向同个父节点。
每个节点通过previousSibling和nextSibling属性访问相邻节点。首个节点的previousSibling和末尾节点的nextSibling都是null。
@只有一个子节点时,firstChild和lastChild指向同个子节点。没有节点时都为null。
@hasChildNodes()方法在节点包含子节点时返回true。
③ 操作节点
appendChild():1个子节点参数。向NodeList列表末尾添加一个新节点。
@当传入的节点已经是文档的一部分时,该节点讲过从原点的位置转移到新位置。
insertBefore():2个参数。在指定节点前插入一个新节点。
@当指定的节点即第一个参数为null时,insertBefore()和appendChild()执行相同的操作。
//插入后成为最后一个子节点 returnedNode = someNode.insertBefore(newNode,null); alert(newNode == someNode.lastChild); //true //插入后成为第一个子节点 returnedNode = someNode.insertBefore(newNode,firstChild); alert(returnedNode == newNode ); //true alert(newNode == someNode.firstChild); //true //插入到最后一个子节点前面 returnedNode = someNode.insertBefore(newNode,someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.lehgth-2]); //true
replaceChild():2个参数。以上述2种不同的是,这个方法会代替原来的节点。
@插入一个节点时,原节点的所有关系指针都会被代替节点复制过去,但原节点仍在文档中,只是没有了自己的位置。
//替换第一个子节点 var returnedNode = someNode.replaceChild(newNode,someNode.firstChild); //替换最后一个子节点 returnedNode = someNode.replaceChild(newNode,someNode.lastChild);
removeChild():1个参数。移除节点。移除的节点成为方法的返回值。
@同样,移除的节点仍为文档所有,只是没有了自己的位置。
前面4中操作方法,都需要先取得父节点。使用该4种方法操作不支持子节点的节点时,会导致错误发生。
④ 其他方法
2个所有类型节点都有的方法:cloneNode() 和 normalize()
cloneNode():1个布尔值参数(表是否执行深复制)。用于创建调用这个方法的节点的一个完全相同的副本。
参数为true时,执行深复制,即复制节点及其整个子节点树。
参数为false时,执行浅复制,即只复制节点本身。
@cloneNode():不会复制DOM节点的JavaScript属性(如事件处理程序),只复制特性。IE则会复制JavaScript属性。
normalize():用于处理文档树中的文本节点:删除空文本节点 和 合并相邻文本节点。
10.1.2 Document 类型
@JavaScript通过Document类型表示文档(HTML或XML)。浏览器中,document是HTMLDocument(继承自Document类型)的一个实例。
@document对象表示整个HTML页面,是window对象的属性,故可作为全局对象来访问。
nodeType=9
nodeName="#document"
nodeValue、parentNode、ownerDocument = null
子节点 可能是:DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment。
① 文档的子节点
@访问document子节点的2个快捷方式:documentElement属性 和 通过childNodes列表访问文档元素。
var html = document.documentElement; //取得对<html>的引用 //html的值 与 document.childNodes[0] 和 document.firstChild相同。
@document对象还有一个body属性,指向<body>元素。
@Document有一个可能的子节点DocumentType。
通常将<!DOCTYPE>标签看成一个与文档其他部分不同的实体,可通过doctype属性访问,但该属性兼容性差。
② 文档信息
@作为HTMLDocument的实例,document对象有一些标准Document对象没有的属性。
@title属性:将<title>元素中的文本,显示在浏览器窗口的标题栏的标签页上。
@存在请求的HTTP头部的属性:
URL属性,包含网页完整的URL。 var url = document.URL;
domain属性,包含页面的域名。
referrer属性,保存着链接到当前页面的那个页面的URL。没有来源页面时,为空字符串。
@3个中只有domain可设置,但受安全限制:
如:URL为aa.bbb.com时,domain只能设为bbb.com;
URL为www.bbb.com时,domain只能设为bbb.com。
不能将domain设置为URL不包含的域。
@跨域:当包含来自其他子域的框架或内嵌框架时,由于安全限制,默认不能与不同子域的页面进行JavaScript通信。
但通过将彼此的document.domain设置相同值,就能互访对方的JavaScript对象。
如:主页www.bbb.com,框架内的页面aa.bbb.com,设置彼此的document.domain = "bbb.com";,就能互访。
@对domain的另一个限制:不能将"松散的"(loose)domain属性 再设置为"紧绷的"(tight)
如:不能将松散的"bbb.com"设置为紧绷的"aa.bbb.com"。
③ 查找元素
Document类型提供2种方法:getElementById() 和 getElementByTagName()
getElementById():通过元素的ID引用元素。
@当页面有多个元素相同ID时,返回文档中第一次出现的元素。
@在IE7及以下版本中,name值以ID相同的表单元素也会被返回。当name与ID值相同的表单元素位于前面时,后面的“ID元素”不会被返回。
应该避免表单元素中name属性与其他元素的ID值相同。
getElementByTagName():接收标签名,返回包含零或多个元素的NodeList。在HTML中,返回一个HTMLCollection对象,一个"动态"集合。
@通过方括号语法 、item()方法 和 name值 访问HTMLCollection对象中的项,元素数量通过其length属性取得。
@HTMLCollection对象的namedItem()方法可通过元素name特性取得集合中的项。
@对HTMLCollection而言,可向方括号中传入数值或字符串形式的牵引值,
在后台,对数值牵引就会调用item()方法,对于字符串就会调用namedItem()。
getElementByName():返回给定name特性的所有元素。常用于取得单选按钮。
④ 特殊集合
document.anchors:包含所有带name特性的<a>元素。
document.links:包含所有带href特性的<a>元素。
document.forms:包含所有<form>元素。结果同document.getElementByTagName(“form”);
document.images:包含所有<img>元素。
⑤ 文档写入
将输出流写入页面的4个方法:write()、writeln()、opne() 和 close()
@write()原样写入。writeln()在字符串较为添加一个换行符(\n)。
@输入"</script>"时,应该添加转义序列,不然会被解释为与外部<script>标签匹配,' "); '会出现在页面中。
document.write("<script type=\"text\javascript\" src=\"file.js\">" + "<\/script>");
@加载结束后,再调用document.write()输出的内容会重写整个页面。
<body>
<p>This is some content that you won't get to see because it will be overwritten.</p>
<script type="text/javascript">
window.onload = function(){
document.write("Hello world!");
};
</script>
</body>
@open()和close()分别打开和关闭输出流。
10.1.3 Element类型
@Element节点的子节点可能是:Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。
@通过nodeName和tagName能访问元素的标签名。
@在HTML中,标签名始终全部大写。在XML中,始终与源代码一致。最好使用toLowerCase()统一转换为小写。
① HTML元素
@所有HTML元素 都由HTMLElement类型或其子类型表示。HTMLElement继承自Element。
@每个HTML元素的标准特性:
id元素在文档的唯一标识符、className对应元素的name特性、title附加说明信息、lang元素内容的语言代码、dir语言方向"ltr"/"rtl"。
var div = null; function getValues(){ if (div == null) { div = document.getElementById("myDiv"); } alert(div.id); //"myDiv" alert(div.className); //"bd" alert(div.title); //"Body text" alert(div.lang); //"en" alert(div.dir); //"ltr" } function setValues(){ if (div == null) { div = document.getElementById("myDiv"); } div.id = "someOtherId"; div.className = "ft"; div.title = "Some other text"; div.lang = "fr"; div.dir ="rtl"; }
② 取得特性
操作DOM的3个方法:getAttribute()、setAttibute()、removeAttribute()
getAttribute()【应该尽量不用它访问HTML特性】
@传入的为实际的特性名,故应该用class而不是className。不存在时,返回null。
@可获取自定义特性(标准HTML语言没有的特性),特性名称不区分大小写。在HTML5规范中,自定义特性应该以“data-”前缀。
@只有公认特性才会以属性的形式添加到DOM对象中。自定义特性只有在IE中才被创建为属性。
//<div id="myDiv" my_attribute="hello!">Some text</div> var div = document.getElementById("myDiv"); alert(div.id); //"myDiv" alert(div.my_special_attribute); // undefined (IE 返回"hello!")
@两类特殊的特性:style属性 和 事件处理程序
@通过getAttribute()返回CSS文本,通过访问style属性返回一个对象。
@事件句柄中的JS代码通过getAttribute()访问时,返回相应代码的字符串。而在直接访问类似onclick属性时,返回一个函数。
@在IE7-中两个特性分别返回一个对象和一个函数。
③ 设置特性
setAttitude()
@通过此方法设置的特性名会被统一转换为小写形式。
@所有特性都是属性,故可以直接赋值和调用:div.id = "balabala";
但自定义特性在IE以外的浏览器,为DOM元素添加一个自定义的属性时,该属性不会自动成为元素的特性。
div.myColor = "red"; alert(div.getAttibute("myColor")); //null (IE除外)
在IE7-中使用上述方法设置class、style属性 和 事件句柄,没有效果。故推荐使用属性来设置特性。
removeAttitude()【IE6-不支持】
④ attitude属性【没getAttribute()等方法方便】
@Element类型是使用attributes属性的唯一一个DOM节点类型。
@attributes属性包含一个NamedNodeMap集合(类似NodeList)。
元素的每一个特性都由一个Attr节点表示,Attr节点保存早NameNodeMap对象中。
@NameNodeMap对象的方法:
getNamedItem(name):返回nodeName属性等于name的节点。
removeNamedItem(name):从列表中移除nodeName属性等于name的节点。
setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引。
item(num):返回位于数字num位置处的节点。
@attributes属性中包含一系列节点,每个节点nodeName为特性名,nodeValue为特性值。
var id = element.attribute.getNamedItem("id").nodeValue; var id = element.attribute["id"].nodeValue;
@removeNamedItem()效果同removeAttribute(),但removeNamedItem()返回被删除特性的Attr节点。
@迭代元素的每个特性:
function outputAttributes(element){ var pairs = new Array(), attrName, attrValue, i, len; for (i=0, len=element.attributes.length; i < len; i++){ attrName = element.attributes[i].nodeName; attrValue = element.attributes[i].nodeValue; pairs.push(attrName + "=\"" + attrValue + "\""); } return pairs.join(" "); }
以上代码并不完善,IE7-会返回HTML元素中的所有可能特性,包括没指定的特性。
解决方案:每个特性节点都有一个specified属性。
该属性值为true时,表示要么在HTML中指定了相应特性,要么通过setAttribute()方法设置了该特性。
在IE中没设置的特性的specified属性值为false。
而在其他浏览器,根本不会为未设置的特性生成对应的特性节点。所有特性节点的specified属性值都为true。
function outputAttributes(element){ var pairs = new Array(), attrName, attrValue, i, len; for (i=0, len=element.attributes.length; i < len; i++){ attrName = element.attributes[i].nodeName; attrValue = element.attributes[i].nodeValue; if (element.attributes[i].specified){ pairs.push(attrName + "=\"" + attrValue + "\""); } } return pairs.join(" "); }
⑤ 创建元素
document.createElement(),一个参数(标签名)。
@使用createElement()创建新元素时,也为元素设置了ownerDocument属性。
@传入完整的元素标签,能解决IE7-中创建元素的一些问题:
不能设置动态创建<iframe>元素的name特性;
不能通过标签元素的reset()方法重设动态创建的<input>元素;
动态创建的type属性值为reset的<button>元素重设不了表单;
动态创建的一批name值相同的单选按钮彼此毫无关系。
@上述IE7-中的问题都能通过给createElement()穿日完整的标签解决:
if(client.browser.ie && client.browser.ie <= 7){ var iframe = document.createElement("<iframe name=\"maiframe\"></iframe>"); var input = document.createElement("<input type=\"checkbox\"/>"); var button = document.createElement("<buttom 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\"/>"); }
10.1.4 Text类型
文本节点由Text类型表示,不支持子节点。
@可通过nodeValue属性或data属性访问Text节点中包含的文本。
@操作节点中的文本:
appendData(text):将text添加到节点的末尾。
deleteData(offset,count):从offset位置开始删除count个字符。
insertData(offset,text):从offset位置插入text。
replaceData(offset,count,text):用text代替从offset位置开始的count个字符。
splitText(offset):从offset位置将文本分成两部分。
substringData(offset,count):提取从offset位置到offset+count位置的文本。
length属性:节点中的字符个数。
@在修改文本节点时,字符串会经过编码,即不会字符串中的标签会与文本的形式显示:
div.firstChild.nodeValue = "Some <strong>other</strong> message"; //输出Some <strong>other</strong> message
① 创建文本节点
document.createTextNode(),同上,传入的文本参数也会进行编码。
@连续添加多个文本节点时,各相邻节点会连起来显示,中间无空格。
function addNode(){ var element = document.createElement("div"); element.className = "message"; var textNode = document.createTextNode("11111"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("22222"); element.appendChild(anotherTextNode); document.body.appendChild(element); } //输出1111122222
② 规范化 文本节点
normalize():能将包含的多个文本节点合并成一个。此方法由Node类型定义,故存在于所有节点类型中。
function addNode(){ var element = document.createElement("div"); element.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element); alert(element.childNodes.length); //2 element.normalize(); alert(element.childNodes.length); //1 alert(element.firstChild.nodeValue); //"Hello World!Yippee!" }
③ 分隔文本节点
splitText():此方法会将文本节点分成两个文本节点,原来的文本节点剩下没指定的,新文本节点包含指定的文本,并被此方法返回。
10.1.5 Comment类型
@注释类型Comment与Text类型继承自相同的基类。
@浏览器不会识别位于</html>标签后的注释,故应该保证它们是<html>的后代。
10.1.6 CDATASection类型
此类型只针对XML文档,表示CDATA区域。
10.1.7 DocumentType类型
包含文档的dictype有关的所有信息,仅有Firefox、Safari、Chrome4 和 Opera支持。
10.1.8 DocumentFragment类型
@所有节点类型中,只有DocumentFragment类型没有对应的标记。
@DOM规文档片段是一种“轻量级”文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。
①document.createDocumentFragment()方法,创建文档片段。
@继承了Node的所有方法。
@将文档中的节点添加到文档片段中,就会从文档树中移除节点。添加在文档片段的新节点同样不属于文档树。
@文档片段本身永远不会成为文档树的一部分。
②当需要给一个<ul>元素添加三个列表项时,逐个添加列表项节点会导致浏览器反复渲染和呈现新信息,使用文档片段能避免:
<body> <ul id="myList"></ul> <input type="button" value="Add Items" onclick="addItems()"> <script type="text/javascript"> function addItems(){ 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); } </script> </body>
@添加后,文档片段中的子节点会被删除。
10.1.9 Attr类型
元素的特性在DOM中与Attr类型表示。
@特性是存在于元素的attribute属性中的节点,但特性不被认为是DOM文档的一部分。
@Attr类型有三个属性:name(特性名,同nodeName)、value(特性值,同nodeValue) 和 specified(布尔值,区分特性是指定还是默认)。
function assignAttribute(){ var element = document.getElementById("myDiv"); var attr = document.createAttribute("align"); attr.value = "left"; element.setAttributeNode(attr); alert(element.attributes["align"].value); //"left" alert(element.getAttributeNode("align").value); //"left" alert(element.getAttribute("align")); //"left" }
@实际上,使用getAttribute()等3个方法比操作特性节点更方便。
10.2 DOM操作技术
10.2.1 动态脚本
① 通过<script>元素,有 src属性指定外部文件 和 本身包围代码 2种方法向页面添加JavaScript代码。
② 动态脚本,指在加载页面时不存在,但在某时通过修改DOM动态添加的脚本。
@创建动态脚本也有两种方法:插入外部文件 和 直接插入JavaScript代码。
③ IE中,<script>为一个特殊元素,不允许DOM访问其子节点:
script.appendChild(document.createTextNode("function sayHi(){alert("Hello!");}")); (在IE会报错)
@IE中可以使用script的text属性 指定JavaScript代码:
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); } function addScript(){ loadScriptString("function sayHi(){alert('hi');}"); sayHi(); }
这样加载的代码会在全局作用域中执行,与在全局作用域中 把相同的字符串传递给你eval()一样。
10.2.2 动态样式
①有2个元素 能将CSS样式包含到HTML页面中,<link>元素包含外部文件,<style>元素指定嵌入的样式。
@加载外部样式文件是异步的,即与执行JavaScript代码的过程没有固定程序。
②同样的,IE中<style>为一个特殊的元素,不允许访问其子节点:
style.appendChild(document.createTextNode("body{color:red;}")); (在IE会报错)
@IE中可以使用访问元素的styleSheet属性的cssText属性 指定CSS样式:
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); } function addStyle(){ loadStyleString("body{background-color:red}"); }
@在针对IE编码时,应注意使用styleSheet.cssText属性:
在重用同个<style>元素并再次设置这个属性时,可能会导致浏览器崩溃。将cssText属性设为空字符串也可能使浏览器崩溃。
10.2.3 操作表格
为了方便构建表格,HTML DOM还为<table>、<tbody> 和 <tr>元素添加了一些属性:
为<teble>元素添加的属性和方法:
caption:(若有)保存着对<caption>元素的指针。
tHead:(若有)同上。
tFoot:(若有)同上。
tBodies:一个所有<tbody>元素的HTMLCollection。
rows:一个所有行的HTMLCollection。
createCaption:创建该元素,放如表格,返回引用。
createTHead():同上。
createTFoot():同上。
deleteCaption():删除<caption>元素。
deleteTHead():同上。
deleteTFoot():同上。
insertRow(pos):向rows集合中的指定位置 插入一行。
deleteRow(pos):删除指定位置的一行。
<tbody>:
rows:一个保存着<tbody>元素中行的HTMLCollection。
insertRow(pos):向rows集合中指定位置插入一行,返回新行的引用。
deleteRow(pos):删除指定位置的行。
<tr>:
cells():一个保存着<tr>元素中的单元格的HTMLCollection。
insertCell(pos):向cells集合中的指定位置插入一个单元格,返回新单元格引用。
deleteCell(pos):删除指定位置的单元格。
//创建talbe
var table = document.createElement('table');
table.border = 1;
table.width = "100%";
//创建tbody
var tbody = document.createElement('tbody');
table.appendChild(tbody);
//创建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode('cell 1,1'));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode('cell 2,1'));
//创建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode('cell 1,2'));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode('cell 2,2'));
document.body.appendChild(table);
10.2.4 使用NodeList
NodeList、NameNodeMap 和 HTMLColletction 三个集合都是动态的。
@浏览器不会将创建的所有集合保存在一个列表中,而是在下一次访问集合是更新集合。
迭代一个NodeList时,应该在比较之前,先将其length保存在一个变量中:
var divs = document.getElementByTagName("div"), i, len, div; for(i=o, len=divs.length; i<len; i++){ div = document.createElement("div"); document.body.sppendChild(div); }