《JavaScript高级程序设计》(第3版)读书笔记 第10章 DOM

  • DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,容许开发人员添加、移除和修改页面的某一部分。DOM脱胎于Netscape及微软公司创始的DHTML(动态HTML),但如今它已经成为表现和操做页面标记的真正跨平台、语言中立方式。
  • 1998年10月DOM 1 级规范成为W3C的推荐标准,为基本的文档结构及查询提供了接口。本章主要讨论与浏览器中的HTML页面相关的DOM1级的特性和应用,以及JavaScript对DOM1级的视线。
  • IE中的全部DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM对象与原生JavaScript对象的行为或活动特色并不一致。本章将较多的谈及这些差别。

节点层次

<html>
  <head>
    <title>Sample Page</title>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>
  • 能够将这个简单的HTML文档视为一个层次结构,如图10-1所示。

![alt](img/charptor10_1.jpg)

  • 文档节点是每一个文档的根节点。在这个例子中,文档节点只有一个子节点,既<html>元素,咱们称之为文档元素
  • 文档元素是文档的最外层元素,文档中的其余全部元素都包含在文档元素中。每一个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。在XML中,没有预约义的元素,所以任何元素均可能成为文档元素。

Node 类型

  • DOM1级定义了一个 Node 接口,该接口将由 DOM 中全部节点类型实现。这个Node接口在JavaScript中是做为Node类型实现的;除了IE以外,在其余全部浏览器中均可以访问到这个类型。
  • JavaScript中的全部节点类型都继承自Node类型,所以全部节点类型都共享着相同的基本属性和方法。
  • 每一个节点都有一个nodeType属性,用于代表节点的类型。及诶单类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一(编号为节点类型常量存储的数值):javascript

    1. Node.ELEMENT_NODE
    2. Node.ATTRIBUTE_NODE
    3. Node.TEXT_NODE
    4. Node.CDATA_SECTION_NODE
    5. Node.ENTITY_REFERENCE_NODE
    6. Node.ENTITY_NODE
    7. Node.PROCESSING_INSTRUCTION_NODE
    8. Node.COMMENT_NODE
    9. Node.DOCUMENT_NODE
    10. Node.DOCUMENT_TYPE_NODE
    11. Node.DOCUMENT_FRAGMENT_NODE
    12. Node.NOTATION_NODE
// 经过比较上面的常量,很容易的肯定节点类型
// 在IE中无效
if (someNode.nodeType == Node.ELEMENT_NODE) {
  console.log("Node is an element");
}

// 因为IE没有公开 Node 类型的构造函数
// 最好仍是将 nodeType 属性与数字比较
if (someNode.nodeType == 1) {
  console.log("Node is an element");
}
  • 并非全部节点类型都受到Web浏览器的支持。开发人员最经常使用的就是元素和文本节点。

nodeNamenodeValue 属性

  • 了解节点的具体信息,可使用nodeNamenodeValue 两个属性。这两个属性的值彻底取决于节点类型。在使用这两个值之前,最好用上述的代码检查节点的类型。
if (someNode.nodeType == 1) {
  value = someNode.nodeName;            // nodeName的值是元素的标签名
}

节点关系

  • 每一个节点都有一个childNodes属性,其中保存着一个NodeList对象。注意,能够经过方括号语法来访问NodeList的值,并且也有length属性,但它并非Array的实例
  • NodeList对象的独特之处在于,它其实是基于DOM结构动态执行查询的结果,所以DOM结构的变化可以自动反映在NodeList对象中。咱们常说NodeList是有生命、有呼吸的对象,而不是咱们第一次访它的瞬间拍摄下来的一张快照。
// 方括号和 item() 语法结果是相同的
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = comeNode.childNodes.length;

// 虽然不是Array的实例,但咱们能够将它转换成数组
// 在IE8及以前的版本中无效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
// 因为IE8及更早版本将 NodeList 实现为一个 COM 对象
// 必须手动枚举全部成员,才能转换成数组
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]);
    }
  }

  return array;
}
  • 每一个节点都有一个parentNode属性,指向文档中的父节点。
  • 包含在childNodes中的全部节点都具备相同的父节点,而相互之间是同胞节点。
  • 经过每一个节点的previousSiblingnextSibling 属性能够访问同一列表中的其余节点。列表第一个节点previousSiblingnull,列表最后一个nextSiblingnull,固然若是列表只有一个节点,那么两个都是null
  • 父节点的firstChildlastChild属性分别指向第一个和最后一个。若是列表没有节点,那么两个属性都是null

图片描述

  • hasChildNodes()也是一个很是有用的方法,当查询节点存在子节点时返回true,不存在返回false。这是比查询childNodes.length更简单的方法。
  • 全部节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在两个或更多个文档中。经过这个属性,咱们能够没必要在节点层次中经过层层回溯达到顶端,而是能够直接访问文档节点。

操做节点

  • 由于关系指针都是只读的,因此DOM提供了一些操做节点的方法 。
  • 最经常使用的方法是appendChild(),用于向childNodes列表的末尾添加一个节点,执行后,方法返回新增的节点。
var returnedNode = someNode.appendChild(newNode);
console.log(returnedNode == newNode);           // true
console.log(someNode.lastChild ==newNode);      // true
  • 若是须要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,可使用insertBefore()方法。这个方法接收两个参数:要插入的节点和做为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling),同时被方法返回。若是参照节点是null,则 insertBefore()appendChild() 执行相同操做。
