动态创建标记
传统技术:document.write和innerHTML
深入剖析DOM方法:createElement、createTextNode、appendChild和insertBefore。
一、传统方法
1.1 document.write
利用document.write可以在页面上写入内容,但它有着一个很大的缺点就是它违背了“行为与表现分离”的原则。即使将document.write语句挪到外部函数里,也还是需要在标记的body部分使用script标签才能调用那个函数。而将JS和HTML代码混杂在一起既不易阅读和编辑,也无法享受到行为与结构分离的好处。
这样混杂的文档容易导致验证错误。还有,MIME类型application/xhtml+xml与document.write不兼容,浏览器在呈现这种XHTML文档时根本不会执行document.write方法。从某种意义上讲,使用document.write方法有点像使用font标签去设定字体和颜色,虽然在HTML里运行的不错,但不够优雅。
1.2 innerHTML属性
现如今的浏览器几乎都支持innerHTML属性,它并不是W3C DOM标准的组成部分,但已被包含在HTML5规范中。
对于如下testdiv例子:
<div id="testdiv">
<p>This is <em>my</em> content.</p>
</div>
DOM的视角
DOM提供了关于这个标记非常详细的一幅图画,使用DOM方法和属性可以对任何一个节点进行单独的访问。同样的标记从innerHtml属性的角度看则简单很多,如下:
innerHtml视角
很显然,innerHtml属性毫无细节可言。要想获得细节就必须使用DOM方法和属性。标准化的DOM像手术刀一样精细,innerHtml属性就象一把大锤那样粗放。当然,如果需要把一大段HTML内容插入网页时,innerHtml更适用。它既支持读取,又支持写入。
<div id="testdiv">
</div>
window.onload = function(){
var testdiv = document.getElementById("testdiv");
testdiv.innerHTML = "<p>This is <em>my</em> content.</p>";
}
一样可以在页面上写下内容:This is my content.使用innerHTML,页面的内容将全都被替换。
innerHTML属性要比document.write()方法更值得推荐,使用innerHTML属性,可以把JS代码从标记中分离出来。用不着再在标记的body部分插入script标签。与document.write()方法类似,浏览器在呈现XHTML文档时会直接忽略掉innerHTML属性。
二、DOM方法
DOM是文档的表示,DOM所包含的信息与文档里的信息一一对应,只要学会问正确的问题,使用正确的方法,就可以获取DOM节点树上的任何一个节点的细节。浏览器实际显示的是DOM节点树。明白这个道理,以动态方式创建标记就不那么难以理解了。动态创建标记实则是在改变DOM节点树,从DOM的角度去思考问题。
在DOM看来,一个文档就是一颗节点树。如果想在节点树上添加内容,就必须插入新的节点;如果想添加一些标记,就必须插入元素节点。
2.1 createElement方法
继续使用第一小节的testdiv例子,我们将div设置为空的内容,然后通过动态创建标签的方式写入内容。期望:在div内写入一段文本,内容为:This is my content.
这项任务分两步骤:
1.创建一个新元素
2.把这个元素插入到DOM节点树
首先,我们需要创建标签元素p,用到DOM的createElement方法,createElement的语法是:
document.createElement(nodeName)
。
下面创建段落标签:document.createElement("p")
。这样一个p标签就被创建成功了。
不论何时何地,只要使用了createElement方法,就该把新创建出来的元素附给一个变量:var para = document.createElement("p");
变量para包含着一个指向刚创建出来的p元素的引用,现在新创建出来的元素节点,拥有了自身的DOM属性,但是不在DOM树的任何一个节点上,游荡在JavaScript世界的一个孤儿。
2.2 appendChild方法
把新创建的节点插入某个文档的节点树最简单的方法就是,让这个新节点成为某个现有节点的子节点。具体到testdiv的例子,我们应该让新建的P标签成为div的子节点。这里我们用到appendChild方法,语法是:parent.appendChild(child)
。
这里我们应该先获取父节点,使用getElementById()的DOM方法,即:
var testdiv = document.getElementById("testdiv")
,同样的,我们把查询值赋值给一个变量,方便后面的调用。
有了父节点,事情就好办多了。实现将P节点绑定到testdiv文档中的完整代码如下:
var para = document.createElement("p");
var testdiv = document.getElementById("testdiv");
testdiv.appendChild(para);
2.3 createTextNode 方法
到这步为止,我们已经创建出了一个元素节点并插入到了文档的节点树中,这个节点目前没有任何内容即没有子节点,但是之前的createElement方法只能创建元素节点,并不能创建内容。这里用createTextNode方法来实现它。
createTextNode方法的语法与createElement方法的语法差不多:document.createTextNode("text");
下面是实现创建一个内容为Hello world的文本节点:
var text = document.createTextNode("Hello world");
同样的,现在我们已经创建了一个指向文本内容为”Hello world”文本节点的变量,这个文本节点也不在DOM树节点中,所以我们需要将它成为某个DOM节点的子节点,使得它成为DOM树中一员。这里我们把文本节点绑定到p标签:
var text = document.createTextNode("Hello world");
var para = document.createElement("p");
para.appendChild(text);
到目前为止,我们实现了在页面上有内容显示。完整代码如下:
var text = document.createTextNode("Hello world");
var para = document.createElement("p");
para.appendChild(text);
var testdiv = document.getElementById("testdiv");
testdiv.appendChild(para);
三、进阶
3.1 在已有元素前插入一个新元素
insertBefore()方法将把一个新元素插入到一个现有元素的前面,调用此方法时,必须知道三件事:
- 新元素(newElement):你想插入的元素
- 目标元素(targetElement):你想插入到那个元素之前
- 父元素(parentElement):目标元素的父元素
parentElement.insertBefore(newElement,targetElement)
这里父元素可以用parentNode属性值获取。如下,在gallery前面插入placeholder图片:
gallery.parentNode.insertBefore(placeholder,gallery)
3.2 在已有方法后插入一个新元素
可能你会想,既然有一个insertBefore方法,是否有insertAfter方法,可惜DOM没有提供这个方法。我们依然可以编写实现这个方法。
1.编写insertAfter函数
我们可以用已有的DOM方法和属性编写一个insertAfter函数:
function insertAfter(newElement,targetElement){
var parent = targetElement.parentNode;//parentElement父元素
if (parent.lastChild == targetElement){
parent.appendChild(newElement);
//如果目标元素是父元素的最后一个子元素,则将新元素成为父元素的子节点(追加到父元素上),按顺序则成为新的最后一个子节点,即成功插入在目标元素之后
}
else{
parent.insertBefore(newElement,targetElement.nextSibling);
//如果目标元素不是父元素的最后一个子元素,则将新元素插入到目标元素紧挨着的下一个兄弟元素之前。
}
}
这些函数用到了以下的DOM方法和属性:
- parentNode属性
- lastChild属性
- appendChild方法
- insertBefore方法
- nextSibling属性
四、图片库优化
利用上面的知识,我们将图片库的结构、样式和行为彻底分离。JS代码增加了,但标记相应的减少了。图片库文件现在只包含一个由JS脚本和CSS样式表共用的“挂钩”。这个“挂钩”就是图片清单的id属性。实现效果以及完整代码如下:
JS代码
function addLoadEvent(func){
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else{
window.onload = function(){
oldonload();
func();
}
}
}
function insertAfter(){
var parent = targetElement.parentNode;//parentElement
if (parent.lastChild == targetElement){
parent.appendChild(newElement);
//如果目标元素是父元素的最后一个子元素,则将新元素成为父元素的子节点,按顺序则成为新的最后一个子节点,即成功插入在目标元素之后
}
else{
parent.insertBefore(newElement,targetElement.nextSibling);
//如果目标元素不是父元素的最后一个子元素,则将新元素插入到目标元素紧挨着的下一个兄弟节点之前。
}
}
function preparePlaceholder(){
if (!document.createElement) return false;
if (!document.createTextNode) return false;
if (!document.getElementById) return false;
if (!document.getElementById('imageallery')) return false;
var placeholder = document.createElement('img');
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","./../images/b.jpg");
placeholder.setAttribute("alt","my image");
var description = document.createElement("p");
description.setAttribute("id","description");
var desctext = document.createTextNode("choose an image");
description.appendChild(desctext);
var gallery = document.getElementById("imageallery");
insertAfter(placeholder,gallery);
insertAfter(description,placeholder);
}
function prepareGallery() {
if (!document.getElementsByTagName) return false;
if (!document.getElementById) return false;
if (!document.getElementById('imageallery')) return false;
var gallery = document.getElementById('imageallery');
var links = gallery.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = function() {
return !showPic(this);
}
links[i].onkeypress = links[i].onclick;//对于键盘输入等同于鼠标事件
};
}
function showPic(whichpic) {
if (!document.getElementById('placeholder')) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute("src", source);
// 检查placeholder元素是否存在
if (placeholder.nodeName != "IMG") return false;
if (document.getElementById('description')) {
var text = whichpic.getAttribute("title") ? whichpic.getAttribute("title") : "";
var description = document.getElementById("description");
if (description.firstChild.nodeType == 3) {
description.firstChild.nodeValue = text;
}
}
return false;
}
addLoadEvent(preparePlaceholder);
addLoadEvent(prepareGallery);
CSS代码
body{
font-family: "Helvetica","Arial",serif;
color: #333;
background-color: #ccc;
margin: 1em 10%;
}
h1{
color: #333;
background-color: transparent;
}
a{
color: #c60;
background-color: transparent;
font-weight: bold;
text-decoration: none;
}
ul{
padding: 0;
}
li{
float: left;
padding: 1em;
list-style: none;
}
img{
display: block;
clear: both;
width:80px;
height:60px;
}
#placeholder{
width: 400px;
height: 400px;
}
html代码
<html>
<head>
<meta charset=utf-8>
<title>图片库</title>
<link rel="stylesheet" type="text/css" href="./../styles/showPic.css">
</head>
<body>
<h1>图片库</h1>
<ul id="imageallery">
<li>
<a href="./../images/11.jpg" onclick="showPic(this); return false;" title="狗狗">
<img src="./../images/11.jpg">
</a>
</li>
<li>
<a href="./../images/22.jpg" onclick="showPic(this); return false;" title="花朵">
<img src="./../images/22.jpg">
</a>
</li>
<li>
<a href="./../images/a.jpg" onclick="showPic(this); return false;" title="手套">
<img src="./../images/a.jpg">
</a>
</li>
<li>
<a href="./../images/border.png" onclick="showPic(this); return false;" title="边框">
<img src="./../images/border.png">
</a>
</li>
</ul>
<img id="placeholder" src="./../images/b.jpg" alt="my image">
<p id="description">chooese an image</p>
<script src="./../javascripts/showPic.js"></script>
</body>
</html>