DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描绘了一个层次节点树,允许开发人员添加、移除和修改。

1.节点层次

<html>
  <head>
      <title>Simple Page</title>
  </head>
  <body>
      <p>Hello World!</p>
  </body>
</html>

如上的HTML文档,可以表示为一个层次结构。

文档元素:每个文档的根节点,即<html>元素。文档元素是文档最外层的元素,文档中其他元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。在XML中,没有预定义的元素,因此任何元素都可以成为文档元素。

注意:Element titile->Text Simple Page。这个在dom节点操作的时候,不要将text这个节点忘记。

1.1Node类型

节点类型由Node类型中定义的下列数值常量白哦是,任何节点类型必居其一。

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

确定节点类型:

if(someNode.nodeType == Node.ELEMENT_NODE){  //在IE中无效,因为IE没有公开Node类型的构造函数
        alert("Node is an element");
}
if(someNode.nodeType == 1){      //适用于所有浏览器
    alert("Node is an element");
}

nodeName和nodeValue属性

if(someNode.nodeType == 1){
    value = someNode.nodeName;    //nodeName 的值是元素的标签名,因此需要先判断是否是元素
}

NodeList:一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点,但是他并不是Array的实例,虽然这个对象也有length属性。NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询结果,因此DOM结构的变化能够自动反映在NodeList对象中。

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

someNode指的是父节点,childNodes指的是父节点下面所有的子节点,包括text。

将NodeList转换为数组,

//在IE8及之前版本中无效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
//兼容
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;
}

父节点与同胞节点

对于上述的每个childNodes都有同一个父节点,它们的parentNode属性都指向同一个节点。此外,包含在childNodes列表中的每个节点相互之间都是同胞节点。对于一个节点而言,它有previousSibling和nextSibling属性,对应前后两个节点。

2.操作节点

2.1appendChild(),用于向childNodes列表的末尾添加一个节点。更新完成以后将会返回更新节点。

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

2.2insertBefore(),接受两个参数:要插入的节点和作为参照的节点。插入节点以后,被插入的节点会变成参照节点的前一个同胞节点(previousSibing),同时被方法返回。

//插入称为最后一个节点
returnNode = someNode.insertBefore(newNode,null);
alert(newNode === someNode.lastChild);    //true

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

//插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode,someNode.lastChild);  
alert(newNode === someNode.childNodes[someNode.childNodes.length - 2]);  //true

(注:根据《JavaSctipt语言精粹》的建议,将“==”改为“===”,有兴趣的可以看一下这本书,总共只有155页)

2.3replaceChild(),接受两个参数:要插入的节点和要替换的节点。要替换的节点将会被删除,而以上两种方法并不会删除节点。

使用replaceChild()插入一个节点时,该节点的所有关心指针都会从被它替换的节点复制过来。尽管从技术上将,被替换的节点仍然还在文档中占据位置。

2.4removeChild(),移除节点,这个方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。与replaceChild()一样,通过removeChild()移除的节点仍然为文档所有,只不过在文档中已经没有了自己的位置。

注意:以上方法必须先获得父节点parentNode,并不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用了这些方法,将导致错误。

2.5cloneNode()用于创建调用这个方法的节点的一个完全相同的副本。

someNode.cloneNode(true/false);

true:表示执行深复制,即复制节点及其整个子节点树。

false:表示浅复制,即只复制节点本身。

这里需要注意的是:IE9之前的版本不会为空白符创建节点。还有一个IE中存在的bug就是,它会复制事件处理程序,所以这里建议在复制之前最好先移除事件处理程序。

3.Document类型

简单来说,在浏览器中,document用于表示HTML页面的最底层节点。可以直接使用它来获取页面上的任意节点。可以试着通过document.nodeChilds进行测试。在console中我们可以看到NodeList[2]……

Document节点的特征:

nodeType:9;nodeName:"#document";nodeValue:null;parentNode:null;ownerDocument:null;

子节点:DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。

3.1文档子节点

内置了两个访问其子节点的快速方式:documentElement,该属性始终指向HTML页面中<html>元素。另一个就是通过childNodes列表访问文档元素。

这里需要注意的是,一般情况下我们的页面开始是<!DOCTYPE html>,有这个在页面中时,document.childNodes获取的第一个是<!DOCTYPE html>,要通过childNodes遍历得到html元素是document.childNodes[1],使用从整个子节点获取需要看具体情况分析。

直接获取属性:

var body = document.body;        //获取对<body>的引用
var doctype = document.doctype;     //获取对<!DOCTYPE>的引用

浏览器对于document.doctype的支持不一致,因此这个属性并不常用。