// 插入后成为最后一个子节点
var returnedNode = someNode.insertBefore(newNode, null);

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

// 插入后在最后一个子节点前面
var returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
  • replaceChild() 替换节点。一样接收两个参数,插入的节点和参照节点。插入新的节点并将参照节点从文档树中移除,新的节点会从被替换的节点复制全部关系指针。尽管从技术上讲,被替换的节点仍然在文档中,但它在文档中的位置已经不存在了。
  • removeChild() 移除节点。被移除的节点仍然在文档中,但它在文档中的位置已经不存在了。
  • 以上四个方法必须先取得操做节点的父节点(代码示例中是someNode)。在不存在子节点的节点上调用以上方法,会致使错误。

其余方法

  • 还有两个方法是全部节点都有的。
  • cloneNode() 用于建立调用这个方法的节点的一个彻底相同的副本。接收一个布尔值参数,表示是否执行深复制。css

    • 传入true。执行深复制,复制节点及其整个子节点树
    • 传入false。执行浅复制,即只复制节点自己。
    • 复制返回的节点副本属于文档全部,但并无为它制定父节点。所以这个节点副本就成为了一个“孤儿”,除非经过 appendChild() insertBefore() replaceChild() 将它添加到文档中。
    • IE8及以前的版本不会为包含空白符的文字建立节点(TEXT)
    • clone() 方法不会复制添加到DOM节点中的JavaScript属性,例如时间处理程序。这个方法只复制特性、(在明确指定的状况下也复制)子节点,其余一切都不会复制。
    • IE 会复制事件处理程序,因此咱们建议在复制以前最好先移出事件处理程序。
<ul id="ul">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
</ul>
var myList = document.getElementById("ul");
var deepList = myList.cloneNode(true);
// [text, li, text, li, text, li, text]
console.log(deepList.childNodes);
// 3 (IE < 9) 或 7 (其余浏览器)
// IE8及以前的版本不会为包含空白符的文字建立节点(TEXT)
console.log(deepList.childNodes.length);

var shallowList = myList.cloneNode(false);
console.log(shallowList.childNodes.length);     // 0
  • normalize() 方法惟一的做用就是处理文档树中的文本节点。因为解析器的实现或DOM操做等缘由,可能会出现文本节点不包含文本,或者链接出现两个节点的状况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种状况。html

    • 若是找到了空文本节点,则删除它
    • 若是找到相邻的文本节点,则将它们合并为一个文本节点
    • 本章后面还将进一步讨论方法
var html = document.documentElement;               // 取得对<html>的引用
console.log(html == document.childNodes[0]);       // true
console.log(html == document.firstchild)           // true
  • 全部浏览器都支持document.documentElementdocument.boyd 属性
  • Document另外一个可能的子节点是DocumentType。一般将<!DOCTYPE>标签当作一个与文档其余部分不一样的实体,能够经过doctype属性(在浏览器中是document.doctype)来访问信息。
  • 浏览器对document.doctype的支持差异很大,因此这个属性的用途颇有限:java

    • IE8及以前版本,若是存在文档类型声明,会将其错误的解释为一个注释并把它当作Comment节点;而document.doctype的值始终为null
    • IE9+,若是存在文档类型声明,则将其做为文档的第一个子节点;document.doctype是一个DocumentType节点,也能够经过document.firstChilddocument.childNodes[0]访问同一个节点。
    • Safari, Chrome, Opera :若是存在文档类型声明,则将其解析,但不做为文档的子节点。document.doctype是一个DocumentType节点,但该节点不会出如今document.childNodes中。
  • 从技术上说,出如今<html>元素外部的注释应该是算是文档的子节点。然而,不一样的浏览器在是否解析这些注释以及可否正确处理他们等方面,也存在很大差别。
<!-- 第一条注释 -->
<html>
  <body>
  </body>
</html>
<!-- 第二条注释 -->
  • 看起来这个页面应该有3个子节点:注释、<html>元素、注释。从逻辑上讲,咱们会认为document.childNodes中应该包含与这3个节点对应的3项。可是实际上,浏览器存在如下差别:node

    • IE8及以前版本、Safari3.1及更高版本、Opera和Chrome 只为第一条注释建立节点,不为第二条注释建立节点。结果第一条注释就会成为document.childNodes中的第一个子节点。
    • IE9+,将会将两条都建立节点。
    • Firefox 和 Safari3.1以前的版本会彻底忽略这两条注释。
  • 多数状况下,咱们都用不着在document对象上调用appendChild() removeChild() replaceChild() 方法,由于文档类型(若是存在的话)是只读的,并且它只能有一个元素子节点(该节点一般早就已经存在了)。

文档信息

  • 做为HMLTDocument的一个实例,document对象还有一些标准的Document对象所没有的属性。
  • title 包含着<title>元素中的文本。经过这个属性能够取得当前页面的标题,也能够修改当前页面的标题并反映在浏览器的标题栏中。修改title属性的值会改变<title>元素。
// 取得文档标题
var originalTitle = document.title;

