第10章 - DOM

DOM 就是文档的数据结构,它提供了操作文档的编程接口 API。

10.1 节点层次

文档是由节点组成的树形结构,根节点是文档节点 Documet ,其下是 html 元素节点。

10.1.1 Node 类型

节点的类型

总共有 12 种类型的节点,使用 Node 类可以判断一个节点的类型。注意:在 IE 中只能使用数值,不能使用常量。

常量 说明
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 == 1){ //使用数值可以在所有浏览器下工作
    alert("Node is an element.");
}

节点的信息

每个节点都有 nodeType 、nodeNamenodeValue 三个属性,可以了解节点的信息。

if (someNode.nodeType == 1){
    value = someNode.nodeName; //将会返回标签名
}

不同节点各属性的含义

不同类型的节点,nodeNamenodeValue 的意义不一样,因此,一般情况下要先判断节点类型。

元素节点各属性的含义

名称 说明
nodeName 元素的标签名称,等同之前的 tagName
nodeType 1
nodeValue 元素节点没有值,所以返回 null

属性节点各属性的含义

名称 说明
nodeName 属性名称
nodeType 2
nodeValue 属性值

文本节点各属性的含义

名称 说明
nodeName #text
nodeType 3
nodeValue 文本内容(不包含 html)

节点的关系

节点的层次结构可以划分为:父子节点、 兄弟节点这两种。 利用节点的层次结构可以获取节点树上的任意其他节点

属性 说明
childNodes 获取当前元素节点的所有子节点,返回一个 NodeList 对象
parentNode 获取当前节点的父节点
previousSibling 获取当前节点的前一个同级节点,第一个节点会返回 null
nextSibling 获取当前节点的后一个同级节点,最后一个节点会返回 null
firstChild 获取当前元素节点的第一个子节点
lastChild 获取当前元素节点的最后一个子节点
ownerDocument 获取该节点的文档根节点,相当于文档对象 document
hasChildNodes 判断是否有子节点

子节点列表

获取字节的的属性 childNodes 是个列表对象,可以像数组一样访问它

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

也可以用数组的 slice 方法将 childNodes 对象转换为数组

//在 IE8 之前不工作
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);

节点的增删改查

方法 说明
appendChild 将新节点追加到子节点列表的末尾,返回添加的节点
insertBefore 将新节点插入在某个节点的前面,返回被插入的节点
repalceChild 将新节点替换旧节点,返回被替换的节点
removeChild 移除节点,返回被移除的节点
cloneNode 克隆一个节点,可以深克隆,也可以浅克隆

添加节点

var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true

如果被添加的节点是已经文档中的节点,那么节点将被移动位置

//将第一个节点移动到最后
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true

插入节点

//位置参数为 Null,则插入到最后
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true

//插入到第一个
returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true

//插入到倒数第二个
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true

深克隆和浅克隆

var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3 (IE < 9) or 7 (others)

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

10.1.2 Document 类型

文档对象代表整个页面文档,是 DOM 的根节点,它是只读的,因此不能添加、删除、替换子节点。其下一般只有惟一一个 html 子节点,是系统自动创建的

属性
nodeType 9
nodeName "#document"
nodeValue null
parentNode null
ownerDocument null
子节点 可以有一个 DocumentType 一个 Element 或者一个注释等等

文档对象的属性

除了具有普通节点的属性和方法之外,document 对象还有一些快捷属性

属性 说明
document.documentElement 返回文档包含的子节点,指向 html 元素
document.body 指向 body 元素
document.doctype 指向 doctype 元素,各个浏览器对这个属性不一致,因此不常用
document.title 返回文档标题
document.URL 返回文档的链接
document.referrer 返回链接到当前页面的那个页面的链接,可能为空
document.domain 返回域名,不能设置为 URL 中不包含的域

document.documentElement 的例程

var html = document.documentElement; //返回 <html> 元素对象
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true

文档对象的域

修改域的时候,只能修改子域

//page from p2p.wrox.com
document.domain = "wrox.com"; //成功
document.domain = "nczonline.net"; //不在同一个域中,出错!

设置为同样的主域名,不同页面之间的对象可以进行通信

//page from p2p.wrox.com
document.domain = "wrox.com"; //可以通信
document.domain = "p2p.wrox.com"; //这样不能通信

查找元素

利用文档对象可以查找元素对象。
注意,必须等文档加载完毕,才能查找和操作 DOM 操作对象,可以将相关代码放在 onload 事件中

方法 说明
document.getElementsById 返回一个元素
document.getElementsByTagName 返回同一种标签的集合,可以用通配符
document.getElementsByName 返回有相同名字的元素集合,可以用通配符

