JS学习9(DOM)

DOM,文档对象模型,是一个针对HTML和XML文档的一个API,它描绘了一个层次化的节点树。值得注意的是IE中的所有DOM对象都是使用COM对象实现的,这就造成了IE中的DOM对象与原生JS对象表现并不一致。

节点层次

文档节点是每个文档的根节点。在html文档中,文档节点只有一个子节点即html元素。这个元素我们称之为文档元素,每个文档只有一个文档元素,其他所有元素都包含在文档元素中。
节点分为几种不同的类型,每种类型分别表示文档中不同的信息及标记。
比如:元素节点,特性节点,文档节点,注释节点等。总共有12种节点类型,这些类型都继承自一个基类型。

Node类型

这是DOM1级定义的一个接口,DOM中所有类型节点都会实现这个接口。在JS中作为Node类型实现,所有节点类型皆继承自Node类型。不过IE直接访问不到Node。Node类型由下面12个常量表示:

  • 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)
var htmlNode = document.getElementsByTagName("html")[0];
//本来标准的写法应该是与Node.ELEMENT_NODE这个常量做比较,但是由于IE访问不到Node这个类型,所以只好直接比较
if (htmlNode.nodeType == 1){
    alert("Node is an element.");
    alert(htmlNode.nodeName);  //HTML
    alert(htmlNode.nodeValue);  //null
}

这里查找html元素时使用的方法返回的是一个NodeList对象,基本对node类型的查找和获取都会涉及到这个对象。这个对象是一个类数组对象,许多使用方法都和数组是一样的,但是它并不是Array类型的实例。其特别的地方在于它其实是基于DOM结构动态执行查询的结果,DOM结构的变化可以自动反应在NodeList中。想将NodeList转换为数组,可以使用Array.prototype.slice()方法。

var bodyNode = document.getElementsByTagName("body")[0];
var childList = bodyNode.childNodes;
var childListArray = Array.prototype.slice.call(childList,0);
alert(childList.length);  //8
alert(childListArray.length);  //8
bodyNode.removeChild(bodyNode.lastChild);
alert(childList.length);  //7
alert(childListArray.length);  //8

节点关系
就是父元素与子元素咯。

bodyNode.childNodes;
bodyNode.parentNode;
bodyNode.previousSibling;
bodyNode.nextSibling;
bodyNode.firstChild;
bodyNode.lastChild;
bodyNode.hasChildNodes();
bodyNode.ownerDocument;

不是每种节点都有子节点哦,有的不能有子节点的。
操作节点
appendChild()用于向childNodes列表结尾添加一个节点。如果传入到这个方法中的节点已经是文档的一部分了,那么该节点就会被移动到新节点。任何DOM节点不能同时出现在多个位置。

//someNode 有多个节点 
var returnedNode = someNode.appendChild(someNode.firstChild); 
alert(returnedNode == someNode.firstChild); //false 
alert(returnedNode == someNode.lastChild); //true

insertBefore()

var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); 
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true

replaceChild()
在替换的过程中,该节点的所有关系指针都会从被它替换的节点复制过来,被替换的节点还在文档中,但是失去了自己的位置。

var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);

removeChild()
同样的,被移除的节点还是归文档所有,没有位置了。

var formerFirstChild = someNode.removeChild(someNode.firstChild);

cloneNode()
这个方法接受一个布尔值表示是否进行深复制,深复制就是复制节点及其子节点树。要注意的是,复制后的节点没有父节点,是孤独的节点。。需要使用上面提到的方法添加。
normalize()
这个方法是处理文档树中的文本节点。

Document类型

document是window的一个属性,是Document的实例。

  • nodeType:9
  • nodeName:"#document"
  • nodeValue:null
  • parentNode:null
  • ownerDocument:null
  • 其子节点可以是:DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction、Comment

文档的子节点

document.documentElement//指向html元素
document.body//指向body元素 
document.doctype;//指向<!DOCTYPE>

文档信息

document.title = "New page title";
document.URL;
document.domain;
document.referrer;//来自哪个页面的URL

其中只有title和domain是可写的。
domain对写入的值是有限制的,只能写入当前域的父域。

//来自p2p.wrox.com  
document.domain = "wrox.com";   //成功
document.domain = "nczonline.net"; //报错
document.domain = "p2p.wrox.com "; //报错

对于包含来自其他子域框架的页面这个很有用,因为来自不同域的页面不能通过JavaScript通信,所以通过将domain设置为同一个父域,就可以实现互相访问对方的JS对象。
查找元素