// 设置文档标题
document.title = "New page title";
  • 下面三个属性与网页的请求有关,全部这些信息都存在于请求的HTTP头部,只不过是经过这些属性让咱们可以在JavaScript中访问它们而已:编程

    • URL属性中包含页面完整的URL(地址栏中的URL)
    • domain属性中值包含页面的域名
    • referrer属性中可能会包含空字符串
    • URLdomain属性是相互关联的。例如document.URL等于"http://www.wrox.com/WileyCDA/",那么document.domain就等于"www.wrox.com"。
    • 3个属性中只有domain能够设置,但有安全方面的限制。若是URL中包含一个子域名,例如"p2p.wrox.com",那么就只能讲domain设置为"wrox.com"(URL中包含"www",如"www.wrox.com"时,也是如此)。
    • 当页面中包含来自其余子域的框架或内嵌框架时,可以设置document.domain就很是方便了。因为跨域安全限制,来自不一样子域的页面没法经过JavaScript通讯。而经过将每一个页面的document.domain设置为相同的值,这些页面就能够互相访问对方包含的JavaScript对象了。
// 取得完整的URL
var url = document.URL;

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

// 取得来源 页面的URL
var referrer = document.referrer;
  • 浏览器对domain属性还有一个限制,即若是域名一开始是“松散的”(loose),那么就不能将它再设置为“紧绷的”(tight)。
// 假设页面来自于 p2p.wrox.com域

document.domain = "wrox.com";         // 松散的(成功)
document.domain = "p2p.wrox.com";     // 紧绷的(出错)

查找元素

  • getElementById() 接收一个参数:要取得的元素的ID。找到相应的元素则返回该元素,不然返回null跨域

    • IE8及较低版本不区分ID大小写
    • 若是页面多个元素的ID相同,只会返回第一个匹配的元素。
    • IE7及更早的版本添加了一个怪癖:name特性与给定ID匹配的表单元素也会被该方法返回。
    <input type="text" name="myElement" value="Text field">
    <div id="myElement">A div</div>
    <script>
      // IE7中调用会返回<input>元素
      var el = document.getElementById("myElement");
    </script>
  • getElementsByTagName() 接收一个参数:要取得的元素的标签名,而返回的是包含零或多个元素的NodeList。可使用方括号语法或item()方法来访问对象中的项。
  • namedItem() 使用这个方法能够经过元素的name特性取得集合中的项。或方括号语法能达到一样的效果
<img src="myimage.gif" name="myImage">

<script>
  var images = document.getElementsByTagName("img");

  console.log(images.length);
  console.log(images[0].src);                           // 方括号传入数值就调用 item()
  console.log(images.item(0).scr);
  var myImage = images.namedItem("myImage");
  var myImage = images["myImage"];                      // 方括号传入字符串就调用namedItem()
</script>
  • 要取得文档中的全部元素,能够向getElementsByTagName()中传入"*"。在JavaScript及CSS中,星号一般表示所有。
  • 虽然标准规定标签名须要区分大小写,但为了最大限度的与既有HTML页面兼容,传给getElementsByTagName()的标签名是不须要区分大小写的。但对于XML页面而言(包括XHTML),getElementsByTagName()方法就会区分大小写。
  • getElementByName() 是只有HTMLDocument类型才有的方法,返回带有给定name属性的全部元素。最常使用的状况是取得单选按钮;为了确保发送给浏览器的值正确无误,全部单选按钮必须具备相同的name特性
<fieldset>
  <legend>Which color do you prefer?</legend>
  <ul>
    <li>
      <input type="radio" value="red" name="color" id="colorRed">
      <label for="colorRed">Red</label>
    </li>
    <li>
      <input type="radio" value="green" name="color" id="colorGreen">
      <label for="colorGreen">Green</label>
    </li>
    <li>
      <input type="radio" value="blue" name="color" id="colorBlue">
      <label for="colorBlue">Blue</label>
    </li>
  </ul>
</fieldset>
  • 上述例子使用getElementsByName()方法能够返回三个input元素。可是对于这里的单选按钮来讲namedItem()方法只会取得第一项(由于每一项的name特性都相同)。

特殊集合

  • document.anchors 包含文档中全部带name特性的<a>元素
  • document.applets 包含文档中全部的<form>元素,与document.getElementsByTagName("form")获得的结果相同
  • document.images 包含文档中全部的<img>元素,与document.getElementsByTagName("img")获得的结果相同
  • document.links 包含文档中全部带 href 特性的<a>元素

DOM一致性检测

  • 因为DOM分为多个级别,也包含多个部分,所以检测浏览器实现了DOM的哪些部分就十分必要。document.implementation属性就是为此提供的,与浏览器对DOM的实现直接对应。
  • DOM1级别只为document.implementation规定了一个方法,即hasFeature()。接收两个参数:要检测的DOM功能的名称及版本号。若是支持返回true
var hasXmlDom = docuemnt.implementation.hasFeature("XML", "1.0");
  • 下表列出了能够检测的不一样值得版本号
