JavaScript小特性(2) – DOM

上一篇讲的主要是JavaScript语言的核心部分,这次来讲讲DOM(Document Object Model)。

JavaScript允许修改调整html的文档结构,可以添加删除元素,修改显示样式,修改内容等等,这个功能是由浏览器的一个DOM API提供的(不同浏览器会有不同的实现,但大致相近)。

DOM,就是“文档对象模型”,它是一个独立于平台和语言的接口,允许程序动态的存取、修改文档的内容、结构及样式。

HTML文档的每一个东西(标签、属性、文本、注释)都是一个节点,这些节点之间有层次的关系,构成一棵节点树(有些古老的网页,可能并不是结构良好的html,例如标签不闭合啥的,但是浏览器会根据它自己的理解来建立这棵节点树)。

JavaScript访问DOM是通过document这个对象,它是所有DOM方法的入口。

 

1、寻找节点

在这个节点树中寻找节点有以下一些方法:

长途方法,这个好理解,不多说,可惜没有getElementByClass)

getElementById(id), getElementsByTagName(name)

短途方法,左边是标准方法,右边的是不包括文本节点的元素集合,但非W3C标准,旧版浏览器不支持)

parentNode

firstNode // firstElementChild

lastChild // lastElementChild

previousSibling // previousElementSibling

nextSibling // nextElementSibling

childNodes[] // children[]

选择器API,现代浏览器多数已经原生实现,效率较高,但使用前要先检验是否支持)