//这个返回一个元素
document.getElementById("myDiv");
//这个返回元素列表HTMLCollectioin
document.getElementsByTagName("img");
//找到name属性符合的元素。这个返回元素列表HTMLCollectioin
document.getElementsByName("color");
var ul = document.getElementById("myList");
//可以直接在元素里查找
var items = ul.getElementsByTagName("li");

一些特殊集合:

document.anchors
document.applets
document.forms
document.images
document.links

DOM一致性检测
用来检测浏览器实现了DOM的哪些部分。DOM1提供了一个方法。将要检测的功能和版本号传入。

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

不过这个方法并不保险,有的浏览器对没实现的功能也返回true,所以建议对不确定的直接进行能力检测。
文档写入
将输出流写入到网页中的功能。有4个方法:write()、writeln()、open()、close()。

<html>
    <head>
        <title>document.write() Example 3</title>
    </head>
    <body>
        <script type="text/javascript">
            document.write("<strong>" + (new Date()).toString() + "</strong>");
        </script>
    </body>
</html>

在文档加载过程中使用,则会加载内容到当前位置,在文档加载结束后再执行则会覆盖掉整个文档

Element类型

Element就是我们最常用的元素

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

HTML元素
所有HTML元素都是HTMLElement或其子类。HTMLElement直接继承自Element。有这么几个通用属性:

//<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
var div = document.getElementById("myDiv");
alert(div.id); //"myDiv""
alert(div.className); //"bd"
alert(div.title); //"Body text"
alert(div.lang); //"en"
alert(div.dir); //"ltr"

取得特性
getAttribute()、setAttribute()、removeAttribute()

alert(div.getAttribute("id"));
alert(div.getAttribute("class"));
alert(div.getAttribute("title"));
alert(div.getAttribute("lang"));
alert(div.getAttribute("data_myOwnAttr"));
alert(div.data_myOwnAttr); //undefined(除IE)

要注意的是,只有公认的特性才能通过属性名的方式来访问,自定义的特性除IE外的浏览器不会为他们创建属性。
对于style这个属性,直接访问属性得到的是一个对象,便于访问各个样式;而getAttribute("style")则返回字符串。
对于onclick这样的事件处理特性,直接访问属性得到的是一个函数,getAttribute()则返回字符串。
所以在访问非自定义属性时推荐使用直接访问元素属性的方法。
设置特性

div.setAttribute("id", "someOtherId");
div.id = "someOtherId";

同样,通过属性来设置自定义特性是无效的。
删除特性

div.removeAttribute("class");

attributes属性
只有Element类型有这个属性。这个属性中包含一个NameNodeMap。元素的每一个特性由一个Attr节点表示,每个节点都保存在NameNodeMap对象中。

//获取特性
var id = element.attributes.getNamedItem("id").nodeValue;
var id = element.attributes["id"].nodeValue;
//修改特性
element.attributes["id"].nodeValue = "someOtherId";
var oldAttr = element.attributes.removeNamedItem("id");
element.attributes.setNamedItem(newAttr);

创建元素

var div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
//在IE中也可以这样
var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >");

遍历子节点

<ul id="myList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
</ul>
<ul id="myList"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>

对于上面的两个ul元素childNodes包含的数目可能不同,IE外的浏览器对第一个会包含3个li和4个文本节点。如果你真的要遍历,你可以在遍历的同时判断下节点的类型。

Text类型

包含纯文本内容

  • nodeType:3
  • nodeName:"#text"
  • nodeValue:文本内容
  • parentNode:Element
  • ownerDocument:Document
  • 无子节点
<div>Hello World!</div>
var textNode = div.firstChild;
//获取或设置文本的值
div.firstChild.nodeValue = "Some other message";
div.firstChild.data = "Some other message";

创建文本节点

var element = document.createElement("div");
element.className = "message";
//这里的大于小于号之类的会被转义,不会被解释为元素标签
var textNode = document.createTextNode("<strong>Hello</strong> world!");
element.appendChild(textNode);
//多个相邻文本节点会被拼接
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
//多个相邻节点导致混乱
alert(element.childNodes.length);    //2
//这个方法合并相邻文本节点
element.normalize();
alert(element.childNodes.length);    //1
alert(element.firstChild.nodeValue);

分割文本节点
被分割出来的新节点于原来的有相同的父节点。

var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
var newNode = element.firstChild.splitText(5);