功能 版本号 说明
Core 1.0、2.0、3.0 基本的DOM,用于描述表现文档的节点树
XML 1.0、2.0、3.0 Core的XML拓展,添加了对CDATA、处理指令及实体的支持
HTML 1.0、2.0 XML的HTML拓展,添加了对HTML特有元素及实体的支持
Views 2.0 基于某些样式完成文档的格式化
StyleSheets 2.0 将样式表关联到文档
CSS 2.0 对层叠样式表1级的支持
CSS2 2.0 对层叠样式表2级的支持
Events 2.0, 3.0 常规的DOM事件
UIEvents 2.0, 3.0 用户界面事件
MouseEvents 2.0, 3.0 由鼠标引起的事件(click、mouseover等)
MutationEvents 2.0, 3.0 DOM树变化时引起的事件
HTMLEvents 2.0 HTML4.01事件
Range 2.0 用于操做DOM树种某个范围的对象和方法
Traversal 2.0 遍历DOM树的方法
LS 3.0 文件与DOM树之间的同步加载和保存
LS-Asnyc 3.0 文件与DOM树之间的异步加载和保存
Validation 3.0 在确保有效的前提下修改DOM树的方法
  • hasFeature() 方法确实方便,但也有缺点。由于实现者能够自行决定是否与DOM规范的不一样部分保持一致。事实上,想让hasFearture()针对全部值都有返回true很容易,但返回true有时候也不意味着实现与规范一致。
  • 为此咱们建议,在使用hasFreatrue()以外,还同时使用能力检测。

文档写入

  • write()writeln()方法都接收一个字符串参数,即要写入到输出流中的文本。wirte()会原样写入,而writeln()则会在字符串的末尾添加一个换行符(n)。在页面加载的过程当中,可使用这两个方法动态的加入内容。
  • 在包含JavaScript文件时,必须注意不能像下面的例子那样直接包含字符串"</script>",由于这会致使该字符串被解释为脚本块的结束,后面的代码将没法执行。使用转义"</script>"能够避免这个问题。
  • open()close()分别用于打开和关闭网页的输出流。若是是在页面加载期间使用write()writeln()方法,则不须要用到这两个方法。
  • 严格型XHTML文档不支持文档吸入。对于那些按照application/xml+xhtml内容类型提供的页面,这两个方法也一样无效。

Element类型

  • Element类型用于表现XML或XHTML元素,提供了对元素标签名、子节点及特性的访问。
  • Element类型具备如下特征:数组

    • nodeType的值为1
    • nodeName的值为元素的标签名
    • nodeValue的值为null
    • parentNode的值可能为Dcoment或Element
    • 其子节点多是 ElementTextCommentProcessingInstructionCDATASectionEntityReference
  • 访问元素的标签名,可使用nodeName属性,也能够是使用tagName属性,这两个属性会返回相同的值。
var div = document.getElementById("myDiv");
console.log(div.tagName);                   // "DIV"
console.log(div.nodeName);                  // "DIV"
console.log(div.tagName == div.nodeName);   // true
if (element.tagName == "div") {
  // 不能这样比较,很容易出错
}

if (element.tagName.toLowerCase() == "div") {
  // 推荐这样作(适用于任何文档)
}

HTML元素

  • 全部HTML元素都由HTMLElement类型表示。HTMLElement类型直接继承自Elment并添加了一些属性。每一个HTML元素中都存在的下列标准特性:浏览器

    • id 元素在文档中的惟一标识符
    • title 有关元素的附加说明信息,通常经过工具提示条显示出来
    • lang 元素内容的语言代码,不多使用
    • dir 语言的方向值为"ltr"(left-to-right 从左至右)或 "rtl"
    • className 与元素的class特性对应,即为元素指定的CSS类。没有将这个属性命名为class是由于class是ECMAScript的保留字。
  • 并非对全部属性的修改都会在页面中直观的表现出来。对id或lang的修改对用户而言是透明不可见的。而对title的修改则只会在鼠标移动到这个元素之上时才会显示出来。对dir的修改会在属性重写的那一刻,当即影响页面中文本的左右对齐方式。修改className时,若是新类关联了与此前不一样的CSS样式,就当即应用新的样式。
  • 下面表格列出了全部HTML元素以及与之关联的类型(以斜体印刷的元素表示不推荐使用了)。注意表中的这些类型在Opera、Safari、Chrome、Firefox中均可以经过JavaScript访问,但在IE8以前的版本中,不能经过JavaScript访问。

图片描述
图片描述

取得特性

  • 操做特性的DOM方法主要有三个,分别是getAttribute()setAttribute()removeAttribute()
var div = document.getElemntByid("myDiv");
console.log(div.getAttribute("id"));          // "myDiv"
console.log(div.getAttribute("class"));          // "bd"
console.log(div.getAttribute("title"));          // "Body Text"
console.log(div.getAttribute("lang"));          // "en"
console.log(div.getAttribute("dir"));          // "ltr"
  • 注意,传递给getAttribute()的特性名与实际的特性名相同。所以想要获得class特性值,应该传入"class" 而不是"className",后者只在经过对象属性访问特性时才用。
  • 若是给定的特性不存在,getAttribute()返回null
  • 也能够取得自定义特性,即标准HTML语言中没有的特性的值。须要注意,特性的名称不区分大小写,即"ID" 和 "id" 表明的都是同一个特性。另外也要注意,根据HTML5规范,自定义特性应该加上data-前缀以便验证。
  • 任何元素的全部特性,也均可以经过DOM元素自己的属性来访问。固然HTMLElement也会有5个属性与相应的特性一一对应。不过只有公认的(非自定义)特性才会以属性的形式添加到DOM对象中。例如能够经过div.id访问div元素的id属性。不过自定义特性在Safari、Opera、Chrome、Firefox中是不存在的,但IE却会为自定义特性也建立属性。
  • CSS经过getAttribute()访问时,返回的style特性值中包含的是CSS文本,而经过属性来访问它则会返回一个对象。因为style属性是用于以编程方式访问元素样式的(本章后面讨论),所以并无直接映射到style特性。
  • 时间处理程序(例如onclick)经过getAttribute()访问,返回的是相应的代码字符串。而在访问onclick属性时,则返回的是一个JavaScript函数(若是未在元素中指定相应特性,则返回null)。这是由于onclick及其余事件程序属性自己就应该被赋予函数值。
  • 因为存在上述差异,在经过JavaScript以编程方式操做DOM时,开发人员不常用 getAttribute()方法,而只是使用对象的属性。只有在取得自定义特性值得状况下,才会使用getAttribute()方法。
  • 在IE7及之前版本中,经过getAttribute()访问style特性或onclick,返回的值与属性相同,都返回对象值或函数值。虽然IE8已经修复了这个bug,但不一样IE版本间的不一致性,也是致使开发人员不适用getAttribute()访问HTML特性的一个缘由。