元素集合

getElementsByTagName 和 getElementsByName 这两个查找方法会返回元素的集合

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

alert(images.length); //output the number of images
alert(images[0].src); //output the src attribute of the first image
alert(images.item(0).src); //output the src attribute of the first image

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

特殊集合的快捷访问

文档对象有几个属性,可以直接获取某些元素集合

属性 说明
document.anchors 锚点集合
document.applets 小程序集合
document.forms 表单集合
document.images 图像集合
document.links 链接集合

DOM 一致性检测

DOM 本身分很多级别,不同浏览器实现的程度也有所不同,可以用 document.implementation.hasFeature 方法检测

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

下面的表格时可以进行检测的功能和版本号

功能 版本号 说明
Core 1.0, 2.0, 3.0
XML 1.0, 2.0, 3.0
HTML 1.0, 2.0
Views 2.0
StyleSheets 2.0
CSS 2.0
CSS2 2.0
Events 2.0, 3.0
UIEvents 2.0, 3.0
MouseEvents 2.0, 3.0
MutationEvents 2.0, 3.0
HTMLEvents 2.0
Range 2.0
Traversal 2.0
LS 3.0
LS-Async 3.0
Validation 3.0

文档写入

document.write方法可以向文档写入内容,有两个需要注意的地方

  • 包含 "</script>" 这个特殊字符串的时候要转义一下
  • 当文档加载完毕后调用 document.write方法,将重写整个文档
document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");

10.1.3 Element 类型

元素对象是网页中最基本的对象

属性
nodeType 1
nodeName 元素的标签名
nodeValue null
parentNode null
ownerDocument Document 或者 Element
子节点

获取元素对象的标签名

除了用节点的 nodeName 之外,也可以直接用 tagName 属性获取标签名

if (element.tagName == "div"){ //AVOID! Error prone!
    //do something here
}

if (element.tagName.toLowerCase() == "div"){ //Preferred - works in all documents
    //do something here
}

元素的属性

元素对象的所有属性都可以读取和设置

属性 说明
tagName 获取元素节点的标签名,一般返回大写字符串
innerHTML 获取元素节点里的内容,非 W3C DOM 规范,但是个浏览器都支持
id 元素节点的 id 名称
title 元素节点的 title 属性值
style CSS 内联样式属性值
className CSS 元素的类,因为 class 是关键字,所以使用 className
bbb 不支持自定义属性,
onclick 返回事件函数的代码

元素的特性

通过特性除了可以访问元素的属性之外,还可以:

  • 自定义特性,前缀为 data- ,自定义特性名都被转换为小写
  • 通过属性访问 style 返回的是对象,通过特性访问 style 返回字符串
  • 通过属性访问事件,返回函数对象,通过特性访问事件,返回字符串

特性的相关方法

  • getAttribute() 方法
  • setAttribute() 方法
  • removeAttribute() 方法

特性对象数组 attributes

attributes 是包含特性对象的键值对数组,可以通过名字或者索引号获取其中的对象

attrName = element.attributes[i].nodeName;

element.attributes["id"].nodeValue = "someOtherId";

可以通过特性对象数据增删改查元素的特性,但是相关方法并不方便,不如直接使用元素对象的特性相关方法。
所以,一般情况下不使用特性,只在枚举特性和自定义特性的情况下使用

动态创建元素对象

可以用 document.createElement() 方法动态创建元素对象:

  • 通常情况下传入标签名
  • 对于老式浏览器,传入 HTML 代码
var div = document.createElement("div");

var div2 = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div>");

div.id = "myNewDiv";
div.className = "box";

document.body.appendChild(div);

不同浏览器对空白的不同解释

大部分浏览器都将子元素之间的换行符也当做一个文本元素子节点;IE 浏览器只将子元素作为子节点,忽略换行符。
因此,统计子节点时可以对节点类型进行一个判断

for (var i=0, len=element.childNodes.length; i < len; i++){
    if (element.childNodes[i].nodeType == 1){
        //do processing
    }
}

10.1.4 Text 类型

文本节点一般是元素对象中的文字内容,本身也是一个节点

属性
nodeType 3
nodeName #text
nodeValue 文本内容
parentNode 包含该文本的元素节点
子节点

下面是文本节点的增删改查方法及相关属性

属性或方法
appendData(text) 添加
deleteData(offset, count) 删除
insertData(offset, text) 插入
replaceData(offset, count, text) 替换
splitText(offset) 切分
substringData(offset, count) 子串
length 长度

元素最多只能包含一个文本节点,而且必须有内容存在

下面代码中空格也是一个文本节点

<!-- 空格也是一个文本节点 -->
<div> </div>

