# 节点层次

HTML 中每一段标记都可以通过树中的一个节点来表示:HTML 元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点来表示,而注释通过注释节点来表示。

# Node 类型

JavaScript 中所有节点类型都继承自 Node 类型,因此所有节点属性都共享着相同的基本属性和方法。

每个节点都有一个 nodeType 属性,用于表明节点的类型。节点类型由在 Node 类型中定义的下列 12 个数值常量来表示,任何节点类型必具其一:

  • Node.ELEMENT_NODE(1)
  • Node.ATTRIBUTE_NODE(2)
  • Node.TEXT_NODE(3)
  • Node.CDATA_SECTION_NODE(4)
  • Node.ENTITY_PREPERENCE_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 中无效
if (someNode.nodeType === Node.ELEMENT_NODE) {
  console.log('node is an element')
}
// 可以通过与数值进行比较适用于所有浏览器
if (someNode.nodeType === 1) {
  console.log('node is an element')
}

# nodeName 和 nodeValue 属性

nodeName 是元素的标签名。Node 的 nodeValue 属性返回或设置当前节点的值。

# 节点关系

  • 每个节点都有一个 childNodes 属性,用于保存他的子元素,是一个 NodeList 对象,NodeList 是一种 类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。NodeList 实际上是基于 DOM 结构动态执行查询的结果,因此 DOM 结构的变化能够自动反应在 NodeList 对象中。他并不是在某一时期的快照。

下面代码将 NodeList 对象转换为数组

// 在 IE8 之前无效
var arrayOfNodes = Array.prototype.slice.call(someNode, chilNodes, 0)

下面代码是兼容 IE8:

function convertToArray(nodes) {
  var array = null
  try {
    array = Array.prototype.slice.call(nodes, 0)
  } catch(ex) {
    array = new Array()
    for (var i = 0; leng = nodes.length; i ++) {
      array.push(nodes[i])
    }
  }
}
  • 每个节点都有一个 parentNode 属性;该属性指向文档树中的父节点。包含在 childrenNodes 列表中的每个节点相互之间都是同胞节点,通过使用列表中每个节点的 previousSibling 和 nextSilling 属性,可以访问他的前一个与后一个兄弟节点。
if (someNodes.nextSibling === null) {
  console.log('Last node in the parents childNodes list')
} else if (someNodes.previousSibling === null) {
  console.log('First node in the parents childNodes list')
}
  • 父节点的 firstChild 和 lastChild 属性分别指向其 childNodes 列表中的第一个和最后一个节点。

  • 使用 hasChildNodes() 方法可以检查是否有子节点;

  • 所有节点都有一个最后一个属性是 ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于他所在的文档,任何节点都不能同时存在于两个或更多的文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档顶点。

# 操作节点

  • appendChild() 方法:用于向 chilNodes 列表的末尾添加一个节点。返回值为新增的节点。
var returnedNode = someNode.appendChild(newNode)
console.log(returnedNode === newNode) // true
console.log(someNode.lastChild === newNode) // true

如果新添加的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新文职。即使可以将 DOM 树看成是由一系列指针连接起来的,但任何 DOM 节点也不能同时出现在文档中的多个位置上。

// someNode 有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild)
console.log(returnNode === someNode.firstChild) // false
console.log(returnNode === someNode.lastChild) // true
  • insertBefore() 方法:用于将节点放在 childNodes 列表中某个特定的位置上;他接收两个参数,第一个是要插入节点,第二个是作为参照的节点;插入节点后,被插入的节点会变成参照节点的前一个兄弟节点,同时被方法返回。如果参照节点是 null,则 insertBefore() 与 appendChild() 执行相同的操作,如下
// 插入后成为左右一个子节点
returnNode = someNode.insertBefore(newNode, null)
console.log(newNode === someNode.lastChild) // true

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