设置特性

  • getAttribute()对应的方法时setAttribute()这个方法接收两个参数:要设置的特性名和值。若是特性已经存在,setAttribute()会以指定的值替换现有的值;若是特性不存在,则建立该属性并设置相应的值。
  • setAttribute()方法既能够操做HTML特性也能够操做自定义特性。经过这个方法设置的特性名会统一转换为小写形式,即"ID"最终变成"id"。
div.setAttribute("id", "someOtherId");
div.id = "someOtherId";

// 添加自定义属性,该属性不会自动成为元素的特性
div.mycolor = "red";
div.getAttribute("mycolor");   // null ie除外
  • removeAttribute() 用于完全删除元素的特性,调用这个方法不只会清楚特性的值,并且也会从元素中彻底删除特性。这个方法并不经常使用,IE6及之前版本不支持。
div.removeAttribute("class");

attributes属性

  • Element 类型是使用 attributes 属性的惟一一个DOM节点类型 。
  • attributes属性中包含一个NamedNodeMap,与NodeList相似,也是一个动态集合。元素的每个 特性都由一个Attr节点表示,每一个节点都保存在NamedNodeMap对象中。
  • NamedNodeMap对象拥有如下方法缓存

    • getNamedItem(name):返回nodeName属性等于name的节点
    • removeNamedItem(name):从列表中移除nodeName属性 等于name的节点
    • setNameItem(node):向列表中添加节点,以节点的nodeName属性为索引
    • item(pos):返回位于数字pos位置处的节点
  • attributes属性中包含一系列节点,每一个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。
// 取得元素的id
var id = element.attributes.getNamedItem("id").nodeValue;

// 设置元素的id
element.attributes["id"].nodeValue = "someOtherId";

// 删除元素id,并返回被删除特性的Attr节点
var oldAttr = element.attributes.removeNamedItem("id");

// 传入一个新的特性节点
element.attributes.setNameItem(newAttr);
  • 因为attributes的方法不够方便,所以开啊人员更多的会使用getAttribute()removeAttribute()setAttribute()方法。若是想要遍历元素特性,能够用attributes
  • 针对attributes对象中的特性,不一样浏览器返回的顺序不一样。
  • IE7及更早版本返回HTML元素中全部可能的特性,包括没有指定的特性。返回100多个特性是常见的
// 迭代元素的每个特性,而后构形成 name="value"字符串
function outputAttributes(element) {
  var pairs = new Array(),
      attrName,
      attrValue,
      i,
      len;
  for (i=0, len=elment.attributes.length; i < len; i++) {
    attrName = element.attributes[i].nodeName;
    attrValue = element.attributes[i].nodeValue;
    // 针对 IE7- 作兼容
    // 根据specified属性,只返回指定的特性
    if (element.attributes[i].specified) {
      paris.push(attrName + "=\"" + attrValue + "\"");
    }
  }
  return pairs.join(" ");
}

建立元素

  • document.createElement()方法能够建立新元素。只接收一个参数,即要建立元素的标签名,在HTML文档中不区分大小写,而在XML(包括XHTML)文档中,则是区分大小写。
  • document.createElement()建立元素的同时,也为新元素设置了ownerDcoument属性。此时还能够操做元素的特性,为它添加更多子节点。
  • 因为新元素还没有被添加到文档树中,所以设置这些特性不会影响浏览器的显示。要把新元素添加到文档树,可使用appendChild() insertBefore() replaceChild()方法。
// 建立
var div = document.createElement("div");
// 操做元素特性,添加子节点
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
  • 在IE中能够传入完整的元素标签,也能够包含属性(仅IE支持)。这样有助于避开在IE7及更早版本中动态建立元素的某些问题:

    • 不能设置动态建立的<iframe>元素的name特性
    • 不能经过表单的reset()方法重设动态建立的<input>元素(第13章讨论reset()方法)
    • 动态建立的type特性值为“reset”的<button>元素重设不了表单
    • 动态建立的一批name相同的单选按钮彼此毫无关系。
if (client.browser.id && client.browser.ie <= 7) {
  var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div>");
}

元素的子节点

  • 元素能够有任意书目的子节点和后台节点,由于元素能够是其余元素的子节点。元素的childNodes属性中包含了它全部子节点,这些子节点多是元素、文本节点、注释或处理指令。不用浏览器在看待这些节点方面存在显著的不一样。
