DOM

本章内容:理解包含不同层次节点的DOM、使用不同的节点类型、克服浏览器兼容性问题及各种陷阱;本篇文章主要基于DOM1级进行讲解,DOM2和DOM3后续会提到。

DOM(文档对象模型)时针对于HTML和XML文档的一个API(应用程序接口),DOM描绘了一个层次化的节点树,允许开发人员添加、移除、修改页面的某一部分。DOM脱始于Netscaoe及微软公司创建的DHTML(动态HTML)

一、节点层次

DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构。每个节点都拥有各自的特点,数据和方法,另外也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点未根节点的树形结构。



  Sample page


  

Hello World

每一段标记都可以通过数中的一个节点来表示;HTML元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点表示,注释可以通过注释节点表示。总共又十二种节点类型,这些节点类型都继承自一个基类型。

1.1、Node 类型

DOM1级定义了一个Node接口,由DOM中的所有节点类型实现。除了IE低版本(IE8及以下)外,在其他浏览器中都可以访问这个类型。所有节点类型都继承自Node类型,所有节点类型都共享着相同的基本属性和方法。

节点类型由在Node类型中定义的下列12给数值常量来表示,任何节点类型必居其一:

  • Node.ELEMENT_NODE(1)
  • Node.ATTRIBUTE_NODE(2)
  • Node.TEXT_NODE(3)
  • Node.CDATA_SECTION_NODE(4)
  • Node.ENTTY_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)

可以通过这些常量,可以很容易地确定节点的类型

var someNode = document.getElementsByTagName('div')[0]
if (someNode.nodeType == Node.ELEMENT_NODE) { // IE8及以下 中无效
  // todo
}

由于低版本的IE没有公共Node类型的构造函数,为了确保跨浏览器兼容,最好还是将nodeType属性与数字值进行比较。

if (someNode.nodeType == 1) {
  // todo
}
1.1.1、 nodeName 和 nodeValue 属性

了解节点的 具体信息可以使用,nodeName 和 nodeValue 这两个属性。

if (someNode.nodeType == 1) {
  console.log(someNode.nodeName)
}

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

1.1.2、 节点关系

文档中所有的节点之间都存在这样那样的关系。每个节点都有一个childNodes属性,其中保存着一个NodeList对象。是一种类似数值对象,保存一组有序的节点,可以通过位置来访问这些节点;虽然可以通过方括号语法来访问,而且这个对象也有length属性,但它并不是Array的实例。NodeList对象的独特之处在于、他实际上是基于DOM结构动态执行查询的结构,因此DOM结构的变化能够自动反映在NodeList对象中。

访问NodeList中的节点可以通过方括号或者 item() 方法

var someNode = document.getElementsByTagName('div') // HTMLCollection
var childs = someNode[0].childNodes // NodeList
console.log(childs[0]) // 第一个节点
console.log(childs.item(1)) // 第二个节点
console.log(childs.length) // 长度

方括号语法颇受开发人员的青睐,因为看起来更像访问数组。

前面介绍过,对 arguments 使用 Array.prototype.slice() 方法可以将其转换为数组。而采用同样的方法,也可以将NodeList对象转换为数组

console.log(Object.prototype.toString.call(childs)) //[object NodeList]
var arrayOfNodes = Array.prototype.slice.call(childs, 0)

console.log(Object.prototype.toString.call(arrayOfNodes)) //[object Array]

在IE8及更早版本中将 NodeList 实现为一个 COM对象,要想在IE中将NodeList转换为数组,必须手动枚举所有成员。

function convertToArray(nodes) {
  var array = null
  try {
   array = Array.prototype.slice.call(childs, 0)
  }  catch(e) {
    array = new Array()
    for(var i = 0, len = nodes.length; i < len; i++) {
      array.push(nodes[i])
    }
  }
  return array
}

节点的 parentNode 属性,该属性指向文档树种的父节点。previousSibling 指向 当前节点前面的兄弟节点。nextSibling 指向 当前节点的后面的兄弟节点。列表中第一个previousSiblings的值为null,同样列表中最好一个节点的 nextSiblings也为Null

if (someNode.nextSibling == null) {
  alert('Last node in the parent\'s childNodes list')
} else if (someNode.previousSibling == null) {
  alert('First node in the parent\'s childNodes list')
}

父节点与其第一个和最后一个子节点之间也存在特殊关系。父节点的 firstChildlastChild 分别指向其 childNodes中 第一个和最后一个子节点。其中 firstChild = childNodes[0];lastChild = childNodes[childNodes.length - 1]明确这些关系能够对我们查找和访问文档结构中的节点提高极大的便利。如图 10-2