// 插入到最后一个子节点的前面
returnNode = someNoed.insertBefore(newNode, someNode.lastChild)
console.log(newNode === someNode.childNodes[someNode.childNodes.length - 2]) // true
  • replaceChild() 方法:替换节点。接收两个参数,要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点替换其位置,如下:
// 替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild)

// 替换最后一个子节点
var rerurnedNode = someNode.replaceChild(newNode, someNode.lastChild)
  • removeChild() 方法:移除节点,该方法接收一个参数,即要移除的节点,该方法的返回值为被移除的节点。如下:
// 移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild)

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

# 其他方法

  • cloneNode() 方法:用于创建调用这个方法的节点的一个完全相同的副本;接收一个布尔值参数,表示是否执行深拷贝,ture 执行深拷贝。也就是复制节点及其整个子节点树;参数为 false 的情况下,执行浅拷贝,即只拷贝节点本身。拷贝的节点需要指定父节点,可以通过 appendChild()、insertBefore() 或者 replaceChild() 将它添加到文档中。

  • normalize() 方法:主要作用处理文档树中的文本节点。由于解析器的实现或 DOM 操作的原因,可能出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除他,如果找到相邻的文本节点 ,则将他们合并为一个文本节点。

# Document 类型

JavaScript 通过 Document 类型表示文档,在浏览器中,document 对象是 HTMLDocument (继承自 Document 类型) 的一个实例,表示整个 HTML 页面。而且 document 对象是 window 对象的一个属性。具有以下特性:

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

# 文档的子节点

  • 可以通过 documentElement 属性,访问 HTML 页面中的 HTML 元素。
  • 可以通过 childNodes 列表访问文档元素。
var html = document.documentElement // 取得 html 的引用
console.log(html === document.childNodes[0]) // true
console.log(html === document.firstChild) // true

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

  • 可以通过 document.doctype 获取 DOCTYPE 标签的引用。

# 文档信息

  • document.title 属性:包含着 title 标签元素中的文本。
  • document.URL 属性:包含页面完整的 URL。
  • document.domain 属性:只包含页面的域名。
  • document.referrer 属性:保存着链接当前页面的那个页面的 URL。在没有来源页面的情况下,该 属性中可能会包含空字符串。

上面的属性中,只有 domain 属性可以设置,但由于安全方面的限制,也并非可以给 domain 设置任何值。如果 URL 中包含一个子域名,例如 P2.wrox.com,那么只能将 domain设置为 wrox.com (URL 中包含 www,如 www.wrox.com 时,也是如此),不能将这个属性设置为 URL 中不包含的域。如下:

// 假设页面来自于 p2p.wrox.com
document.domain = 'wrox.com' // 成功
document.domain = 'nczonline.net' // 失败

# 查找元素

  • getElementById():接收一个参数,要取得的元素的 id,如果找到相应的元素则返回该元素,如果不存在带有相应 id 的元素,则返回 null。如果页面中有多个元素的 id 值是相同的,那么只返回文档中第一次出现的元素。
  • getElementsByTagName():接收一个参数,即要取得元素的标签名,而返回的是包含零或者多个元素的 NodeList。在 HTML 文档中,这个方法返回一个 HTMLCollection 对象。该对象有一个 namedItem() 方法,使用这个方法可以通过元素的 name 特性取得集合中的项。在提供按索引访问项的基础上,HTMLCollection 还支持按名称访问项,对命名的项可以使用方括号语法来访问(就是传入元素的 name 属性),如果要取得所有元素,则传入 "*" ,获取全部元素。
  • getElementsByName():这个方法会返回带有给定 name 特性的所有元素。一般用于获取单选按钮。为了确保发送给浏览器的值正确无误,所有单选按钮必须具有相同的 name 特性;这个方法返回一个 HTMLCollection 对象。

# 特殊集合

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

# 文档写入

将输出流写入到网页中主要为下面几个方法:这几个方法都接收一个字符串参数,即要写入到输出流中的文本;

  • write():原样写入
  • writeln():会在每个字符串的末尾添加一个换行符 在页面被加载的过程中,可以使用上面两个方法向页面中动态地加入内容。
  • open():打开网页的输出流。
  • close():关闭网页的输出流。 如果是在页面加载期间使用 write() 或 writeln() 方法,则不需要使用上面两个方法。