<ul id="myList">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
</ul>
  • IE解析,<ul>元素会有3个子节点,分别是3个<li>元素。但若是是其余浏览器,<ul>元素都会有7个元素,包括3个<li>元素和4个文本节点(表示<li>元素之间的空白符)。
  • 若是将元素间的空白符删除,那么全部浏览器都会返回相同数目的子节点
<ul id="myList"><li>item 1</li><li>item 2</li><li>item 3</li></ul>
  • 若是须要经过childNodes属性遍历子节点,那么必定不要忘记浏览器间的这一差异。这意味着在执行某项操做之前,一般都要先检查nodeType属性
for (var i=0, len = element.childNodes.length; i < len; i++) {
  if (element.childNodes[i].nodeTpe == 1) {
    ...
  }
}
  • 若是想经过某个特性的标签名取得子节点或后代节点,能够经过元素调用getElementsByTagName()方法,结果只会返回当前元素的后代。
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");

Text类型

  • 文本节点由Text类型表示,包含的是能够照字面量解释的纯文本内容。纯文本中能够包含转义后的HTML字符,但不能包含HTML代码。
  • Text节点具备如下特征:

    • nodeType的值为3
    • nodeName的值为'#text'
    • nodeValue的值为节点所包含的文本
    • parentNode是一个Element
    • 不支持(没有)子节点
  • 能够经过nodeValue属性或data属性访问Text节点中包含的文本,这两个属性的值相同。对nodeValue的修改也会经过data反映出来,反之亦然。
  • 使用下列方法能够操做节点中的文本

    • appendData(text):将text添加到节点的末尾
    • deleteData(offset, count):从offset指定的位置插入text
    • insertData(offset, text):在offset指定的位置插入text
    • replaceData(offset, count, text):用text替换从offset指定的位置开始到 offset+count为止处的文本
    • splitText(offset):从offset指定的位置将当前文本节点分红两个文本节点。
    • substringData(offset, count):提取从offset指定的位置开始到 offset+count为止处的字符串
    • length属性:保存着节点中字符的书目。并且nodeValue.lengthdata.length中也保存着一样的数值
  • 在默认状况下,每一个能够包含内容的元素最多只能有一个文本节点,并且必须确实有内容存在
<!-- 没有内容,也就没有文本节点 -->
<div></div>

<!-- 有空格,由于有一个文本节点 -->
<div> </div>

<!-- 有内容,由于有一个文本节点 -->
<div>Hello World!</div>
// 能够像这样取得文本子节点
var textNode= div.firstChild;   // 或者 div.childNodes[0]

// 取得文本节点的引用后,就能够修改它了
div.firstChild.nodeValue = "Some other message";
  • 若是这个文本节点当前存在于文档树中,那么修改文本节点的结果就会当即获得反映。
  • 修改文本节点时,字符串会通过HTML(或XML,取决于文档类型)编码。换言之,小于号、大于号或引号都会像下面的例子同样被转义
div.firstChild.nodeValue = "Some <strong>other</strong> message";
// 输出结果:"Some &lt;strong&gt;other&lt;/strong&gt; message"
  • 这是在向DOM文档中插入文本以前,先对其进行HTML编码的一种有效方式

建立文本节点

  • document.createTextNode()建立新的文本节点。与设置已有文本节点的值同样,做为参数的文本也将按照HTML或XML的格式进行编码。
var textNode = document.createTextNode("<strong>Hello</strong> World!");
  • 在建立新文本节点的同时,也会为其设置ownerDocument属性。不过除非把新节点添加到文档树中已经存在的节点中,不然咱们不会在浏览器窗口中看到新节点。
var element = document.createElement("div");
elment.className = "message";

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

document.body.appendChild(element);
  • 通常状况下,每一个元素只有一个文本子节点。不过在某些状况下也可能包含多个文字子节点。相邻的同胞文本节点,之间会连起来,中间不会有空格。
var element = document.createElement("div");
elment.className = "message";

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

var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);

document.body.appendChild(element);

规范化文本节点

  • DOM文档中存在相邻的同胞文本节点很容易致使混乱,由于分不清文本节点之间的界限。因而催生了一个可以将相邻文本节点合并的方法。
  • normalize()方法是由Node类型定义的(于是在全部节点类型中都存在)。若是在一个包含多个文本节点的父元素上调用normalize()方法,则会将全部文本节点合并成一个文本节点。
var element = document.createElement("div");
elment.className = "message";

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

var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);

document.body.appendChild(element);

console.log(element.childNodes.length);   // 2

element.normalize();
console.log(element.childNodes.length);   // 1
console.log(element.firstChild.nodeValue); // "Hello World!Yippee!"
  • 浏览器在解析文档时永远不会建立相邻的文本节点,这种状况只会做为DOM操做的结果出现。
  • normalize()有时候会致使IE6崩溃,IE7以上修复了此问题。

分割文本节点

  • splitText()方法会将一个文本节点分割成两个。
var element = document.createElement("div");
elment.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类型

  • 注释在DOM中是经过Comment类型来表示的。Comment节点具备如下特征:

    • nodeType的值为8
    • nodeName的值为 "#comment"
    • nodeValue的值是注释的内容
    • parentNode多是Dcoment或Element
    • 不支持(没有)子节点
  • Comment类型与Text类型继承自相同的基类,所以它拥有除splitText()以外的全部字符串操做方法。
