DOM

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2020-04-08

DOMDocument Object Model 文档对象模型)是针对HTMLXML 文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM 脱胎于Netscape 及微软公司创始的DHTML(动态HTML),但现在它已经成为表现和操作页面标记的真正的跨平台、语言中立的方式。

一、Node类型

DOM1级定义了一个Node接口,该接口将由DOM 中的所有节点类型实现。每个节点都有一个nodeType 属性,用于表明节点的类型。节点类型由在Node 类型中定义的下列12 个数值常量来表示,任何节点类型必居其一: 节点类型nodeType 节点的nodeName和nodeValue

节点

每个节点都有一个childNodes属性,其中保存着一个NodeList 对象。NodeList是一种类数组对象,可以通过下标访问,有length属性,但它并不是Array的实例。NodeList对象的独特之处在于,**它实际上是基于DOM 结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList 对象中。**我们常说,NodeList是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照。

如何访问保存在NodeList中的节点,可以通过方括号,也可以使用item()方法。

var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var length = someNode.childNodes.length;

使用Array.prototype.slice()方法可以将NodeList对象转换为数。

var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);

节点关系的引用属性

  • parentNode:该属性指向文档树中的父节点
  • previousSibling:该属性指向文档书中的前一个兄弟节点,第一个节点的previousSibling 属性值为null
  • nextSibling:该属性指向文档书中的前一个兄弟节点,最后一个节点的nextSibling属性值为null
  • firstChild :该属性指向父节点的第一个子节点
  • lastChild:该属性指向父节点的最后一个子节点

操作子节点

  • appendChild:用于向childNodes列表的末尾添加一个节点,返回新增的节点,并且更新DOM,并且该节点自动作为lastChild存在。如果传入到appendChild中的节点已经是文档的一部分了,那么该节点就会成为父节点的lastChild

  • insertBefore:把节点放在childNodes列表中某个参照节点的前面,接受两个参数:要插入的节点和作为参照的节点,返回被插入的节点。如果参照节点是null,则insertBeforeappendChild执行相同的操作。

  • replaceChild:接受两个参数,要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。在使用replaceChild插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。

  • removeChild:这个方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。

操作节点的其他方法

  • cloneNode:用于创建调用这个方法的节点的一个完全相同的副本,接受一个布尔值参数,表示是否执行深复制。为true时执行深复制,复制节点及其整个子节点树;反之,执行浅复制,只复制节点本身。复制后返回的节点副本属于“文档碎片”,除非通过appendChildinsertBeforereplaceChild将它添加到文档中。
  • normalize:由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)

var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0

cloneNode方法不会复制DOM节点中的JavaScript属性,例如事件处理程序等。这个方法只复制特性,深复制情况下还复制子节点,其他一切都不会复制。

二、 Document 类型

document对象:在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document 对象是window 对象的一个属性,因此可以将其作为全局对象来访问。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。Document 节点具有下列特征: nodeType 的值为9;nodeName 的值为"#document"nodeValue 的值为nullparentNode 的值为null

document对象的属性:

//取得对<html>的引用
var html = document.documentElement; 

//取得对<body>的引用
var body = document.body;

//取得对<DOCTYPE>文档类型的引用
var doctype = document.doctype; 

//取得文档标题
var originalTitle = document.title;
//设置文档标题
document.title = "New page title";

//取得完整的URL,不可设置
var url = document.URL;

//取得域名,可设置,但是不能随便设置
var domain = document.domain;

//假设页面来自p2p.wrox.com 域
document.domain = "wrox.com"; // 成功
document.domain = "nczonline.net"; // 出错! 只能省略域名

//取得来源页面的URL,不可设置
var referrer = document.referrer;

查找元素

  • getElementById:接收一个参数:要取得的元素的ID。如果找到则返回该元素,如果不存在,则返回null。如果页面中多个元素的ID 值相同,只返回文档中第一次出现的元素。

  • getElementsByTagName:接受一个参数,即要取得元素的标签名,返回一个HTMLCollection对象,作为一个“动态”集合,返回所有的标签元素组成的类数组。这个HTMLCollection 对象还有一个方法namedItem,可以通过元素的name特性取得集合中的项,而不用通过下标。例如:

<img src="myimage.gif" name="myImage">

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

  // HTMLCollection 还支持按名称访问项
  var myImage = images["myImage"];
</script>
  • getElementsByClassName:接受一个参数,即要取得元素的类名,返回一个HTMLCollection对象。

在通过元素调用这些方法时,除了搜索起点是当前元素之外,其他方面都跟通过**document**调用这个方法相同,因此结果只会返回当前元素的后代。

var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");

创建元素

  • document.createElement() 创建元素:接受一个参数,即要创建元素的标签名,返回一个“文档碎片”。

为访问文档常用的部分提供的快捷方式

  • document.anchors,包含文档中所有带name特性的<a>元素;
  • document.forms,包含文档中所有的<form>元素
  • document.images,包含文档中所有的<img>元素
  • document.links,包含文档中所有带href 特性的<a>元素。

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

文档写入

writewriteln方法都接受一个字符串参数,即要写入到输出流中的文本。write会原样写入,而writeln则会在字符串的末尾添加一个换行符(\n)。在页面被加载的过程中,可以使用这两个方法向页面中动态地加入内容。

方法openclose分别用于打开和关闭网页的输出流。

document.write("<strong>" + (new Date()).toString() + "</strong>");

