DOM

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

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

一、节点层次

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

<html>
<head>
  <title>Sample page</title>
</head>
<body>
  <p>Hello World</p>
</body>
</html>

每一段标记都可以通过数中的一个节点来表示;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()将它添加到文档中。

<ul>
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
</ul>

如果我们已经将<ul>元素的引用保存在了变量 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页面中的<html>元素;childNodes 列表访问文档元素,但通过documentElement 属性则能更快捷、更直接地访问该元素。

<html>
  <body></body>
</html>

如上文档结构

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

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

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

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


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

console.log(document.doctype) // <!doctype html>

浏览器对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中。
<!-- 第一条注释 -->
<html>
  <body>

  </body>
</html>
<!-- 第二条注释 -->

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

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

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

1.2.2、 文档信息

作为HTMLDocument的一个实例document对象还有一些标准的Document对象所没有的属性。其中一个属性是 title,包含着<title>元素中的文本。

// 取得文档标题
console.log(document.title)

// 设置文档标题
document.title = 'Happy New Day'

接下来介绍的三个属性都和网页的请求有关:URL、domain、referrer,这些信息都存在与HTTP头部。只不过通过这些属性我们可以在JavaScript中进行访问

URL
包含页面完整的URL(及地址栏中显示的URL)

// 取得完整的 URL
var url = document.URL

domain
domain属性中只包含页面的域名

// 取得页面的域名
var domain = document.domain

referrer
referrer属性中保存着连接到当前页面的那个页面的URL。在没有来源页面的情况下,referrer属性中可能会包含空字符串。

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

这三个属性中只有 domain 是可以设置的。但由于安全方面的限制,也并非可以给domain设置任何值。不能将这个属性设置未URL 中不包含的域。

// 假设页面来自 p2p.wrox.com

document.domain = "wrox.com" // 成功
document.domain = "nczonline.net" // 失败

当页面包含来自其他子域的框架或内嵌框架时,能够设置 document.domain 就非常方便了。由于跨域安全限制,来之不同子域的页面无法通过 JavaScript 通信。通过将每个页面的 document.domain 设置相同的值,这些页面就可以互相访问对象包含的JavaScript对象

domain还有一个限制,即如果域名一开始是"松散的"(loose),那么不能将它再设置为"紧绷的"(tight)。换句话说,在将 document.domain 设置为 "wrox.com"之后,就不能再将其设置会"p2p.wrox.com"

// 假设页面来自于 p2p.wrox.com域
document.domain = "wrox.com" // 松散的(成功)
document.domain = "p2p.wrox.com" // 紧绷的(失败)

所有浏览器中都存在这个限制,但IE8是实现这一限制的最早的IE版本

1.2.3、 查找元素

说到最常用的DOM应用,恐怕就要数取得特定的某个或某组元素的引用,然后再执行一些操作了。Document类型唯一提供了两个方法:

getElementById()
接受一个参数:要去的的元素的ID。如果不存在则返回null。这里的ID必须也页面中元素的id特性(attribute)严格匹配,包括大小写。如果多个ID相同,则返回 文档中第一次出现的元素。

// 获取一个 id=container 的DOM元素
const container = document.getElementById('container')

在IE7 及 较低版本还有一个怪癖:name特性与给定 ID 匹配的表单元素 (<input>、<textarea>、<button>、<select>)也会被改方法返回。如果有哪个表单元素的name属性等于指定的ID,而且改元素在文档中位于带有给定ID的元素前面,那么IE就会返回那个表单元素。

<input type="text" name="myElement" value="Text field" />
<div id="myElement">A div</div>
<!-- 在IE7中,使用 getElementById('myElement') 会返回 input 元素-->

getElementsByTagName()
接受一个参数,既要取得元素的标签名,返回的是包含零或多个元素的 NodeList。在HTML文档中,这个方法会返回一个 HTMLCollection 对象,作为一个“动态”集合,该对象与NodeList非常类似。

var images = document.getElementsByTagName('img')