<div id="myDiv"><!--A comment--></div>
var div = document.getElementById("myDiv");
var comment = div.firstChild;
console.log(comment.data);                     // "A comment"
  • 使用document.createComment()并为其传递注释文本也能够建立注释节点
var comment = document.createComment("A comment ");
  • 开发人员不多会建立和访问注释节点,此外浏览器也不会识别位于</html>标签后的注释。若是要访问注释节点,必定要保证它们是位于<html></html>之间。

CDATASection类型

  • CDATASection类型只针对基于XML的文档,表示的是CDATA区域。与Comment相似、CDATASection类型继承自Text类型,所以拥有除splitText()以外的全部字符串操做方法。
  • CDATASection节点具备下列特征:

    • nodeType的值为4
    • nodeName的值为"#cdata-section"
    • nodeValue的值是CDATA区域中的内容
    • parentNode多是DocumentElement
    • 不支持(没有)子节点
  • CDATA区域只会出如今XML文档中,所以多数浏览器都会把CDATA区域错误的解析为Comment或Element。
<div id="myDiv"><![CDATA[This is some content.]]></div>
  • 这个例子中div元素应该包含一个CDATASection节点。但四大主流浏览器都不能正确解析。即便对于有效的XHTML页面,浏览器也没有正确的支持嵌入的CDATA区域。
  • 在真正的XML文档中,可使用document.createCDataSection()来建立CDATA区域。

DocumentType类型

  • DocumentType类型在Web浏览器中并不经常使用,仅有 Firefox Safari 和 Opera支持它。

    • nodeType的值为10
    • nodeName的值为doctype的名称
    • nodeValue的值是null
    • parentNodeDocument
    • 不支持(没有)子节点
  • 一般,浏览器中的文档使用的都是HTML或XHTML文档类型,只有name属性是有用的。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
console.log(document.doctype.name);          // "HTML"
  • IE不支持DocumentType,所以 document.doctype的值始终都是null

DocumentFragment 类型

  • 全部节点类型中,只有DocumentFragment在文档中没有对应的标记。
  • DOM规定文档片断(document fragment)是一种轻量级的文档,能够包含和控制节点,但不会像完整的文档那样占用额外的资源。

    • nodeType的值为11
    • nodeName的值为"#document-fragment"
    • nodeValue的值是null
    • parentNodenull
    • 子节点能够是ElementProcessingInstructionCommentTextCDATASectionEntityReference
  • 虽然不能把文档文段直接添加到文档中,但能够将它做为一个仓库来使用,在里面保存未来可能会添加到文档中的节点。
  • document.createDocumentFragment() 方法建立文档片断
<ul id="myList"></ul>
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;

// 若是直接向ul添加li元素会致使浏览器反复渲染
// fragment做为一个元素中转的仓库避免了这个问题
for (var i=0; i < 3; i++) {
  li = document.createElement("li");
  li.appendChild(document.createTextNode("Item " + (i+1)));
  fragment.appendChild(li);
}

// 这里只会将fragment的全部子节点添加到ul上
// 而fragment自己永远不会成为文档树的一部分
ul.appendChild(fragment);

Attr类型

  • 元素的特性在DOM中以Attr类型来表示。在全部浏览器中(包括IE8),均可以访问 Attr类型的构造函数和原型。

    • nodeType的值为2
    • nodeName的值就是特性的名称
    • nodeValue的值就是特性的值
    • parentNodenull
    • HTML中不支持(没有)子节点
    • XML中子节点能够是Text或EntityReference
  • 尽管Attr是节点,但特性却不被认为是DOM文档树的一部分。
  • Attr对象有三个属性:name value specified
  • document.createAttribute()传入特性的名称能够建立新的特性节点。
var attr = document.createAttribute("align");
attr.value = "left";
element.setAttribute(attr);
console.log(element.attributes["align"].value);      // left
console.log(element.getAttributeNode("align").value);      // left
console.log(element.getAttribute("align"));           // left

DOM操做技术

动态脚本

  • 使用<script>元素能够向页面中插入JavaScript代码,一种是经过其src特性包含外部文件,另外一种就是用这个元素自己包含代码。
  • 动态加载的JavaScript文件可以当即运行。
// 在执行最后一行代码把<script>元素添加到页面中以前
// 是不会下载外部文件的
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "client.js";
document.body.appendChild(script);
<script type="text/javascript" src="client.js"></script>
  • 遗憾的是,并无什么标准方式来探知脚本是否加载完成。
  • 从逻辑上讲,使用行内方式直接插入代码是有效的。在Firefox Safari Chrome Opera中,均可以正常运行,但在IE中,会致使错误。IE将<script>视为一个特殊元素,不容许DOM访问其子节点。不过可使用<script>元素的text属性来制定JavaScript代码
var script = document.createElement("script");
script.type = "text/javascript";

// 这样IE不支持
script.appendChild(
  document.createTextNode("function sayHi() { console.log('Hi')}")
);
// 可使用`<script>`元素的text属性来制定JavaScript代码
script.text = "function sayHi() { console.log('Hi')}";

document.body.appendChild(script);
  • Safari3以前的版本不支持这种写法,能够这样作兼容处理
var script = document.createElement("script");
script.type = "text/javascript";
var code = "function sayHi() { console.log('Hi')}"