<html>
  <head>
    <title>document.write() example</title>
  </head>
  <body>
    <p>The current date and time is:</p>
    <script type="text/javascript">
      document.write('<strong>' + (new Date().toString() + '</strong>'))
    </script>
  </body>
</html>

上面的代码使用功能 document.write() 在页面被呈现的过程中直接向其中输出了内容,如果在文档加载结束后再调用 document.write() ,那么输出的内容将会重写整个页面。

# Element 类型

Element 类型提供了对元素标签名、子节点及特性的访问,具有下面特性:

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

如果要访问元素的标签名,可以使用 nodeName 或者 tagName 属性;在 HTML 中标签名始终都已全部大写表示。我们如果需要比较可以将其转换为小写进行比较:element.tagName.toLowerCase()。

# HTML 元素

HTML 元素都由 HTMLELement 类型表示,不是直接通过这个类型,也是通过他的自列席来表示,HTMLElement 类型直接继承自 Element 并添加了一些属性。添加的这些属性分别对应每个 HTML 元素中都存在的下列标准属性:

  • id
  • title
  • lang
  • dir:语言的方向
  • className

上面这些属性都可以通过读取属性方式获取:

var div = document.getElementById('myDiv')
console.log(div.id)
console.log(div.title)
console.log(div.className)
// 也可以直接进行赋值,修改对应的属性
div.id = 'someo'
div.className = 'ft'

# 获取属性

操作属性主要有三个方法:getAttribute()、setAttribute() 和 removeAttribute() ;

  • 传递给 getAttribute() 的特性名需要与实际的特性名一致,如 class 应该传入 class;也可以访问到自己定义的特性。自定义的特性应该加上 data- 前缀以便验证。

任何元素的所有特性,也都可以通过 DOM 元素本身的属性来访问,不过只有公认的(非自定义)特性才会以属性的形式添加到 DOM 对象中,如下:

<div id="myDiv" align="left" my_attribute="hello"></div>

因为 id 和 align 在 HTML 中是 div 的公认特性,因此该元素的 DOM 对象中也将存在对应的属性,不过自定义的属性在除 IE 浏览器外是不存在的:

console.log(div.id) // myDiv
console.log(div.my_attribute) // undefined(IE 除外)
console.log(div.align) // left

有两种比较特殊的属性,属性值与 getAttribute() 返回的值并不相同:

  • style 属性:使用 getAttribute() 返回的 style 属性中包含的是 css 文本,通过属性访问则返回的是一个对象。
  • onclick 这种事件处理程序:getAttribute() 返回的是相应的代码字符串,通过属性访问返回是一个 JavaScript 函数。

# 设置属性

setAttribute():接收两个参数,要设置的属性名,以及值。如果要设置的属性已经存在,那么就会替换现有的值。通过这个方法设置的属性名会被统一转换为小写的形式。 也可以直接通过属性值进行设置:div.id = 'some' 这样设置也可以,不过自定义的属性是不能通过这种方式设置。

removeAttribute() :用于彻底删除元素的特性,调用这个方法不仅会清除属性的值,而且会直接删除属性。

# attributes 属性

attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似,也是一个动态的集合,元素的每一个属性都由一个 Attr 节点表示,每个节点都保存在 NamedNodeMap 对象中, NamedNodeMap 对象拥有下面方法:

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

attributes 属性中包含一系列节点,每个节点的 nodeName 就是特性的名称,而节点的 nodeValue 就是特性的值,要取得 id 的属性,入下代码:

var id = element.attributes.getNamedItem('id').nodeValue
// 也可以用下面代码
var id = element.attributesp['id'].nodeValue

也可以使用这种语法来设置属性的值:

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

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

setNamedItem() 可以为元素添加一个新属性:

element.attribute.setNamedItem(newAttr)

下面代码是遍历元素的属性,构造成 name = 'value' name = 'value' 这样的字符串格式:

function outputAttributes(element) {
  let pairs = []
  for (let i = 0; len = element.attributes.length; i < len; i ++) {
    const attribute = element.attributes[i]
    pairs.push(`${attribute.nodeName}=${attribute.nodeValue}`)
  }
  return pairs.join(" ")
}

# 创建元素

  • document.createElement() 方法创建新元素,该方法接收一个参数,即要创建元素的标签名。使用 createElement() 方法创建新元素的同时也会将其 ownerDocument 属性设置为 document ,此时,可以再为其添加属性、添加更多子元素。比如:
div.id = "myNewDiv"
div.className = "box"

可以使用 appendChild() 、 insertBefore() 或 replaceChild() 。

# 后代的子节点

元素的 childNodes 包含所有子节点,这些子节点可能是其他元素、文本节点、注释或处理指令;可以通过下面代码筛选元素节点:

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

如果需要通过特定的标签名取得子元素或后代节点,可以使用元素的 getElementsByTagName() 方法,他会限制搜索范围只在当前元素内,也就是说只返回当前元素后代中特定的元素:

let ul = document.getElementById('myList')
let 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 开始删除 count 个字符;
  • insertData(offset, text):在位置 offset 插入 text;
  • replaceData(offset, count, text):用 text 替换从 offset 指定的位置开始到 offset + count 为止处的文本;
  • splitText(offset):从 offset 指定的位置将当前文本节点分为两个文本节点;
  • substringData(offset, count):提取从位置 offset 到 offset + count 位置的字符串;

如下代码:

<div>hello world</div>

可以通过下面代码访问文本节点

let textNode = div.firsrChild // 或 div.childNodes[0]
// 修改
div.firstChild.nodeValue = 'some other message'

修改文本的时候还需要注意就是 HTML 或 XML 代码会被转换为实体编码,即小于号、大于号或引号会被转义,如下:

// 输出为"Some &lt;strong&gt;other&lt;/strong&gt; message"
div.firstChild.nodeValue = "Some <strong>other</strong> message"

# 创建文本节点

使用 document.createTextNode() 创建新文本节点;它接收一个参数,即要插入节点的文本。如下代码,给 div 元素添加一段文本消息:

let element = document.createElement('div')
element.className = 'message'

let textNode = document.createTextNode('hello world')
element.appendChild(textNode)

document.body.appendChild(element)

一般情况下,每个元素只有一个文本节点,不过,在某些情况下也可能包含多个文本节点,如下:

let element =  document.createElement('div')
element.className = 'message'

let textNode = document.createTextNode('hello world')
element.appendChild(textNode)

let anotherTextNode = document.createTextNode('Yippee!')
element.appendChild(anotherTextNode)

document.body.appendChild(element)

如果两个文本节点是相邻的同胞节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。

# 规范化文本节点

如果在一个包含两个或者多个文本节点的父元素上调用 normalize() 方法,则会所有文本接地那合并成一个节点,结果节点的 nodeValue 等于将合并前每个文本节点的 nodeValue 值拼接起来的值:

let element =  document.createElement('div')
element.className = 'message'

let textNode = document.createTextNode('hello world')
element.appendChild(textNode)

let anotherTextNode = document.createTextNode('Yippee!')
element.appendChild(anotherTextNode)

document.body.appendChild(element)

console.log(element.childNodes.length) // 2
element.normalize()
console.log(element.childNode.length) // 1
console.log(element.firstChild.nodeValue) // 'hello world!Yippee!'

# 拆分文本节点

splitText() 方法可以将一个文本节点分成两个文本节点,即按照指定的位置分隔 nodeValue 值。

let element =  document.createElement('div')
element.className = 'message'

let textNode = document.createTextNode('hello world')
element.appendChild(textNode)

let anotherTextNode = document.createTextNode('Yippee!')
element.appendChild(anotherTextNode)