与NodeList 对象类似,则可以使用方括号或item()方法来访问 HTMLCollection 对象中的项。长度可以通过 length 属性来获取

console.log(images.length)
console.log(images[0] == images.item(0)) // true

HTMLCollection 对象还有一个方法,叫做 namedItem(),使用这个方法可以通过元素的 name 特性取得集合中的项。

images.nameItem('importantImg') // 或去images 集合中,name属性未 importantImg的 元素

也可以使用方括号来表示按 name 值 访问

images['importantImg']

如果项获取文档中的所有元素,可以通过 getElementsByTagName(“*”) 来获取。

// 获取所有元素
var allElements = document.getElementsByTagName("*")

由于IE(8及以下)将注释(Comment)实现为元素 (ELement),因此在IE中调用 会返回所有的注释节点。

getElementsByName()
这个方法是 HTMLDocument才有的。返回符合给定 name 值的所有元素;常用于获取单选按钮

// 获取 所有name 为 habit 的单选按钮
var allRadio = document.getElementsByName('habit')
1.2.4、 特殊集合

除了属性和方法,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式:

  • document.anchors:包含文档中所有带name 特性的 <a> 元素
  • document.forms:包含文档中所有的<form>元素,与document.getElementsByTagName('form') 所得到的结果一致
  • document.images: 包含文档所有的<img> 元素
  • document.links:包含文档中所有带 href 特性的 <a> 元素

这个特殊集合始终可以通过HTMLDocument 对象访问到,而且,与HTMLCollection对象类似,集合中的项也会随着当前文档的内容而跟新。

1.2.5、 DOM一致性检测

由于DOM分为多个级别,document.implementation为此提供相应信息和功能的对象,与浏览器对DOM的实现直接对应。DOM1级只为document.implementaion 规定了一个方法
hasFeature(feature, version)
接受两个参数:要检测的DOM功能、版本号。如果浏览器支持给定名称和版本的功能,返回 true

var hasXmlDOm = document.impementation.hasFeature('XML', '1.0')

可供检测的其他值及版本号

多数情况下,在使用DOM的某些特殊功能之前,最好除了检测 hasFeature() 之外,还同时使用能力检测。

1.2.6、 文档写入

输出流写入到网页中,提供4个方法:
write() 、writeln()、open()、close()

write() 和 writeln() 方法都接受一个字符串参数,即要写入到输出流中的文本。write() 会原样写入,writeln()则会在 末尾添加一个 换行符(\n)

<html>
  <head>
    <title>example</title>
  </head>
  <body>
  
    <script>
      document.write("<script type=\"text/javascript\" src=\"./client.js\"><\/script>")
    </script>
  </body>
</html>

如果在文档加载结束后在调用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属性:会返回相同的值

// <div id="div"></div>
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类。
    上述属性都是可以用来取得或修改相应的特性值。
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

上述的属性 也可以通过 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。这方法也可以取得自定义特性。

<!-- 根据 h5 规范 自定义属性由 data-开头 -->
<div id="div" name="dv" data-width="200">
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 文本,而通过属性来访问则会返回一个对象。

    <div id="div" name="dv" data-width="200" style="width:200px;height:200px;background:cyan;">
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)

<div id="div" onclick="alert('morning')">
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('<div id="myDiv" class="box"></div>')

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

  • 不能设置动态创建的<iframe>元素的name特性
  • 不能通过表单的reset()方法重设动态创建的<input>元素
  • 动态创建的type特性值为”reset“的 <button>元素重设不了表单
  • 动态创建的一批name相同的单选按钮彼此毫无关系。name值相同的一组单选按钮本来应该用于表示同一选项的不同值,但动态创建的一批这种单选按钮之间却没有这种关系。
    上述的这些问题,皆可以通过在 createElement() 中指定完整的HTML标签来解决
if (client.browser.ie && client.browser.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">')
  var radio2 = document.createElement('<input type="radio" name="choice">')
}

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

1.3.6、 元素的子节点

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