try {
  script.appendChild(document.createTextNode(code));
} catch (ex) {
  script.text = code;
}

document.body.appendChild(script);
  • 实际上,这样执行代码与在全局做用域中把相同的字符串传递给eval()是同样的。

动态样式

  • 与动态脚本相似,所谓动态样式是指在页面刚加载时不存在的样式;动态样式是在页面加载完成后动态添加到页面中的。
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(head);
<link rel="stylesheet" type="text/css" href="styles.css">
  • 必须将<link>元素添加到<head>而不是<body>元素,才能保证所在浏览器中的行为一致。
  • 加载外部样式文件的过程是异步的,也就是加载样式 与执行JavaScript代码的过程没有固定的次序。
  • 通常是否知道样式已加载完成并不重要,但也存在几种利用事件来检测这个过程是否完成的技术,将在第13章讨论。
  • 行内方式插入样式也是能够的,一样要对IE作兼容处理
function loadStyleString(css) {
  var style = document.createElement("style");
  style.type = "text/css";
  try {
    style.appendChild(document.createTextNode(css));
  } catch (ex) {
    style.styleSheet.cssText = css;
  }
  document.getElementsByTagName("head")[0].appendChild(style);
}
  • 若是专门针对IE编写代码,务必当心使用styleSheet.cssText属性。在重用同一个<style>元素并再次设置这个属性时,有可能致使浏览器崩溃。一样将cssText属性设置为空字符串也可能致使浏览器崩溃。

操做表格

  • <table>元素是HTML中最复杂的结构之一。想要建立表格,通常都必须涉及表示表格行、单元格、表头等方面。因为涉及的标签多,于是使用核心DOM方法建立和修改表格每每都免不了要编写大量的代码。
<table border="1" width="100%">
  <tbody>
    <tr>
      <td>Cell 1,1</td>
      <td>Cell 2,1</td>
    </tr>
    <tr>
      <td>Cell 1,2</td>
      <td>Cell 2,2</td>
    </tr>
  </tbody>
</table>
// 使用核心DOM方法建立这些元素
// 建立table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";

// 建立tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);

// 建立第一行
var row1 = document.createElement("tr");
tbody.appendChild(row1);
var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);

// 建立第二行
var row2 = document.createElement("tr");
tbody.appendChild(row2);
var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
var cell2_2 = document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);

// 将表格添加到文档主体中
document.body.appendChild(table);
  • DOM代码很长,还有点很差理解。为了方便构建表格,HTMLDOM还为<table> <tbody> <tr> 元素添加了一些属性和方法。
  • <table>元素添加的属性和方法:

    • caption: 保存着对<caption>元素(若是有)的指针
    • tBodies: 是一个<tbody>元素的HTMLCollction
    • tFoot: 保存着对<tfoot>元素的(若是有)指针
    • tHead: 保存着对<thead>元素的(若是有)指针
    • rows: 是一个表格中全部行的HTMLCollection
    • createTHead(): 建立<thead>元素,将其放到表格中,返回引用
    • createTFoot(): 建立<tfoot>元素,将其放到表格中,返回引用
    • createCaption(): 建立<caption>元素,将其放到表格中,返回引用
    • deleteTHead(): 删除<thead>元素
    • deleteTFoot(): 删除<tfoot>元素
    • deleteCaption(): 删除<caption>元素
    • deleteRow(pos): 删除指定位置的行
    • insertRow(pos): 向rows集合中的指定位置插入一行
  • <tbody>元素添加的属性和方法以下:

    • rows: 保存着<tbody>元素中行的HTMLCollection
    • deleteRow(pos): 删除指定位置的行
    • insertRow(pos): 向rows集合中的指定位置插入一行
  • <tr>元素添加的属性和方法以下:

    • cells: 保存着<tr>元素中单元格的HTMLCollection
    • deleteCell(pos): 删除指定位置的单元格
    • insertCell(pos): 向cells集合中的指定位置插入一个单元格,返回对新插入单元格的引用。
// 根据以上属性和方法,能够大大简化前述代码

// 建立table
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(0);
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);

使用NodeList

  • 理解 NodeList 及其近亲 NamedNodeMapHTMLCollection,是从总体上透彻理解DOM的关键所在。这三个集合都是动态的,每当文档结构发生变化,它们都会获得更新。
  • 本质上说,全部NodeList对象都是在访问DOM文档实时运行的查询。
// 下列代码会致使无限循环
var divs = document.getElementsByTagName("div");
var div;

// 每次循环都要对条件 i < divs.length 求值
// 但每次循环都添加了一个新的div
for (var i=0; i < divs.length; i++) {
  div = document.createElement("div");
  document.body.appendChild(div);
}
// 最好使用length属性初始化第二个变量
var divs = document.getElementsByTagName("div");
var i, len, div;

// len保存着第一次循环时div的数量,不会随着循环增长
for (i=0, len=divs.length; i < len; i++) {
  div = document.createElement("div");
  document.body.appendChild(div);
}
  • 尽可能减小访问NodeList的次数,由于每次访问都会运行一次基于文档的查询。能够考虑将从NodeList中取得的值缓存起来。
  • 理解DOM的关键就是理解DOM对性能的影响。DOM操做每每是JavaScript程序中开销最大的部分。有鉴于此,最好减小DOM操做。
相关文章
相关标签/搜索