调用document.write()会重写页面内容,因为这样做会创建一个DOM元素,而且可以在将来访问该元素。通过writewriteln输出的任何HTML代码都将如此处理。

还可以使用writewriteln方法动态地包含外部资源,例如JavaScript文件等。在包含JavaScript文件时,必须注意不能直接包含字符串"</script>",因为这会导致该字符串被解释为原有脚本块的结束,它后面的代码将无法执行。应该使用转义符做处理"<\/script>"

三、Element 类型

Element 类型用于表现XMLHTML元素,提供了对元素标签名、子节点及特性的访问。

具有以下特征:nodeType 的值为1,nodeName 的值为元素的标签名;nodeValue 的值为null

所有HTML元素都由HTMLElement 或者它的子类型来表示,HTMLElement 类型直接继承自Element 并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。

// id,元素在文档中的唯一标识符
// title,有关元素的附加说明信息
// lang,元素内容的语言代码,很少使用。
// dir,语言的方向,值有ltr从左到右  rtl从右到做 ,很少使用
// className,与元素的class 特性对应,即为元素指定的CSS类。

<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>

操作元素属性

  • getAttribute获取属性

任何元素的所有特性,可以通过DOM元素本身的属性来访问。但自定义属性只能通过getAttribute来获得,而使用元素.属性 = 新值的方式设置的自定义属性是无法通过getAttribute获得的。

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

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

第二类与众不同的特性是onclick这样的事件处理程序,如果通过getAttribute访问,则会返回相应代码的字符串String,而在访问onclick属性时,则会返回一个JavaScript函数Function,如果未在元素中指定,则返回null

除了上面的差别外,要想得到class特性值,应该传入"class"而不是"className",由于存在这些差别,开发人员大都只会在取得自定义特性值的情况下,才会使用getAttribute方法。

  • setAttribute获取属性:接受两个参数,要设置的特性名和值。如果该特性是存在的,则替换现有的值;不存在,则设置。通过setAttribute方法既可以操作HTML特性也可以操作自定义特性。

  • removeAttribute删除属性:接受一个参数,即删除的属性key,不仅会清除特性的值,而且也会从元素中完全删除特性;

四、Text 类型

每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。(只有空格也算是有内容)

  • nodeType 的值为3
  • nodeName 的值为"#text"
  • nodeValue 的值为节点所包含的文本;

如果这个文本节点当前存在于文档树中,那么修改文本节点的结果就会立即得到反映。另外,在修改文本节点时还要注意,此时的字符串会经过HTML(或XML,取决于文档类型)编码。换句话说,小于号、大于号或引号都会像下面的例子一样被转义。

Some <strong>other</strong> message 
转义为
Some &lt;strong&gt;other&lt;/strong&gt; message

创建文本节点

document.createTextNode : 创建新文本节点,接受一个参数,即要插入节点中的文本。与设置已有文本节点的值一样,作为参数的文本也将按照HTMLXML 的格式进行编码,也属于“文档碎片”。

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

一般情况下,每个元素只有一个文本子节点。不过,在某些情况下也可能包含多个文本子节点,比如使用appendChild()方法多次添加文本节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。如果在一个包含两个或多个文本节点的父元素上调用normalize方法,则会将所有文本节点合并成一个节点,结果节点的nodeValue等于将合并前每个文本节点的nodeValue 值拼接起来的值。

五、DOM操作技术

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

动态脚本

指的是在页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本。

1、插入外部文件

function loadScript(url){
  var script = document.createElement("script");
  script.type = "text/javascript";
  script.src = url; 
  document.body.appendChild(script); // 在这行代码执行之前都不会下载文件
}

loadScript("client.js");  // 方法调用时 动态添加script标签 并执行脚本

2、行内方式,也是添加script标签 ,但是脚本代码吧不是从外部引入,而是本地添加进去。所有写在标签对中的代码都算是script元素的文本节点

var script = document.createElement("script");
script.type = "text/javascript";

// 有的浏览器 不支持text属性 可以使用 script.appendChild(document.createTextNode(code))
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);

动态样式

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

1、外部引入link

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

loadStyles("myStyle.css");

2、内部style:这种方式会实时地向页面中添加样式,因此能够马上看到变化。

function loadStyleString(css){
  var style = document.createElement("style");
  style.type = "text/css";
  try{
      style.appendChild(document.createTextNode(css));
  } catch (ex){ // 兼容IE
      style.styleSheet.cssText = css;
  }
  var head = document.getElementsByTagName("head")[0];
  head.appendChild(style);
}

loadStyleString("body{background-color:red}");

操作表格

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 集合中的指定位置插入一行。

tbody元素的属性和方法如下:

  • rows:保存着<tbody>元素中行的HTMLCollection
  • deleteRow(pos):删除指定位置的行。
  • insertRow(pos):向rows 集合中的指定位置插入一行,返回对新插入行的引用。

tr元素的属性和方法如下:

  • cells:保存着<tr>元素中单元格的HTMLCollection
  • deleteCell(pos):删除指定位置的单元格。
  • insertCell(pos):向cells 集合中的指定位置插入一个单元格,返回对新插入单元格的引用。

使用NodeList

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

如果想要迭代一个NodeList,因为NodeList是动态的,NodeList.length也是动态的,如果在迭代的过程对这个NodeList有添加Node的操作,将会无限迭代下去,所以在这种场景一下,应该使用变量暂存length:var len = NodeList.length

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