document.body.appendChild(element)

let 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 值为 Document 或 Element 对象;
  • 不支持子节点。

有 splitText() 方法,注释的实际内容可以通过 nodeValue 或 data 属性获得。

可以使用 document.createComment() 方法创建注释节点,参数为注释文本,如下所示:

let commen = document.createComment('a comment')

# DocumentType 类型

DocumentType 类型的节点包含文档的文档类型( doctype )信息,具有以下特征:

  • nodeType 等于 10;
  • nodeName 值为文档类型的名称;
  • nodeValue 值为 null ;
  • parentNode 值为 Document 对象;
  • 不支持子节点

DocumentType 对象在 DOM Level 1 中不支持动态创建,只能在解析文档代码时创建。对于支持这个类型的浏览器, DocumentType 对象保存在 document.doctype 属性中。DOM Level 1 规定了 DocumentType 对象的 3 个属性:name 、entities 和 notations 。其中, name 是文档类型的名称,entities 是这个文档类型描述的实体的 NamedNodeMap ,而 notations 是这个文档类型描述的表示法的 NamedNodeMap 。因为浏览器中的文档通常是 HTML 或 XHTML 文档类型,所以 entities 和 notations 列表为空。(这个对象只包含行内声明的文档类型。)无论如何,只有 name 属性是有用的。这个属性包含文档类型的名称,即紧跟在 !DOCTYPE 后面的那串文本。比如下面的 HTML 4.01 严格文档类型:

<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01// EN"
"http:// www.w3.org/TR/html4/strict.dtd">

对于这个文档类型, name 属性的值是 "html" :

alert(document.doctype.name); // "html"

# DocumentFragment 类型

在所有节点类型中, DocumentFragment 类型是唯一一个在标记中没有对应表示的类型。DOM 将文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗。DocumentFragment 节点具有以下特征:

  • nodeType 等于 11;
  • nodeName 值为 "#document-fragment" ;
  • nodeValue 值为 null ;
  • parentNode 值为 null ;
  • 子节点可以是 Element 、 ProcessingInstruction 、 Comment 、 Text 、 CDATASection 或 EntityReference 。

不能直接把文档片段添加到文档。相反,文档片段的作用是充当其他要被添加到文档的节点的仓库。可以使用 document.createDocumentFragment() 方法像下面这样创建文档片段:

let fragment = document.createDocumentFragment()

可以通过 appendChild() 或 insertBefore() 方法将文档片段的内容添加到文档。

假设想给这个 ul 元素添加 3 个列表项。如果分 3 次给这个元素添加列表项,浏览器就要重新渲染 3 次页面,以反映新添加的内容。为避免多次渲染,下面的代码示例使用文档片段创建了所有列表项,然后一次性将它们添加到了 ul 元素:

let fragment = document.createDocumentFragment()
let ul = document.getElementById('mylist')

for (let i = 0; i < 3; i ++) {
  let li = document.createElement('li')
  li.appendChild(document.createTextNode(`Item ${i + 1}`))
  fragment.appendChild(li)
}
ul.appendChild(fragment)

# Attr 属性

元素的属性在 DOM 中以 Attr 类型来表示;属性数存在于元素的 attributes 属性中,它具有下面的特性:

  • nodeType 等于 2;
  • nodeName 值为属性名;
  • nodeValue 值为属性值;
  • parentNode 值为 null ;
  • 在 HTML 中不支持子节点;
  • 在 XML 中子节点可以是 Text 或 EntityReference 。

通常使用 getAttribute() 、 removeAttribute() 和 setAttribute() 方法操作属性。Attr 对象上有 3 个属性: name 、value 和 specified 。其中, name 包含属性(与 nodeName 一样), value 包含属性值(与 nodeValue 一样),而 specified 是一个布尔值,表示属性使用的是默认值还是被指定的值。

可以使用 document.createAttribute() 方法创建新的 Attr 节点,参数为属性名。比如,要给元素添加 align 属性,可以使用下列代码:

let attr = document.createAttribute('align')
attr.value = 'left'
element.setAttributeNode(attr)

console.log(element.attributesp['align'].vaule) // left
console.log(element.getAttributeNode('align').vaule) // left
console.log(element.getAttribute('align')) // left

# DOM 操作技术

# 动态脚本

通过 dom 操作动态加载脚本;

与直接在 html 中下面的效果一样:

<script src="foo.js"></script>

dom 编程:

function loadScript(src) {
  let script = document.createElement('script')
  script.src = src
  document.body.appendChild(script)
}

注意,在上面最后一行把 script 元素添加到页面之前,是不会开始下载外部文件的。当然也可以把它添加到 head 元素,同样可以实现动态脚本加载。

动态插入 JavaScript 的方式嵌入代码:

<script>
  function sayHi() {
    console.log('hi')
  }
</script>

使用 DOM,可以实现以下逻辑:

let script = document.createElement('script')
script.appendChild(document.createTextNode("function sayHi()  { console.log('hi') }"))
document.body.appendChild(script)

上面的代码在 IE 中是不能运行的,IE 限制了 DOM 访问其子节点,但 script 元素有一个 text 属性,可以用来添加 JavaScript 代码:

const script = document.createElement('script')
script.text = "function sayHi()  { console.log('hi') }"
document.body.appendChild(script)

对函数进行封装如下:

function loadScriptString(code) {
  const script = document.createElement('script')
  script.type = 'text/javascript'
  try {
    script.appendChild(document.createTextNode(code))
  } catch (ex) {
    script.text = code
  }
  document.body.appendChild(script)
}

# 动态样式

html 中表示:

<link rel="stylesheet" type="text/css" href="styles.css">

使用 DOM 代码如下:

let link = document.createElement('link')
link.rel = 'stylesheet'
link.type = "text/css"
link.href = 'styles.css'

let head = document.getElementByTagName('head')[0]
head.appendChild(link)

另一种定义样式的方式是使用 style 元素包含嵌入的 CSS 规则,例如:

<style type="text/css">
  body {
    background-color: red;
  }
</style>

使用下面 DOM 代码:

let style = document.createElement('style')
style.type = 'text/css'
style.appendChild(document.createTextNode('body{background: red}'))
let head = document.getElementByTagName('head')[0]
head.appendChild(style)

上面代码不能在 IE 中运行,可以使用下面兼容方式:

let style = document.createElement('style')
style.type = 'text/css'
try {
  style.appendChild(document.createTextNode('body{background: red}'))
} catch (ex) {
  style.styleSheet.cssText = 'body{background: red}'
}
let head = document.getElementByTagName('head')[0]
head.appendChild(style)

# 操作表格

使用 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编程方式重建这个表格的代码:

// 创建表格
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// 创建表体
let tbody = document.createElement("tbody");
table.appendChild(tbody);
// 创建第一行
let row1 = document.createElement("tr");
tbody.appendChild(row1);
let cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
let cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);
// 创建第二行
let row2 = document.createElement("tr");
tbody.appendChild(row2);
let cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
let cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);
// 把表格添加到文档主体
document.body.appendChild(table);

以上代码相当烦琐,也不好理解。为了方便创建表格,HTML DOM 给 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) ,在行集合中给定位置插入一行。

tbody>元素添加了以下属性和方法:

  • rows ,包含 tbody 元素中所有行的 HTMLCollection ;

tr 元素添加了以下属性和方法:

  • cells ,包含 tr 元素所有表元的 HTMLCollection ;
  • deleteCell(pos) ,删除给定位置的表元;
  • insertCell(pos) ,在表元集合给定位置插入一个表元,返回该表元的引用。

使用这些方法重写上面代码:

// 创建表格
let table = document.createElement("table");
table.border = 1;
table.width = "100%";
// 创建表体
let 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);

评 论:

更新: 12/27/2020, 4:59:16 PM