同样还需要注意的是关于在html外面的注释是否是节点这个问题,不同的浏览器处理是不一样的。

3.2文档信息

//获取文档标题
var originalTitle = document.title;

//设置文档标题
document.title = "New page title";

//获取完整的URL
var url = document.URL;

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

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

URL、domain、referrer三个属性都和页面请求有关

document.URL
>"http://www.cnblogs.com/lyhabc/p/6131034.html"
document.domain
>"www.cnblogs.com"
document.referrer
>"http://www.cnblogs.com/"

随便开个页面输出一下,我们可以看到三个属性的差别。

在这三个属性中,只有domain是可以设置的。但由于安全方面的限制,并非可以给domain设置任何值。

例如上面这个页面,只能写 document.domain = "www.cnblogs.com"/ document.domain = "cnblogs.com",由于跨域安全设置,来自不同子域的页面无法通过javascript通信,而通过将每个页面的document.domain设置为相同值,这些页面就可以相互访问对方的javascript对象了。

在内嵌框架中会使用到这个。(http://www.3lian.com/edu/2015/05-22/215443.html这个讲的比较粗浅明白)

3.3查找元素

这个是重点!!!!!!!

1.getElementById(),通过接收获取元素的ID来找到对应元素,这里的ID必须与页面中元素的id严格对应。

这里要注意的是:在IE7及更早的版本中并不区分id的大小写,还有如果页面中存在多个相同的id值,那么它会返回第一个出现的元素。

另一个问题是,表单字段的name特性,在IE7种通过getElementById()将会去与表单中name值进行匹配。因此最好不要让表单字段的name特性与其他元素的ID相同。

2.getElementsByTagName(),通过接受一个参数获取元素的标签名,返回的是包含零个或多个元素的NodeList。

这里有个“动态”集合的概念

例如,在页面中有多个div

var list = document.getElementsByTagName('div');
console.log(list.length);      

document.body.appendChild(document.createElement('div'));

在浏览器中,直接输出list.length是9。

对于图像元素来说,还有以下方法。

var imges = document.getElementsByTagName("img");

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

var myImage = images.namedItem("myImage");
var myImage = images["myImage"];

imgaes[0].src;  //输出第一个图像元素的src特性

3.getElemnetsByName() ,是只有HTMLDocument类型才有的,即返回带有给定name特性的所有元素。

常见案例是单选按钮,它们的name必须是一样的。对于单选按钮来说,namedItem()方法只会取得第一项。

4.其他集合,这些都是HTMLCollection对象

document.anchors,包含文档中所有带name特性的<a>元素

document.forms,包含文档中所有<form>元素

document.images包含文档中所有<img>元素

document.link,包含文档中所有带href特性的元素

5.DOM一致性检测

DOM分为多个级别,也包含多个部分。

document.implementation属性提供相应信息和功能

  1. DOMImplementation
    1. __proto__:DOMImplementation
      1. constructor:DOMImplementation()
      2. createDocument:createDocument()
      3. createDocumentType:createDocumentType()
      4. createHTMLDocument:createHTMLDocument()
      5. hasFeature:hasFeature()
      6. Symbol(Symbol.toStringTag):"DOMImplementation"
      7. __proto__:Object

这里主要是使用hasFeature()进行版本检测

var hasXmlDom = document.implementation.hasFeature("XML","1.0");

通过返回的布尔值判断是否是对应版本。

这个功能并不常用,一般情况下,我们在使用一个属性判断是否可以在各种浏览其中使用时,可以直接使用通过try-catch来进行兼容。

3.6文档写入

write()、writelin()、open()、close()

write()会原样写入,writeln()会在字符串末尾添加换行符\n

这里需要注意的是,如果你在文档加载完成后使用了write()或writeln()写入,那么写入的内容将会重新整个页面。

它们可以进行HTML代码的处理,也可以写入<script>只是需要转义符。

4.Element类型

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

nodeType 的值为1

nodeName 的值为元素的标签名;

nodeValue 的值为null;

parentNode 可能是Document或Element;

nodeName和tagName用于访问元素的标签名

<div id="myDiv"></div>

var div = document.getElementBId("myDiv");

alert(div.tagName);//"DIV"

alert(div.tagName === div.nodeName);//true

在HTML中,表签名始终以大写表示;而在XML中(有时候也包括XHTML)中,标签名始终与源代码中的保持一致。

所以要比较的时候最好使用:

if(element.tagName.toLowerCase() === "div"){

}

4.1HTML元素

id,元素在文档中唯一标识符。

title,有关元素的附加说明,一般通过工具提示条显示出来。

lang,元素内容的语言代码,很少使用。

dir,语言方向,值为“ltr”(left-to-right,从左至右)或“rtl”(right-to-left,从右至左),也很少使用。

className,与元素class特性对应,即为元素指定的CSS类。没有将这个属性命名为class,是因为class是ECMAScript的保留字。

(这里插一条,在IE8及以下,无法使用getElementsByClassName()获取对应的元素,因此通过className进行兼容)

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

var div = document.getElementById("myDiv");

div.id    //"myDiv"
div.className   //"bd"
div.title    //"Body text"
div.lang    //"en"
div.dir    //"ltr"

同时也可以进行修改

div.id = "somOtherId";
div.className = "ft";
div.title = "Some other text";
div.lang = "fr";
div.dir = "rtl";

操作特性的DOM三个主要方法:getAttribute()、setAttribute()、removeAttribute()

4.2取得特性,getAttribute()

var div = document.getElementById("div");
alert(div.getAttribute("id"));    //"myDiv"
alert(div.getAttribute("class"));
alert(div.getAttribute("title"));
alert(div.getAttribute("lang"));
alert(div.getAttribute("dir"));

注意:传递给getAttribute()的特性名与实际的特性名相同。因此要想得到class特性值,应该传入“class”而不是“className”,后者只有在通过对象属性访问特性的时候才使用。

可以使用自定义特性,同时特性名词不区分大小写。

根据HTML5规定,自定义特性应该加上data-前缀以便验证,实际浏览器不添加该前缀并不会报错,但是最好按照标准来。

对于HTML中公认的特性,该元素的DOM对象也将存在对应的属性。

对于特殊的存在:style和onclick类似事件处理程序

通过getAttribute()访问时,返回的style特性值中包含css文本,而通过属性来访问它则会返回一个对象。注意,这里是指的内联样式。

document.getElementById("d").getAttribute("style");
 >"backgroundColor:red;font-size:10px;"
document.getElementById("d").style;
>CSSStyleDeclaration {0: "font-size", alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: ""…}

onclick这样的事件处理程序。当在元素上使用时,通过getAttribute()访问,则会返回相应代码的字符串。而在访问onclick属性时,则会返回一个JavaScript函数。

一般不建议获取HTML公认特性的时候使用getAttribute(),而是使用对象属性。

4.3设置特性

div.setAttribute("id","someOtherId");
div.setAttribute("class","ft");
div.setAttribute("title","Some other text");
div.setAttribute("lang","fr");
div.setAttribute("dir","rtl");

这个方法既可以操作HTML特性也可以操作自定义特性,而div.id = "someOtherId"这种方法不可以操作自定义特性。

4.4removeAttributes()

这个方法用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性。

4.5document.createElement()创建新元素

var div = document.createElement("div");

创建完成以后,可以痛过操作元素特性添加更多子节点以及其他操作。

div.id = "myNewDiv";

div.className = "box";

通过appendChild()、insertBefore()、replaceChild()方法,添加到文档树中。

在IE7以及更早的版本中,有以下已知问题:

不能设置动态创建的<iframe>元素的name特性。

不能通过表单的reset()方法重设动态创建的<input>元素。

动态创建的type特性值为“reset”的<button>元素重设不了表单。

动态创建的一批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\" value=\"1\"");
            var radio2 = document.createElement("<input type=\"radio\" name=\"choice\" value=\"2\"");
        }

5.关于元素的子节点的差别childNode获得的节点

<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>元素之间的空白符)

通过childNodes属性遍历nodeType属性,需要element.childNodes[i].nodeType === 1,判断一下。

6.Text类型

nodeType的值为3;

nodeName的值为“#text”;

nodeValue的值为节点所包含的文本;

parentNode是一个element;

不支持(没有)子节点。

在默认情况下,一个元素最多只能包含有一个文本系欸但,而且必须确实存在内容。

var textNode = div.firstChild;//获取文本节点

div.firstChild.nodeValue = "Some other message";  //修改文本节点中nodeValue内容

div.firstChild.nodeValue = "Some <strong>other</strong> message";

输出结果是"Some &lt;strong&gt;other&lt;/strong&gt; message";

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

本来标题应该是【JavaScript整理】,想要整理的稍微规范并且全面些,拿出了“尘封已久”的《JavaScript高级程序设计》,然后在强迫症的作用下一发不可收拾成了笔记摘录了。这本书还是强烈推荐都看看的,虽然很厚,不过有时候弯路才是真正的捷径,在对学习前端开始入门并且感兴趣以后,可以回归书本一下。

顺便推荐本书:《CSS禅意花园》