图 10-2

hasChildNodes()
此外,hasChildNodes() 也是一个非常有用的方法,这个方法在节点包含一或多个子节点的情况下返回

if (someNode.hasChildNodes()) {
  // todo
}

ownerDocument
所有节点都有的最后一个属性是 ownerDocument,该属性指向表示整个文档的文档节点。通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点。

1.1.3、 操作节点

因为关系指针式只读的,DOM提供了一些操作节点的方法。最常用的方法式 appendChild(),用于向 childNodes 列表的末尾添加一个节点。更新完成后,appendChild() 返回新增的节点。

appendChild()

var returnedNode = someNode.appendChild(newNode)
console.log(returnedNode == newNode)
console.log(returnedNode == someNode.lastChild)

如果传入到appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置。

// someNode由多个子节点
var returnedNode = someNode.appendChild(someNode.firestChild)
console.log(returnedNode == someNode.lastChild) // true
console.log(returnedNode == someNode.firstChild)

insertBefore()
如果需要把节点放在 childNodes 列表中某个特定的位置上,可以使用 insertBefore() 方法。接受两个参树:插入的节点、参照的节点。插入的节点会变成参照节点的前一个同胞节点,同时被返回。如果参照节点是 null,则insertBefore() 与 appendChild() 执行相同的操作。

// 参照物为null,插入后称为最后一给子节点
returnedNode = someNode.insertBefore(newNode, null)
console.log(newNode == someNode.lastChild) // true

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

// 插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastNode)
console.log(newNode == someNode.lastNode.previousSibling) // true

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

// 移除第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild)

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

removeChild()
如果指向移除而非替换节点,可以使用 removeChild() 方法。接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。

// 移除第一个节点
var formerFirstChild = someNode.removeChild(someNode.firstNode)

// 移除最后一个节点
formerLastChild = someNode.removeChild(someNode.lastNode)

前面介绍的四个方法操作的都是某个节点的子节点,也就是说,要是有这几个方法必须先取得父节点。另外,比不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用这些方法,将会导致错误发生

1.1.4、 其他方法

有两个是所有类型的节点都有的,他们发别是 cloneNode() 和 normalize()

cloneNode()
姐搜一个布尔值,表示是否执行深复制。深复制:复制节点及整个子节点树;浅复制:即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此,这个节点副本就成为了一个“孤儿”,而非通过appendChild()、insertBefore()、replaceChild()将它添加到文档中。

  • item 1
  • item 2
  • item 3