<ul>
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
</ul>

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

<ul><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].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 属性。

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

<!-- 没有内容,也就没有文本节点 -->
<div></div>

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

<!-- 有内容,因而有一个文本节点 -->
<div>Hello World</div>

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

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('<strong>Hello</strong> 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 属性来取得注释的内容。注释节点可以通过父节点来访问。

<div id="div"><!-- A Comment --></div>

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

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

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


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

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。

<div id="div"><! [CDATA[This is some conent .]]></div>

这个例子中,<div>元素 应该包含一个 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 的文档类型声明为例:

<!DOCTYPE html>

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() 将文档片段中内容添加到文档中。将文档片段作为参数传递给这两个方法时,只会将文档片段的所有子节点添加到相应的位置上;文档片段本身永远不会成为文档树的一部分。

<ul id="list"></ul>

假设我们想为这个 <ul> 元素添加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会将<script>视为一个特殊的元素,不允许DOM访问其子节点。不过可以使用 <script>元素的 text属性来指定 JavaScript 代码。

script.text = 'alert("hi")'

然而在 Safari3.0 之前却不支持 text 属性。如下兼容写法

function loadScriptString(code) {
  var script = document.createElement('script')
  script.type = 'text/javascript'
  try {
    script.appendChild(document.createTextNode(code))
  }catch(e) {
    script.text = code
  }
  document.body.appendChild(script)
}
loadScriptString('alert("Hi")')

以这种方式加载的代码会在全局作用域中指向,而且当脚本执行后立即可用。实际上,这样执行代码与在全局作用域中把相同的字符串传递给 eval() 是一样的。

2.2、动态样式

能够把 CSS 样式包含到 HTML 页面中的元素有两个。其中,<link>元素用于包含来自外部的文件;而 <style>元素用于指定潜入的样式。

创建<link>

function loadStyles(url) {
  var link = document.createElement('link')
  link.rel = 'stylesheet'
  link.type = 'text/css'
  link.href = url
  document.getElementsByTagName('head')[0].appendChild(link)
}

loadstyles('./style.css')

使用<style>元素来包含嵌入式的 CSS
与<script> 标签类似,在IE(8及以下)中会将<style>视为一个特殊的、与<script>类似的节点,不允许访问其子节点。可以通过 访问元素 的 styleSheet 属性的 cssText 属性,赋值 css 代码。以下是兼容写法

function loadStylesString(css) {
  var style = document.createElement('style')
  style.type = 'text/css'
  try {
    style.appendChild(document.createTextNode(css))
  } catch(e) {
    style.styleSheet.cssText = css
  }
document.getElementsByTagName('head')[0].appendChild(style)
}

loadStylesString('body{background: red;}')

2.3 操作表格

<table>元素是 HTML中最复杂的结构之一。相较于使用DOM核心方法动态创建表格的方式(createElement、createTextNode、appendChild这些Api)。HTMLDOM为 <table>、<tbody>、<tr>元素添加了一些属性和方法。


<table>元素的属性和方法:

  • caption:保存在对<caption>元素(如果有)的指针
  • tBodies:是一个<tbody>元素的HTMLCollection
  • tFoot:是一个<tfoot>元素(如果有)的指针
  • thead:是一个<thead>元素(如果有)的指针
  • rows:是一个表格中所有行的HTMLCollection。
  • createTHead():创建<thead>元素,将其放到表格中,返回引用
  • createTFoot():创建<tfoot>元素,将其放到表格中,返回引用
  • createCaption():创建<caption>元素,将其放到表格中,返回引用
  • deleteTHead():删除<thead>元素
  • deleteTFoot():删除<tfoot>元素
  • deleteCaption():删除<caption>元素
  • deleteRow(pos):删除指定行
  • insertRow(pos):向 rows 集合中的指定位置插入一行,返回对新行插入行的引用。

为<tr>元素添加的属性和方法如下:

  • cells:保存在<tr>元素中单元格的 HTMLCollection
  • deleteCell(pos):删除指定位置的单元格
  • insertCell(pos):向cells集合中的指定位置插入一个单元格,返回新单元格的引用。
<table border="1" width="100%">
  <tbody>
    <tr>
      <td>Cell 1,1 </td>
      <td>Cell 1,2 </td>
    </tr>
    <tr>
      <td>Cell 1,2</td>
      <td>Cell 2,2</td>
    </tr>
  </tbody>
</table>

创建如上表格

// 创建 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 1,2'))

// 创建第二行 
tbody.insertRow(1) // 指定位置 插入行
tbody.rows[1].insertCell(0) // 指定位置插入单元格
tbody.rows[1].cells[0].appendChild(document.createTextNode('Cell 1,1'))
tbody.rows[1].insertCell(1) // 插入第二个单元格
tbody.rows[1].cells[1].appendChild(document.createTextNode('cell 1,2'))

// 将表格添加到文档中
document.body.appendChild(table)

使用这些属性和方法创建表格的逻辑性更强,也容易看懂。

2.4 使用 NodeList

理解 NodeList 及其 ”近亲“ NamedNodeMap 和 HTMLCollection,是从整体上透彻理解ODM的关键所在。这三个集合都是”动态的“;每当文档结构发送变化时,它们都会得到更新。始终保存着最新、最准确的信息。从本质上说,所有 NodeList 对象都是在 访问 DOM 文档时 时时运行的查询。

例如下面代码会导致无限循环

var divs = document.getElementsByTagName('div') // 动态集合,实时跟新
  , i
  , div
for (i = 0; i < divs.length; i++) {
  div = document.createElement('div')
  document.body.appendChild(div)
}

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

var divs = document.getElementsByTagName('div'), i, len, div
for (i = 0, len = divs.length; i < len; i++){
  div = document.createElement('div')
  div.appendChild(document.createTextNode(i))
  document.body.appendChild(div)
}

一般来说,应该尽量减少访问 NodeList的次数。因为每次访问 NodeList,都会运行依次基于全文档的查询。所以,可以考虑将从 NodeList 中 取得的值缓存起来。

小结

DOM 是语言中立的API,用于访问和操作 HTML 和 XML文档。DOM1级将它们形象的看出一个层次化的节点数,可以使用JavaScript来操作这个节点树,进而改变底层文档的外观和结构。
DOM由各种节点构成,简要总结如下

  • 最基本的节点类型是 Node,用于抽象地表示文档中一个独立的部分;所有其他类型都继承子Node
  • Document 类型表示整个文档,是一组分层节点的根节点。在JavaScript中,document对象是Document的一个实例。使用document对象,有很多种方式可以查询和取得节点。
  • Element 节点表示文档中的所有HTML 或 XML 元素,有很多种方式可以查询和取得节点。
  • 另外还有一些节点类型,分别表示文本内容、注释、文档类型、CDATA区域、文档片段。

理解DOM的关键,就是理解DOM对性能的影响。DOM操作往往是JavaScript程序中开销最大的部分,而因访问 NodeList 导致的问题为最多。NodeList对象都是”动态的“,这就意味着每次访问 NodeList对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少DOM的操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  •   DOM(文档对象模型)是针对 HTML 和 XML 文档的一个 API(应用程序编程接口)。   DOM 描绘...
    霜天晓阅读 3,632评论 0 7
  •   DOM 1 级主要定义的是 HTML 和 XML 文档的底层结构。   DOM2 和 DOM3 级则在这个结构...
    霜天晓阅读 1,434评论 1 3
  • 参考书:《JavaScript高级程序设计》 知识点前提:什么是节点 Node类型 DOM1级定义了一个Node接...
    DHFE阅读 404评论 0 0
  • 本章内容 理解包含不同层次节点的 DOM 使用不同的节点类型 克服浏览器兼容性问题及各种陷阱 DOM 是针对 HT...
    闷油瓶小张阅读 631评论 0 1
  • 之前通过深入学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西...
    微醺岁月阅读 4,467评论 2 62