querySelectorAll(‘#menu a’)//根据css选择器获取元素集合

querySelector(‘#menu a’)//等价于querySelectorAll(‘#menu a’)[0]

 

短途方法的问题(空文本节点的影响):

我们看看这个例子。

HTML代码:

<div id="test">
    <!-- hello world -->
    <p>Element:1</p>
    hello world
    <p>Attribute:2</p>

    <p>Text:3</p><p>Comment:8</p><p>Document:9</p>
</div>

JavaScript代码:

var test = document.getElementById("test");
var nodes = test.childNodes;
//test.children 则不受文本节点影响
//但是不是每个浏览器都支持,使用前要做对象检验
for(var i=0,len=nodes.length;i<len;i++){
    alert(nodes[i].nodeName + ':' + nodes[i].nodeType);
}

输出依次为:

  • #text:3(div和第一个注释之间的空文本节点,包括空格、换行符等)
  • #comment:8(hello world 注释)
  • #text:3(空文本节点)
  • P:1
  • #text:3(文本节点 hello world)
  • P:1
  • #text:3(段落2和段落3之间的空文本节点)
  • P:1(因为后面三个P是连在一起的,所以之间没有空文本节点)
  • P:1
  • P:1
  • #text:3

由于短途方法会把文本节点、注释节点都算进去,所以很可能我们获取的节点不是我们想要的节点,所以使用时必须判断清楚节点的类型。

 

2、修改/创建节点

W3C DOM提供以下一些方法:

(修改方法)

appendChild(), insertBefore(), removeChild(), replaceChild

(创建方法)

createElement(), createTextNode(), cloneNode()

//cloneNode不会克隆事件处理程序,记得要给克隆的节点重新注册事件

 

innerHTML方法(好理解,不多说):

直接修改文档的html内容,虽然不是W3C DOM规范,但是却有着几乎完美的浏览器兼容性。

 

我们对两种方法进行一下对比,动态的生成一个无序列表:

innerHTML方法(7行)

var msg = [1,2,3,4,5,6,7,8,9,10 ]
var test = document.getElementById("test");
var content = "";
for(var i=0,len=msg.length; i<len; i++){
    content+="<li>"+msg[i]+"</li>";
}
test.innerHTML += content;

W3C DOM方法(10行)

var msg = [1,2,3,4,5,6,7,8,9,10 ]
var test = document.getElementById("test");
var root = document.createElement("ul");
for(var i=0,len=msg.length;i<len;i++){
    var newLi = document.createElement("li");
    var newMsg = document.createTextNode(msg[i]);
    newLi.appendChild(newMsg);
    root.appendChild(newLi);
}
test.appendChild(root);

如果动态内容复杂的话,W3C DOM方法的代码量将大大增加,但是W3C DOM方法比起innerHTML来说,更容易阅读,也不容易导致书写错误(如标签、引号不匹配等),效率上一般innerHTML要优于W3C DOM方法,可根据具体情况与个人偏好进行选择(一般创建大的页面结构推荐用innerHTML)。

 

3、节点列表(NodeList)

说到W3C DOM的访问与修改,不得不提一下“节点列表”这个概念。

某些W3C DOM方法返回的是一个集合,这个集合不是普通的数组,而是一个NodeList,即满足某种筛选条件的所有节点的列表,例如getElementsByTagName()返回的就是一个NodeList。

NodeList也是通过[index](方括号+索引)进行访问,也有length属性,但他是只读的,我们不可以直接对其进行修改(会报错的~),例如:

var test = document.getElementById('test');
var ps = test.getElementsByTagName('p');
var newP = document.createElement('p');
newP.appendChild(document.createTextNode('hello'));
ps[3] = newP;//没反应或者报错~

虽然NodeList是只读的,但它不是静态的(这是一个危险的特性~),当你修改文档的时候,它会立刻反应文档中的变化,例如:

var test = document.getElementById('test');
var ps = test.getElementsByTagName('p');
alert(ps.length);

var newP = document.createElement('p');
newP.appendChild(document.createTextNode('hello'));
test.appendChild(newP)
alert(ps.length);//NodeList会自动更新

为什么说这是一个危险的特性呢?例如:

var divs = document.getElementsByTagName('div');
for(var i=0; i<divs.length; i++){  //这是一个死循环
    document.body.appendChild(document.createElement('div'));
}

般不要将NodeList作为循环或者判断的条件,就算将其用于循环,也尽量避免在循环体中增加或者删除这些元素,避免造成诡异的Bug。

当需要遍历节点列表的时候,最好将其Copy到一个普通数组中:

function toArray(nodeList){
    var arr = [];
    for(var i=0,len=nodeList.length; i<len; i++){
        arr[i] = nodeList[i];
    }
}

 

4、表单与0级DOM

0级DOM,虽然不是W3C标准的一部分,但它是Netscape 3的事实标准,所有的浏览器都能够支持。在表单的处理上,0级DOM给我们带来了很多的方便。

document.form[] 等同于 document.getElementsByTagName(‘form’),但前者可以通过Name和ID访问到相应的表单

每个表单都有一个elements[]的NodeList,包含该表单中的所有表单域(W3C DOM没有相似的实现,这个属性非常有用),我们可以通过索引或者Name访问elements中的表单域。例如:

<form name="sayHello">
    <input type="text" name="toWho" value="World"/>
</form>
var toWho= document.forms['sayHello'].elements[0];
alert(world.value);

每种类型表单域都有一些自己的属性,但下面5个属性是共同的:

type, form, disabled, name, value //其中,form属性指向它所属的表单对象

各种表单域的特性我就不详述了。

 

5、DOM超空间(DOM hyperspace)

直接举例吧:

<div id="root">
    <p id="test">我在学习JavaScript!</p>
</div>
var root = document.getElementById("root");
var test = document.getElementById("test");
root.removeChild(test); //虽然remove了,但还存在于超空间中
test.innerHTML += "我刚才去DOM超空间逛了一回~";
root.appendChild(test);

我们可以利用超空间缓存一些暂时不用的元素,在需要的时候重新插入文档,这要求一个变量或者数组继续指向这些元素,使之不会消失。

 

发布者

Rolf

伪文艺IT攻城师,热爱前端,热爱互联。

《JavaScript小特性(2) – DOM》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

[喜欢] [嘻嘻] [奋斗] [问号] [鼓掌] [泪] [酷] [强] [耶] [握手] [心] [给力] [神马] [围观] [奥特曼] more »