DOM
由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。
始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:
- 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
- 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
- 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
- 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。
在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()和document.getElementsByTagName(),以及CSS选择器document.getElementsByClassName()。
由于ID在HTML文档中是唯一的,所以document.getElementById()可以直接定位唯一的一个DOM节点。document.getElementsByTagName()和document.getElementsByClassName()总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
// 返回ID为'test'的节点:
var test = document.getElementById('test');
// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
var trs = document.getElementById('test-table').getElementsByTagName('tr');
// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
var reds = document.getElementById('test-div').getElementsByClassName('red');
// 获取节点test下的所有直属子节点:
var cs = test.children;
// 获取节点test下第一个、最后一个子节点:
var first = test.firstElementChild;
var last = test.lastElementChild;
第二种方法是使用querySelector()和querySelectorAll(),需要了解selector语法,然后使用条件来获取节点,更加方便:
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
更新DOM
可以直接修改节点的文本,有两种方法:
一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置文本为abc: p.innerHTML = 'ABC'; // <p id="p-id">ABC</p> // 设置HTML: p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ'; // <p>...</p>的内部结构已修改
用innerHTML时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免XSS攻击。
第二种是修改innerText或textContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置文本: p.innerText = '<script>alert("Hi")</script>'; // HTML被自动编码,无法设置一个<script>节点: // <p id="p-id"><script>alert("Hi")</script></p>
两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本。注意IE<9不支持textContent。
修改CSS也是经常需要的操作。DOM节点的style属性对应所有的CSS,可以直接获取或设置。因为CSS允许font-size这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize:
// 获取<p id="p-id">...</p> var p = document.getElementById('p-id'); // 设置CSS: p.style.color = '#ff0000'; p.style.fontSize = '20px'; p.style.paddingTop = '2em';
插入DOM
当我们获得了某个DOM节点,想在这个DOM节点内插入新的DOM,应该如何做?
如果这个DOM节点是空的,例如,
,那么,直接使用innerHTML = ‘ child‘就可以修改DOM节点的内容,相当于“插入”了新的DOM节点。如果这个DOM节点不是空的,那就不能这么做,因为innerHTML会直接替换掉原来的所有子节点。
有两个办法可以插入新的节点。
一个是使用appendChild,把一个子节点添加到父节点的最后一个子节点。例如:
<!-- HTML结构 --> <p id="js">JavaScript</p> <div id="list"> <p id="java">Java</p> <p id="python">Python</p> <p id="scheme">Scheme</p> </div>
例如添加到最后一项:
var
js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);
现在,HTML结构变成了这样:
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>
因为我们插入的js节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。
更多的时候我们会从零创建一个新的节点,然后插入到指定位置:
var
list = document.getElementById('list'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
这样我们就动态添加了一个新的节点:
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="haskell">Haskell</p>
</div>
动态创建一个节点然后添加到DOM树中,可以实现很多功能。举个例子,下面的代码动态创建了一个
insertBefore
如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);,子节点会插入到referenceElement之前。
还是以上面的HTML为例,假定我们要把Haskell插入到Python之前:
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
可以这么写:
var
list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);
新的HTML结构如下:
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p>
<p id="haskell">Haskell</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
可见,使用insertBefore重点是要拿到一个“参考子节点”的引用。很多时候,需要循环一个父节点的所有子节点,可以通过迭代children属性实现:
var
i, c,
list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i]; // 拿到第i个子节点
}
删除DOM
删除一个DOM节点就比插入要容易得多。
要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉:
// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
当你遍历一个父节点的子节点并进行删除操作时,要注意,children属性是一个只读属性,并且它在子节点变化时会实时更新。
例如,对于如下HTML结构:
<div id="parent">
<p>First</p>
<p>Second</p>
</div>
当我们用如下代码删除子节点时:
var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // <-- 浏览器报错
浏览器报错:parent.children[1]不是一个有效的节点。原因就在于,当
First
节点被删除后,parent.children的节点数量已经从2变为了1,索引[1]已经不存在了。因此,删除多个节点时,要注意children属性时刻都在变化。
Node类型
每个节点都有一个nodeType属性,用于表明节点的类型。
+ nodeName和nodeValue属性:要了解节点的具体信息,用这两个属性。对于元素节点,nodeName中保存的始终是元素的标签名,nodeValue的值始终是null。
+ 每个节点都有一个childNodes属性,其中保存着一个NodeList对象。
下面的例子展示如何访问保存在NodeList中的节点:
var frstChild=someNode.childNodes[0];
var secondChild=someNode.childNodes.item(1);
var count=someNode.childNodes.length;
可将NodeList对象转化成数组:
var arrayOfNodes=Array.prototype.slice.call(someNode.childNodes,0);
- appendChild()用于向childNode列表的末尾添加一个节点。
- insertBefore(),可以将节点插入指定位置,接收2个参数:要插入的节点和作为参照的节点。
- replaceChild(),可替换节点,接收2个参数:要插入的节点和要替换的节点。
- removeChile(),可移除节点,接收1个参数:要移除的节点。
- cloneNode(),用于创建调用这个方法的节点的一个完全相同的副本。接收一个布尔值参数,表示是否执行深复制。
- normalize(),处理文档树中的文本节点。
Document类型
document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。
document的title属性是从HTML文档中的xxx读取的,但是可以动态改变:
document.title = '努力学习JavaScript!';
要查找DOM树的某个节点,需要从document对象开始查找。最常用的查找是根据ID和Tag Name。
我们先准备HTML数据:
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
<dt>摩卡</dt>
<dd>热摩卡咖啡</dd>
<dt>酸奶</dt>
<dd>北京老酸奶</dd>
<dt>果汁</dt>
<dd>鲜榨苹果汁</dd>
</dl>
用document对象提供的getElementById()和getElementsByTagName()可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点:
- getElementById()接收一个参数:要取得的元素的ID
getElementsByTagName()接收一个参数:要取得的元素的标签名
var menu = document.getElementById(‘drink-menu’);
var drinks = document.getElementsByTagName(‘dt’);
var i, s, menu, drinks;menu = document.getElementById(‘drink-menu’);
menu.tagName; // ‘DL’drinks = document.getElementsByTagName(‘dt’);
s = ‘提供的饮料有:’;
for (i=0; i
Element类型
- nodeType值为1
- nodeName的值为元素的标签名
- nodeValue的值为null
Element类型用于表现XML或HTML元素,提供了对元素标签名,子节点及特性的访问。
要访问元素的标签名,可以使用nodeName或tagName属性。
操作特性的DOM方法主要有3个:getAttribute(),setAttribute(),removeAttribute()。这三个方法可以针对任何特性使用。
+ getAttribute()取得特性:传递给它的特姓名与实际的特姓名相同。
+ setAttribute()设置特性,接收2个参数:要设置的特姓名和值。通过这个方法设置的特姓名会被统一换成小写行驶
+ removeAttribute():彻底删除元素的特性。
attributes属性
- getNamedItem(name):返回nodeName属性等于name的节点。
- removeNamedItem(name):从列表中移除nodeName属性等于name的节点。
- setNamedItem(node):向列表中添加节点,为索引。
attributes属性中包含一系列节点,每个节点的nodeName就是节点名称,节点nodeValue就是特性的值。
如果想要遍历元素的特性,attributes属性会方便一些:
function outputAttributes(element){
var pairs = new Array(),
attrName,
attrValue,
i,
len;
for (i=0, len=element.attributes.length; i < len; i++){
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
pairs.push(attrName + "=\"" + attrValue + "\"");
}
return pairs.join(" ");
}
function getDivAtts(){
alert(outputAttributes(document.getElementById("myDiv")));
}
创建元素
document.createElement()可以创建新元素,接收一个参数:要创建元素的标签名。
function createNewElement(element){
var div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
}
元素的子节点
元素的childNodes属性中包含了它的所有子节点。
Text类型
- nodeType的值为3
- nodeName的值为”#text”;
- nodeValue的值为节点所包含的文本;
- parentNode是一个Element;
- appendDate(text):将text添加到节点的末尾。
- deletDate(offset,count):从offset指定的位置开始删除count个字符。
- insertDate(offset,text):在offset指定的位置插入text。
- replaceDate(offset,count,text):用text替换从offset指定的位置开始到offset+count为止处的文本。
- splitText(offset):从offset指定的位置将当前文本节点分成两个文本节点。
substringDate(offset,count):提取从offset指定的位置开始到offset+count为止处的字符串。
function changeText(){ var div = document.getElementById("myDiv"); div.firstChild.nodeValue = "Some other message"; }
创建文本节点
可以用document.createTextNode()创建新文本节点,接收一个参数:要插入节点中的文本。在创建新节点的同时,也会为其设置ownerDocument属性。
只有把新节点添加到文档树中已经存在的节点中,才会在浏览器窗口中看到新节点。
function addNode(){
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
}
某些情况下也可能包含多个文本子节点:
function addNode(){
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);
}
规范化文本节点
normalize(),如果在一个包含多个文本节点的父元素调用该方法,会将所有文本节点合并成一个节点,结果节点的nodeValue等于将合并前每个文本节点的nodeValue值拼接起来的值。
function addNode(){
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!"
}
分割文本节点
splitText(),此方法会将一个文本节点分成两个文本节点,即按照指定的位置分割nodeValue值。
function addNode(){
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
}
Comment类型
- nodeType的值为8
- nodeName的值为”#comment”;
- nodeValue的值是注释的内容;
parentNode可能是Document或Element
function getComment(){ var div = document.getElementById("myDiv"); var comment = div.firstChild; alert(comment.data); }
DocumentType类型
- nodeType的值为10
- nodeName的值为doctype的名称;
- nodeValue的值null;
- parentNode是Document.
此对象的3个属性:
* name:表示文档类型的名称
* entities:由文档类型描述的实体的NameNodeMap对象
* notations:由文档类型描述的符号的NameNodeMap对象
DocumentFragment类型
- nodeType的值为11
- nodeName的值为”#document-fragment”;
- nodeValue的值null;
- parentNode是null.
虽然不能把文档片段直接添加到文档中,但可以作为一个“仓库”使用
function addItems(){
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类型表示。
- nodeType的值为12
- nodeName的值为特性的名称;
- nodeValue的值是特性的值;
- parentNode是null.
Attr对象有3个属性:
+ name:特性名称
+ value: 特性的值
+ specified:布尔值,区别特性在代码中是指定的还是默认的
使用document.createAttribute()并传入特性的名称可以创建新的特性节点。
function assignAttribute(){
var element = document.getElementById("myDiv");
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"
}
动态脚本
创建动态脚本有两种方法:
- 插入外部文件
- 直接插入js代码
可以使用script元素的text属性来指定javascript代码:
function addScript(){
var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);
sayHi();
}
IE中,用到下面的方法:
function loadScriptString(code){
var script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(code));
} catch (ex){
script.text = code;
}
document.body.appendChild(script);
}
function addScript(){
loadScriptString("function sayHi(){alert('hi');}");
sayHi();
}
动态样式
把css样式包含到html页面中的元素有2个:
- link元素用于包含来自外部的文件
style元素用于指定嵌入的样式
function addStyle(){ var style = document.createElement("style"); style.type = "text/css"; style.appendChild(document.createTextNode("body{background-color:red}")); //error in IE var head = document.getElementsByTagName("head")[0]; head.appendChild(style); }
通用的解决方法:
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);
}
function addStyle(){
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集合中的指定位置插入一个单元格,返回对新插入单元格的引用