其他方法
appendData(text)
deleteData(offset, count)
insertData(offset, text)
replaceData(offset, count, text)
substringData(offset, count)
length
这些方法都是用来获取或修改文本节点中的内容的。

Comment类型

在DOM中的注释通过这个类型来表示

  • nodeType:8
  • nodeName:"#comment"
  • nodeValue:注释文本内容
  • parentNode:Document或Element
  • ownerDocument:Document
  • 无子节点
  • 与text类型有相同的基类,所以上面的字符串方法它也有
<div id="myDiv"><!--A comment --></div>
var div = document.getElementById("myDiv");
var comment = div.firstChild;
alert(comment.data); 

CDATASection类型

DocumentType类型

DocumentFragment类型

这个类型在文档中没有对应的标记,是一个轻量级的文档,可以包含和控制节点。它不能被直接添加到文档中,但是可以作为一个仓库来使用,在里面保存将来会用到的节点。

  • nodeType:11
  • nodeName:"#document-fragment"
  • nodeValue:null
  • parentNode:null
  • ownerDocument:Document
  • 子节点可以是Element、ProcessingInstruction、Comment、Text、CDATASection、EntityReference

Node的各种DOM节点操作方法文档片段都继承了。
文档片段的一个使用场景就是当我们循环添加节点时,如果直接将节点添加到文档中,那么每次添加都会导致浏览器的重新渲染。如果我们先把要循环的节点添加到一个文档片段中,拼好了再添加到文档中呢,这个问题就解决了。

var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
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);

Attr类型

元素的特性在DOM中以Attr类型来表示。从技术角度讲,特性就是存在于元素的attributes中的节点。尽管它们是节点,但不认为它们是DOM文档树的一部分。

var ul = document.getElementById("myList");
//特性节点不会出现在文档树中
alert(ul.childNodes.length);
alert(ul.attributes["class"].value);
//alert(ul.attributes["align"].value);会报错
//添加特性节点
var attr = document.createAttribute("align");
attr.value = "left";
ul.setAttributeNode(attr);
alert(ul.attributes["align"].value);

一般只使用之前提到的getAttribute()、setAttribute()、remveAttribute()方法,很少直接引用特性节点。

DOM操作技术

动态脚本

指的是页面加载时并不存在,但将来的某一时刻通过修改DOM动态添加的脚本。动态添加同样有两种方式,添加外部文件和行内代码。
外部文件:

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

脚本加载完成就可以使用了,但是这里我们并不能判断是否加载完成了,需要借助一些事件来判断。
行内代码:

function loadScriptString(code){
    var script = document.createElement("script");
    script.type = "text/javascript";
    try {
    //由于IE将script视作一个特殊的元素,不允许DOM访问其子节点,这样在IE中会报错
        script.appendChild(document.createTextNode(code));
    } catch (ex){
    //直接设置text属性有的浏览器不支持,但IE肯定支持
        script.text = code;
    }
    document.body.appendChild(script);
}
loadScriptString("function sayHi(){alert('hi');}");

动态样式

外部文件:

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("styles.css");

内联样式:
IE同样有BUG

function loadStyleString(css){
    var style = document.createElement("style");
    style.type = "text/css";
try{   
    style.appendChild(document.createTextNode(css));
    } catch (ex){
        style.styleSheet.cssText = css;
    }
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(style);
}
loadStyleString("body{background-color:red}");

操作表格

DOM为表格添加了一些属性和方法,以便我们可以更好的操作表格。
table元素的:
caption
tBodies
tFoot
tHead
rows
createTHead()
createTFoot()
createCaption()
deleteTFoot()
deleteCaption()
deleteRow(pos)
insertRow(pos)
body元素的:
rows
deleteRow(pos)
insertRow(pos)
cells
deleteCell(pos)
insertCell(pos)

使用NodeList

NodeList以及其近亲NamedNodeMap和HTMLCollection都是动态的,也就是说每当文档发生变化时,对他们的查询始终返回最新的信息。这也就导致了一些问题,

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

在这段代码中每次增加一个div节点,divs中的元素也会同时增加一个,divs.length永远会比当前的循环大。

// 这样就不会有问题了
var divs = document.getElementsByTagName("div"),
    i,
    len,
    div;
var divtemp = divs;
for (i=0, len=divs.length; i < len; i++){
    div = document.createElement("div");
    document.body.appendChild(div);
}

这也提醒了我们,使用nodeList要谨慎,因为每次使用都会引起一次查询。

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

推荐阅读更多精彩内容