给文本节点设置新的内容,特殊字符会自动被转义

//输出结果是: "Some <strong>other</strong> message"
div.firstChild.nodeValue = "Some <strong>other</strong> message";

创建文本节点

document.createTextNode() 方法可以创建文本节点

var element = document.createElement("div");
element.className = "message";

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

document.body.appendChild(element);

规范化文本节点

在一个元素内可以添加多个文本节点,规范化方法 element.normalize() 可以将这些文本节点合并起来

var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello 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); //"Hello world!Yippee!"

切分文本节点

切分文本节点可以将文本从指定位置处切分,原文本节点保留切分点之前内容,返回一个新文本节点包含切分点之后的内容

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);

alert(element.firstChild.nodeValue); //"Hello"
alert(newNode.nodeValue); //" world!"
alert(element.childNodes.length); //2

10.1.5 Commet 类型

注释类型和文本类型有同样的父类,这个类型在实际项目中用处不大,可以忽略。

10.1.6 CDATASection 类型

该类型一般只出现在 XML 文档中,对 HTML 文档用处不大,可以忽略。

10.1.7 DocumentType 类型

该类型对象不能动态创建,只能通过 document.doctype 获取

10.1.8 DocumentFragment 类型

  • 有时候需要一次在文档树中添加多个节点,会导致页面频繁刷新。
  • 这时候可以创建一个 DocumentFragment 对象,这个对象和文档中的 Node 一样,但是不会刷新页面,也不会显示。
  • 将需要添加的节点添加到文档碎片对象之下,添加完毕后,再将文档碎片对象添加到文档中的目标节点下。这样,文档碎片对象的子节点会自动成为目标节点的子节点
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);

10.1.9 Attr 类型

Attr 类型就是特性对象的类型,attributes 属性中的对象都是特性对象。特性节点不出在 DOM 树中,但是可以承载信息。

特性对象有三个常用属性:name、value 和 specified ,其中 specified 用来确定特性是默认值还是用户设置的值

document.createAttribute() 方法可以创建特性节点

var attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr);

alert(element.attributes["align"].value); //"left"
alert(element.getAttributeNode("align").value); //"left"
alert(element.getAttribute("align")); //"left"

10.2 DOM 操作技术

10.2.1 动态脚本

动态加载外部脚本

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

loadScript("client.js");

动态生成内联脚本

创建脚本节点对象以后,可以为脚本节点对象在创建一个文本子节点,其中包含代码。但是 IE不支持这种方法,可以直接赋值给 script.text

function loadScriptString(code){
    var script = document.createElement("script");
    script.type = "text/javascript";

    try {
        //适用于现代浏览器
        script.appendChild(document.createTextNode(code));
    } catch (ex){
        //适用于 IE
        script.text = code;
    }

    document.body.appendChild(script);
}


loadScriptString("function sayHi(){alert(‘hi’);}");

10.2.2 动态样式

动态加载外部样式表

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");

动态加载内联样式表

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}");

10.2.3 操作表格

如果用普通的节点方法动态操作表格非常麻烦,下面是一个表格的 HTML 代码

<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>

用普通节点方法像下面这样

//创建表格对象
var table = document.createElement("table");
table.border = 1;
table.width = "100%";

//创建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);

//创建第一行
var row1 = document.createElement("tr");
tbody.appendChild(row1);

var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);

var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);

//创建第二行
var row2 = document.createElement("tr");
tbody.appendChild(row2);

var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);

var cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);

//加入到文档中
document.body.appendChild(table);

其实表格对象有一些自己特定的属性和方法

属性或方法 说明
caption
tBodies
tFoot
tHead
rows
createTHead()
createTFoot()
createCaption()
deleteTHead()
deleteTFoot()
deleteCaption()
rows
deleteRow(pos)
insertRow(pos)
cells
deleteCell(pos)
insertCell(pos)

利用这些属性和方法创建表格相对简单一点

//创建表格
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 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);

10.2.4 使用 NodeList

查询获取的 NodeList 数组是动态的,只要文档发生了变化, NodeList 也发生对应的变化。下面的代码会死循环

var divs = document.getElementsByTagName("div"),
        i,div;

for (i=0; i < divs.length; i++){
    //创建新的 div 元素导致 NodeList 的长度动态发生变化,造成死循环
    div = document.createElement("div");
    document.body.appendChild(div);
}

为了达到上述代码想实现的目标,同时避免死循环,可以在循环之前获取 NodeList 的长度

var divs = document.getElementsByTagName("div"),
        i,len,div;

//用临时变量存储 NodeList 的长度,这样去不受动态变化的影响
len=divs.length;

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

推荐阅读更多精彩内容