如果我们已经将

    元素的引用保存在了变量 myList 中

    var deepList = myList.cloneNode(true)
    console.log(deepList.childNodes.length) // 3 (ie < 9) 或 7 (其他浏览器)
    
    deepList = myList.cloneNode(false)
    console.log(deepList.childNodes.length) // 0
    

    normalize()
    这个方法的作用是主力文档树的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况,调用改方法后,当前节点的后代节点如果存在上述的任意一种问题。如果找到空白文本节点,则删除它;如果找到相邻文本节点,则合并它们

    var element = document.createElement('div') // 创建一个根元素
    element.className = 'message'
    
    var textNode = document.createTextNode('Hello') // 创建一个文本节点
    element.appendChild(textNode)
    
    var anotherTextNode = document.createTextNode('World') // 创建第二个文本节点
    element.appendChild(anotherTextNode)
    
    console.log(element.childNodes.length) // 2
    
    element.normalize() // 规范化文本节点
    console.log(element.childNodes.length) // 1
    

    1.2、Document类型

    Javascript 通过 Document 类型表示文档。document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个HTML页面。document对象是window对象的一个属性,因此可以将其作为全局对象来访问。Document具有以下特征:

    • nodeType 的值为 9
    • nodeName 的值为“#document”
    • nodeValue 的值为null
    • parentNode 的值为 null
    • ownerDocument 的值为 null
    • 其子节点可能是一个 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment
    1.2.1、 文档的子节点

    Document节点的子节点由两个内置的访问快捷方式。分别是:documentElement 指向HTML页面中的元素;childNodes 列表访问文档元素,但通过documentElement 属性则能更快捷、更直接地访问该元素。

    
      
    
    

    如上文档结构

    var html = document.documentElement // 取得 的引用
    console.log(html == document.childNodes[0]) // true
    console.log(html == document.firstChild) // true
    

    此外还有一个body 属性,直接指向 元素

    var body = document.body // 获取的引用
    

    所有的浏览器都支持 document.documentElement 和 document.body属性


    Document 另外一个可能的子节点是 DocumentType。通常将标签看成一个与文档其他部分不同的实体,可以通过doctype属性(document.doctype)来访问它的信息。

    console.log(document.doctype) // 
    

    浏览器对document.doctype的支持差别很大,可给出如下总结:

    • IE8 及之前版本:如果存在文档类型声明,将会被错误地解释为一个注释并把它当作 Comment(注释)节点;而document.doctype的值始终未null
    • IE9+ 及 Firefox:如果存在文档类型声明,则将其作为文档的第一个子节点;document.doctype 是一个 DocumentType 节点,也可以通过 document.firstChild 或 document.childNodes[0]来访问同一个节点
    • Safari、Chrome 和 Opera:如果存在文档类型声明,则将其解析,但不作为文档的子节点。document.doctype 是一个 DocumentType 节点,但该节点不会出现在 document.childNodes中。
    
    
      
    
      
    
    
    

    这个页面该有3个子节点:注释、元素、注释。现实中浏览器在处理位于html 外部的注释方面存在如下差异:

    • IE8 及以前版本、Safari3.1及更高版本、Opera 和 Chrome 只为第一条注释创建节点,部位第二条注释创建节点。结果,第一条注释就会成为 document.childNodes 中的第一个子节点。
    • IE9 及更高版本会将第一条注释创建未 document.childNodes 中的一个注释节点,也会将第二条注释创建为 document.childNodes 中的注释子节点。
    • Firefox 以及 Safari3.1 之前的版本会完全忽略这两条注释。

    同样,浏览器的这种不一致性导致了位于元素外部的注释没有什么用处。

    1.2.2、 文档信息

    作为HTMLDocument的一个实例document对象还有一些标准的Document对象所没有的属性。其中一个属性是 title,包含着元素中的文本。</p> </blockquote> <pre><code class="js">// 取得文档标题 console.log(document.title) // 设置文档标题 document.title = 'Happy New Day' </code></pre> <hr> <p>接下来介绍的三个属性都和网页的请求有关:<strong>URL、domain、referrer</strong>,这些信息都存在与HTTP头部。只不过通过这些属性我们可以在JavaScript中进行访问</p> <p><strong>URL</strong><br> 包含页面完整的URL(及地址栏中显示的URL)</p> <pre><code class="js">// 取得完整的 URL var url = document.URL </code></pre> <p><strong>domain</strong><br> domain属性中只包含页面的域名</p> <pre><code class="js">// 取得页面的域名 var domain = document.domain </code></pre> <p><strong>referrer</strong><br> referrer属性中保存着连接到当前页面的那个页面的URL。在没有来源页面的情况下,referrer属性中可能会包含空字符串。</p> <pre><code class="js">// 获取来源页面的URL var referrer = document.referrer </code></pre> <p>这三个属性中只有 domain 是可以设置的。但由于安全方面的限制,也并非可以给domain设置任何值。不能将这个属性设置未URL 中不包含的域。</p> <pre><code class="js">// 假设页面来自 p2p.wrox.com document.domain = "wrox.com" // 成功 document.domain = "nczonline.net" // 失败 </code></pre> <p><code>当页面包含来自其他子域的框架或内嵌框架时,能够设置 document.domain 就非常方便了。由于跨域安全限制,来之不同子域的页面无法通过 JavaScript 通信。通过将每个页面的 document.domain 设置相同的值,这些页面就可以互相访问对象包含的JavaScript对象</code></p> <p>domain还有一个限制,即如果域名一开始是"松散的"(loose),那么不能将它再设置为"紧绷的"(tight)。换句话说,在将 document.domain 设置为 "wrox.com"之后,就不能再将其设置会"p2p.wrox.com"</p> <pre><code class="js">// 假设页面来自于 p2p.wrox.com域 document.domain = "wrox.com" // 松散的(成功) document.domain = "p2p.wrox.com" // 紧绷的(失败) </code></pre> <p>所有浏览器中都存在这个限制,但IE8是实现这一限制的最早的IE版本</p> <h5>1.2.3、 查找元素</h5> <blockquote> <p>说到最常用的DOM应用,恐怕就要数取得特定的某个或某组元素的引用,然后再执行一些操作了。Document类型唯一提供了两个方法:</p> </blockquote> <p><strong>getElementById()</strong><br> 接受一个参数:要去的的元素的ID。如果不存在则返回null。这里的ID必须也页面中元素的id特性(attribute)严格匹配,包括大小写。如果多个ID相同,则返回 文档中第一次出现的元素。</p> <pre><code class="js">// 获取一个 id=container 的DOM元素 const container = document.getElementById('container') </code></pre> <p><code>在IE7 及 较低版本还有一个怪癖:name特性与给定 ID 匹配的表单元素 (<input>、<textarea>、<button>、<select>)也会被改方法返回。如果有哪个表单元素的name属性等于指定的ID,而且改元素在文档中位于带有给定ID的元素前面,那么IE就会返回那个表单元素。</code></p> <pre><code class="html"><input type="text" name="myElement" value="Text field" /> <div id="myElement">A div</div> <!-- 在IE7中,使用 getElementById('myElement') 会返回 input 元素--> </code></pre> <p><strong>getElementsByTagName()</strong><br> 接受一个参数,既要取得元素的标签名,返回的是包含零或多个元素的 NodeList。在HTML文档中,这个方法会返回一个 HTMLCollection 对象,作为一个“动态”集合,该对象与NodeList非常类似。</p> <pre><code class="js">var images = document.getElementsByTagName('img') </code></pre> <p>与NodeList 对象类似,则可以使用方括号或item()方法来访问 HTMLCollection 对象中的项。长度可以通过 length 属性来获取</p> <pre><code class="js">console.log(images.length) console.log(images[0] == images.item(0)) // true </code></pre> <p>HTMLCollection 对象还有一个方法,叫做 <strong>namedItem()</strong>,使用这个方法可以通过元素的 name 特性取得集合中的项。</p> <pre><code class="js">images.nameItem('importantImg') // 或去images 集合中,name属性未 importantImg的 元素 </code></pre> <p>也可以使用方括号来表示按 name 值 访问</p> <pre><code class="js">images['importantImg'] </code></pre> <p><code>如果项获取文档中的所有元素,可以通过 getElementsByTagName(“*”) 来获取。</code></p> <pre><code class="js">// 获取所有元素 var allElements = document.getElementsByTagName("*") </code></pre> <p><code>由于IE(8及以下)将注释(Comment)实现为元素 (ELement),因此在IE中调用 会返回所有的注释节点。</code></p> <p><strong>getElementsByName()</strong><br> 这个方法是 HTMLDocument才有的。返回符合给定 name 值的所有元素;常用于获取单选按钮</p> <pre><code class="js">// 获取 所有name 为 habit 的单选按钮 var allRadio = document.getElementsByName('habit') </code></pre> <h5>1.2.4、 特殊集合</h5> <p>除了属性和方法,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式:</p> <ul> <li>document.anchors:包含文档中所有带name 特性的 <a> 元素</li> <li>document.forms:包含文档中所有的<form>元素,与document.getElementsByTagName('form') 所得到的结果一致</li> <li>document.images: 包含文档所有的<img> 元素</li> <li>document.links:包含文档中所有带 href 特性的 <a> 元素</li> </ul> <p>这个特殊集合始终可以通过HTMLDocument 对象访问到,而且,与HTMLCollection对象类似,集合中的项也会随着当前文档的内容而跟新。</p> <h5>1.2.5、 DOM一致性检测</h5> <p>由于DOM分为多个级别,<strong>document.implementation</strong>为此提供相应信息和功能的对象,与浏览器对DOM的实现直接对应。<code>DOM1级只为document.implementaion 规定了一个方法</code><br> <strong>hasFeature(feature, version)</strong><br> 接受两个参数:要检测的DOM功能、版本号。如果浏览器支持给定名称和版本的功能,返回 true</p> <pre><code class="js">var hasXmlDOm = document.impementation.hasFeature('XML', '1.0') </code></pre> <p>可供检测的其他值及版本号</p> <p>多数情况下,在使用DOM的某些特殊功能之前,最好除了检测 hasFeature() 之外,还同时使用能力检测。</p> <h5>1.2.6、 文档写入</h5> <p>输出流写入到网页中,提供4个方法:<br> write() 、writeln()、open()、close()</p> <p>write() 和 writeln() 方法都接受一个字符串参数,即要写入到输出流中的文本。write() 会原样写入,writeln()则会在 末尾添加一个 换行符(\n)</p> <pre><code class="html"><html> <head> <title>example

    如果在文档加载结束后在调用document.write(),那么输出的呢人将会重写整个页面。如下:

    window.onload = function() {
      document.write('onload')
    }
    

    方法 open() 和 close() 分别用于打开和关闭网页的输出流。如果是在页面加载期间使用 write() 或 writeln() 方法,则不需要用到这两个方法。

    1.3、Element 类型

    Element类型是用于XML或HTML 元素,提供了对元素标签名、子节点 及 特性的访问。具有以下特征。

    • nodeType 的值为1
    • nodeNmae 的值为元素的标签名
    • nodeValue 的值为null
    • parentNode 可能是 Document 或 Element
    • 其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference

    要法官问元素的标签名,可以使用 nodeName属性,也可以使用 tagName属性:会返回相同的值

    // 
    var element = document.getElementById('div') // 获取一个 id 为 div 的元素 console.log(element.nodeName) // DIV console.log(element.tagName == element.nodeName) // true

    在HTML中标签名始终都以全部大写展示:而在XML(有时候也包括XHTML)中,标签名始终会与源代码中的保持一致。加入你不确定自己的脚本将会在HTML还是XML文档中执行,最好是在比较之前将标签名转换为相同的大小写形式。

    if (element.tagName.toLowerCase() === 'div') { (始运用任何文档)
      // todo
    }
    
    1.3.1、 HTML 元素

    所有HTML元素都由 HTMLElement 类型表示,通过它的子类型来表示。HTMLElement类型直接继承自 Element 并添加了一些属性。

    • id,元素在文档中的唯一标识符
    • title,有关元素的附加说明信息,一般通过工具提示条显示出来。
    • lang,元素内容的语言代码,很少使用。
    • dir,语言方向,值为 ”ltr“(left to right) 或 ”rtl“(right to left),也很少使用
    • className,与元素的class特性对应,即为元素指定的CSS类。
      上述属性都是可以用来取得或修改相应的特性值。

    上述的属性 也可以通过 JavaScript 获取 或 修改

    var div = document.getElementByIId('id')
    
    console.log(div.id) // myDiv
    div.id = 'otherId'
    
    1.3.2、 取得特性

    操作特性的DOM方法主要有三个:getAttribute()、setAttribute()、removeAttribute()。这三个方法可以争对任何特性使用,包括那些以 HTMLElement类型属性的定义的特性。
    getAttribute(attr)
    接受一个参数:要获取的特性名。传递的特性名必须于实际的特性名相同。如果给定名称的特性不存在,则返回 null。这方法也可以取得自定义特性。

    
    
    var div = document.getElementById('div')
    
    console.log(div.id) // div
    console.log(div.dataWidth) // undefined (IE8及以下 除外,有效值)
    console.log(div.getAttribute('data-width')) // 200
    

    因为id在HTML中式公认特性,因此改元素的DOM对象中也将存在对应的属性,自定义特性 data-width 在 Safari、Opera、Chrome、Firefox中是不存在的;但IE却会为自定义属性也创建属性。


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

    第一类特性是 style,返回style特性值中包含的是 CSS 文本,而通过属性来访问则会返回一个对象。

        
    var div = document.getElementById('div')
    
    console.log(div.style) // 返回对象 CSSStyleDeclaration
    console.log(div.getAttribute('style')) // 返回字符串 width:200px;height:200px;background:cyan;
    

    第二类特性是onclick这类的事件处理程序,通过 getAttribute() 访问会返回相应的代码字符串。而在访问 onclick 属性是,则会返回一个 JavaScript 函数(如果未知的则返回 null)

    var div = document.getElementById('div')
    
    console.log(div.onclick) // 返回函数 ƒ onclick(event) { alert('morning') }
    console.log(div.getAttribute('onclick')) // 返回字符串  alert('morning')
    

    由于这些差别的存在,经常使用 属性 来获取,只有在取得自定义特性值的情况下,才会使用到getAttribute() 方法。

    1.3.3、设置特性

    与 getAttribute() 对应的方法是 setAttribute()

    setAttribute(attr, value)
    这个方法接受两个参数:设置的特性名、值。
    如果设置的特性值已存在,则会被指定的值替换;如果不存在则会创建该属性并设置相应的值。
    通过setAttribute()方法既可以操作HTML特性也可以操作自定义特性。通过这个方法设置的特性名会被统一转换为小写形式,即”ID“最终会变成”id“

    document.getElementById('div').setAttribute('data-width', '600')
    

    removeAttribute(attr)
    接受一个参数:要删除的特性。
    这个方法用于彻底删除元素的特性,不久会清除特性的值,也会从元素中完全删除特性。

    document.getElementById('div').removeAttribute('class')
    

    这个方法并不常用,但在序列化DOM元素时,可以通过它来确定的指定要包含哪些特性。IE6及以前版本不支持。

    1.3.4、 attributes 属性

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

    • getNamedItem(name):返回nodeName属性等于 name的节点
    • removeNamedItem(name):从列表中移除 nodeName 属性等于 name 的节点
    • setNamedItem(Node):向列表中添加节点,以节点的nodeName属性为索引。
    • item(pos):返回位于数字pos位置处的节点。

    attributes 属性中包含一系列节点,每个节点的nodeName 就是特性的名称,而节点的nodeValue 就是特性的值。比如得元素的id特性。
    getNameItem(name)

    var id = element.attributes.getNamedItem('id').nodeValue
    // 使用方括号的简写方式
    id = element.attriutes['id'].nodeValue
    

    也可以通过这种方式来进行设置值

    element.attributes['id'].nodeValue = 'someOtherId'
    

    removeNamedItem(name)
    这个方法与在元素上调用 removeAttribute() 方法的效果相同。唯一的区别是 removeNamedItem()返回表示被删除特性的Attr节点

    var oldAttr = element.attributes.removeNamedItem('id') // id="xxx"
    

    setNamedItem(node)
    这个方法是一个不常用的方法,通过这个方法可以为元素添加一个新特性。

    element.attributes.setNamedItem(newAttr)
    

    一般来说,前面介绍的 attributes 的方法不够方便,因此开发人员更多的会使用 getAttribute()、setAttribute()、removeAttribute()方法

    `不过在遍历元素的特性的时候,attributes属性倒是可以派上用处。

    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(" ")
    }
    

    关于以上代码的运行结果,有以下两点必要的说明。

    • 针对 attributes 对象中的特性,不同浏览器的顺序不同
    • IE7 及更早的版本会返回HTML 元素中所有的可能性,包括没有指定的特性。

    可以对以上函数进行改进,让它只返回被指定的特性,每个特性节点都有一个名为 specified的属性,如果这个值为 true,意味着 在 HTML中指定了相应的 特性,要么是通过 setAttribute() 方法设置了该特性。在IE中,所有未设置国的特性的该属性都为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.attribues[i].specified) pairs.push(attrName + '="' + attrValue + '"')
      }
      return pairs.join(" ")
    }
    

    经过 specified 属性的判断,这个函数只返回指定的特性

    1.3.5、 创建元素

    document.createElement(tagName)
    通过这个方法可以创建新元素:接受一个参数,即要创建元素的标签名。标签名在HTML文档中不区分大小写,但是在XML文档中区分大小写。

    var div = document.createElement('div')
    

    在创建新元素的同时,也会为新元素设置了 ownerDocument属性(指向整个文档节点 #docment)。此时还可以操作元素的特性,为他添加子节点等。

    div.id = 'myNewDiv'
    div.className = 'box'
    

    要把新创建的元素添加到文档树,可以使用 appendChild()、insertBefore()、replaceChild()方法

    document.body.appendChild(div)
    

    在IE(8及以下)中可以以另一种方式使用 createElement(),即为这个方法传入完整的元素标签,也可以包含属性:

    var div = document.createElement('
    ')

    这种方式有助于避开在IE7及更早版本中动态创建元素的某些问题。如下:

    • 不能设置动态创建的') // 创建 input 元素 var input = document.createElement('') // 创建button 元素 var button = document.createElement('') // 创建单选按钮 var radio1 = document.createElement('') var radio2 = document.createElement('') }

      与使用 createElement() 的惯常方式一样,会返回一个DOM元素的引用。可以将这个元素添加到文档中,也可以对齐增强。

      1.3.6、 元素的子节点

      元素可以由任意数目的子节点和后代节点,这些子节点有可能是元素、文本节点、注释、处理指令。不同浏览器在看待这些节点方面存在显著的不同。如下:

      • item 1
      • item 2
      • item 3

      如果是 IE来解析这些代码,那么

        将会有3个子节点,分别是3个
      • 元素。
        如果是其他浏览器,则会有7给元素,包括3个
      • 元素和4个文本节点(每个
      • 之间的空白符)。如果像下面这样将元素间的空白符删除,那么所有浏览器都会返回相同数目的子节点。

        • item 1
        • item 2
        • item 3

        如果需要通过childNodes 属性遍历子节点,那么一定不要忘记浏览器间的这一差别。通常都要先检查 nodeType 属性。如下:

        for (var i = 0, len = element.childNodes.length; i < len; i++){
          if (element.childNodes[i].nodeType == 1) { // 元素节点 Elmondayement类型
            // todo
          }
        }
        

        1.4、Text类型

        文本节点由Text类型表示,包含的是可以照字面解释的村文本内容。纯文本可以包换转义后的HTML字符,但不能包含HTML代码。Text节点具有以下特征:

        • nodeType 的值为 3;
        • nodeName 的值为”#text“
        • nodeValue 的值为节点所包含的文本
        • parentNode 是一个Element
        • appendData(text):将 text 添加到节点的末尾
        • deleteData(offset, count): 从offset指定位置开始删除 count给字符
        • insertData(offset, text):substringDa在offset指定的位置插入 text
        • replaceData(offset, count, text):用text 替换从 offset 指定的位置开始到 offset+count 为止处的文本
        • splitText(offset):从offset指定的位置将当前文本节点分成两个文本节点。
        • substringData(offset, count):提取从 offset 指定的位置开始到 offset + count 为止处的字符串。

        除了上述的这些,文本节点还有一个length 属性。

        在默认情况下,每个可以包含内容的元素最多只能有一个文本解节点,而且必须确实有内容存在。

        
        
        Hello World

        在取得文本节点的引用后,可以像下面这样来修改它

        var textNode = divv.firstChild // 或者 div.childNodes[0]
        
        // 修改节点
        textNode.nodeValue = 'Some other message'
        

        如果这个文本节点当前存在于文本树中,那么修改文本节点的结果就会立即得到反映。该文本节点时,字符串会经过HTML(或XML,取决于文档类型)编码。换句话说,大于号、小于号或引号会被转义。

        1.4.1、 创建文本节点

        document.createTextNode(text)
        可以使用 document.createTextNode()创建新文本节点,这个方法接受一个参数——要插入节点中的文本。

        var textNode = document.createTextNode('Hello World') 
        
        1.4.2、 规范文本节点

        normalize()
        前面提到过这个方法,DOM文档中存在相邻的同胞文本节点很容易导致混乱,于是催生了一个能够即将相邻文本节点合并的方法。在一个包含两个或多个文本节点的父元素上调用 normalize() 方法,则会将所有文本节点合并成一个节点。

        浏览器在解析文档时永远不会创建相邻的文本节点,这种情况只会作为操作DOM操作的结果出现。

        1.4.3、 分割文本节点

        splitText(pos)
        Text类型提供了一个作用于 normalize() 相反的方法: splitText()。将一个文本节点分成两个文本节点,即按照指定的位置分割 nodeValue 值。该方法返回指定分隔符之后剩余的文本节点。

        var div = document.getElementById('div')
        var textNode = div.firstChild // Hello World
        console.log(div.childNodes.length) // 1
        
        var newNode = textNode.splitText(5) 
        console.log(div.childNodes.length) // 2
        
        console.log(div.firstChild) // Hello
        console.log(newNode) // World
        

        1.5、 Comment 类型

        注释在DOM中式通过 Comment 类型来表示的。Comment节点具有以下特征。

        • nodeType 的值为8
        • nodeName 的值为‘#comment’
        • nodeValue 的值是注释的内容
        • parentNode 可能是 Document 或 Element
        • 不支持(没有) 子节点

        Comment 类型 于 Tetx 类型继承自相同的基类,因此它们除 splitText() 之外的所有字符串操作方法。于Text类型相似,也可以通过 nodeValue 或 data 属性来取得注释的内容。注释节点可以通过父节点来访问。

        通过以下代码 来访问注释节点

        var div = document.getElementById('div')
        var comment = div.firstChild
        // A Comment  
        console.log(comment.nodeValue) // 或者 comment.data
        

        此外使用 document.createComment() 并为其传递注释文本也可以创建注释节点。


        一般来说,很少会创建和访问注释节点,因为注释节点对算法鲜有影响。此外,浏览器也不会识别位于标签后面的注释。

        1.6、 CDATASection 类型

        CDATASection 类型只针对基于XML的文档,表示的是 CDATA 区域。与Comment类似,CDATASection 类型继承自 Text 类型,因此拥有除 splitText() 之外的所有字符串操作方法。CDATASection 节点具有以下特征。

        • nodeType 的值为4
        • nodeName 的值为 ”#cdata-section“
        • nodeValue 的值是 CDATA 区域中的内容
        • parentNode 可能是 Document 或 Element
        • 不自持(没有)子节点

        CDATA区域只会出现在XML文档中,因此多数浏览器会把CDATA区域错误地解析为 Comment 或 Element。

        这个例子中,

        元素 应该包含一个 CDATASection 节点。可是,四大主流浏览器无一能够这样解析它。即使对于有效的XHTML 页面,浏览器也没有正确地支持嵌入式的CDATA区域。
        在真正的XML文档中,可以使用document.createDataSection() 来创建CDATA区域,只需要为其传入节点内容即可

        1.7、 DocumentType 类型

        DocumentType类型在Web浏览器中比不常用,Firefox、Safari、Opera、Chrome4.0+、IE9+支持这个类型,它具有以下特征:

        • nodeType 的值为 10
        • nodeName 的值为doctype的名称
        • nodeValue 的值为 null
        • parentNode 是Document
        • 不支持(没有)子节点。

        DOM1级描述了 DocumentType对象的三个属性:name、entities、notations。
        name 表示文档的名称;entities 是由文档类型描述的实体的 NamedNodeMap对象;notations 是由文档类型描述的符号的 NamedNodeMap对象

        以 h5 的文档类型声明为例:

        
        

        DocumentType 的name 属性中 保存的就是 HTML

        console.log(DocumentType.name) // html
        

        IE8及更早版本不支持 DocumentType,document.doctype 的值为null

        1.8、 DocumentFragment

        在所以的节点类型中,只有DocumentFragment 在文档中没有对于的标记。DOM规定文档片段(document frafment)是一种”轻量级“的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment 节点具有下列特征。

        • nodeType 的值为11
        • nodeName 的值为 ”#document-fragment“
        • nodeValue 的值为null
        • parentNode 的值为 null
        • 子节点可以是 Element、ProcessingInstruction、Comment、Text、CDATASection、EntityReference。

        需要注意的是:不能把文档片段直接添加到文档中去,可以将它作为一个”仓库“来使用,创建文档片段,可以使用 document.createDocumentFragment() 方法
        文档片段继承了 Node 的所有方法,同样用于指向那么针对文档的DOM操作。可以通过appendChild() 和 insertBefore() 将文档片段中内容添加到文档中。将文档片段作为参数传递给这两个方法时,只会将文档片段的所有子节点添加到相应的位置上;文档片段本身永远不会成为文档树的一部分。

          假设我们想为这个

            元素添加3给列表项,如果逐个添加,会导致浏览器反复渲染(降低性能)。可以像下面这样使用一个文档片段来保存创建的列表项,然后再一次性将他们添加到文档中

            var fragment = document.createDocumentFragment()
            var ul = document.getElementById('list')
            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)
            

            1.9、 Attr 类型

            元素的特性再DOM中以 Attr 类型表示。特性就是存在于 元素的 attributes 属性中的节点。特性节点具有下列特征:

            • nodeType 的值为2
            • nodeName 的值是特性的名称
            • nodeValue 的值是特性的值
            • parentNode 的值为null
            • 在HTML 中不支持(没有)子节点
            • 在 XML 中子节点可以是 Text 或 EntityReference

            尽管它们也是节点,但特性却不被认为是 DOM 文档树的一部分。开发人员最常用的是 getAttribute()、setAttribute() 和 removeAttribute()方法,很少直接引用特性节点。


            Attr对象有3个属性name、value、specified;其中,name是特性名称(与 nodeName 的值相同); value 是特性的值(与 nodeValue 的值相同);specified 是一个布尔值,用以区别特性是在代码中指定的,还是默认的。

            使用 document.createAttribute(attr) 传入特性的名称可以创建新的特性节点。如下:

            // 创建一个 size 特性节点
            var size = document.createAttribute('size')
            size.value = '20' // 赋值
            // 添加到文档中
            document.getElementById('div').setAttributeNode(size)
            
            console.log(document.getElementById('div').attributes['size'].nodeValue) // 20
            

            将新创建的特性添加到 元素中,必须使用元素的 setAttributeNode(AttrNode)

            二、DOM 操作技术

            很多时候,DOM操作都比较间明,因此用JavaScript生成那些通常原本是用来HTML代码生成的内容不麻烦。不过,由于浏览器中充斥着隐藏的陷阱和不兼容的问题,用JavaScript处理DOM的某些部分要比处理其他部分更复杂一些。

            2.1、 动态脚本

            指在页面加载是不存在,但将来的某一时刻通过修改DOM动态脚本。跟操作HTML元素一样,创建动态脚本也有两种方式:插入外部文件、直接插入JavaScript代码。

            插入外部文件

            function loadScript(url) {
              var script = document.createElement('script')
              script.type = 'text/javascript'
              script.src = url
              document.body.appendChild(script)
            }
            
            // 
            loadScript('./client.js')
            

            行内方式

            var script = document.createElement('script')
            script.type = 'text/javascript'
            script.appendChild(document.createTextNode('alert("hi")'))
            document.body.appendChild(script)
            

            在IE(8及以下版本中)上述代码会导致错误。IE会将

    你可能感兴趣的:(DOM)