[笔记8]JavaScript DOM编程艺术_动态创建标记
JS也可以用来改变网页的结构和内容
一些传统方法
document.write
document对象的write()方法可以方便快捷地把字符串插入到文档里。
如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script type="text/javascript">
document.write("<p>This is inserted.</p>");
</script>
</body>
</html>
屏幕快照 2017-02-03 下午11.13.04.png
document.write的最大缺点就是违背了行为应该与表现分离的原则。即使把document.write语句挪到外部函数里,也还是需要在标记的<body>部分使用<script>标签才能调用那个函数。
例如:
function insertParagraph(text){
var str="<p>";
str+=text;
str+="</p>";
document.write(str);
}
-----------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script type="text/javascript" src="example.js"></script>
<script type="text/javascript">
insertParagraph("This is inserted.");
</script>
</body>
</html>
PS:上面的这种写法缺点有,很容易导致验证错误。MIME类型application/xhtml+xml与document.write不兼容,浏览器在呈现这种XHTML文档时根本不会执行document.write方法。
innerHTML属性
现如今的浏览器几乎都支持属性innerHTML,这个属性不是W3C DOM标准的组成部分,但现在已经包含到HTML5规范中。innerHTML属性可以用来读写某个给定元素的HTML内容。
举个例子就很容易理解了。
<div id="testdiv">
<p>This is <em>my</em>content.</p>
</div>
屏幕快照 2017-02-04 下午10.57.46.png
从innerHTML属性的角度来看,id为testdiv的标记里面只有一个值为<p>This is <em>my</em>content.</p>的HTML字符串。
window.onload=function(){
var testdiv=document.getElementById("testdiv");
alert(testdiv.innerHTML);
}
很明显,innerHTML属性无细节可言,要想获得细节,就必须使用DOM方法和属性。标准化的DOM像手术刀一样精细,innerHTML属性就像一把大锤那样粗放。
当你需要把一大段HTML内容插入网页时,innerHTML属性更适用,它既支持读取,又支持写入,你不仅可以用它来读出元素的HTML内容,还可以用它把HTML内容写入元素。
window.onload=function(){
var testdiv=document.getElementById("testdiv");
testdiv.innerHTML="<p>I inserted<em>this</em>content.</p>";
}
利用这个技术无法区分插入和替换一段HTML内容,一旦使用了innerHTML属性,它的全部内容将会被替换。innerHTML属性时HTML专有属性,不能用于任何其他标记语言文档。浏览器在呈现正宗的XHTML文档时会直接忽略掉innerHTML属性。
PS:在任何时候,标准的DOM都可以用来替换innerHTML。
DOM方法
DOM是文档的表示。DOM所包含的信息与文档里的信息一一对应。DOM是一条双向的车道。不仅可以获取文档的内容,还可以更新文档的内容。
createElement方法
通过例子说明方法。
以下文档里,想把一段文本插入到testdiv元素。
<div id="testdiv">
</div>
用DOM的语言来说,就是想添加一个p元素节点。目前div已经又一个属性节点(子节点)。任务分为两步:
- 创建一个新的元素 document.createElement(nodeName)
- 把这个新元素插入节点树
实际操作过程如下:
var para=document.createElement("p");
//虽然创建出来p元素的引用,但它还不是任何一颗DOM节点树的组成部分。这种情况称为文档碎片(document fragment)
通过para.nodeName查看节点名称
通过para.nodeType查看节点类型
appendChild方法
把新创建的节点插入某个文档的节点树的最简单的办法是,让它成为这个文档某个现在节点的一个子节点。
- 创建一个新的元素 document.createElement(nodeName)
- 把这个新元素插入节点树 parent.appendChild(child)
var testdiv=document.getElementById("testdiv");
var para=document.createElement("p");
testdiv.appendChild(para);
createTextNode方法
你现在已经创建出了一个元素节点并把它插入了文档的节点树,这个节点是一个空白的p元素。现在把一些文本放入这个p元素,创建文本节点,使用createTextNode方法。
createTextNode语法与createElement很相似。document.createTextNode(text)
在p元素中插入文本节点。
var txt=document.createTextNode("Hello World");
para.appendChild(txt);
综合上面的过程,完整代码如下:
window.onload=function(){
var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
var txt=document.createTextNode("Hello world");
para.appendChild(txt);
}
一个更复杂的组合
假如插入的内容如下所示。
<p>This is <em>my</em>content.</p>
PS:自己写完成,再与课本对对看有什么不一样。
var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
var txt=document.createTextNode("This is ");
var em=document.createElement("em");
var myTxt=document.createTextNode("my");
var contentTxt=document.createTextNode("content.");
para.appendChild(txt);
para.appendChild(em);
em.appendChild(myTxt);
para.appendChild(contentTxt);
testdiv.appendChild(para);
重回图片库
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Image Gallery</title>
<link rel="stylesheet" type="text/css" href="picture.css" media="screen">
</head>
<script type="text/javascript" src="scripts/showpicture.js"></script>
<script type="text/javascript">
</script>
<body>
<h1>Snapshots</h1>
<ul id="imagegallery">
<li><a href="images/fireworks.jpg" title="A fireworks display">Fireworks</a></li>
<li><a href="images/coffee.jpg" title="A cup of black coffee">Coffee</a></li>
<li><a href="images/rose.jpg" title="A red,red rose" >Rose</a></li>
<li><a href="images/bigben.jpg" title="The famous clock">Big Ben</a></li>
</ul>
<!--下面这段代码仅仅为showPic脚本服务,如果能把结构和行为彻底分开那最好不过了-->
![](images/placeholder.gif)
<p id="description">Choose an image</p>
<!------------->
</body>
</html>
PS: 既然上面标注的图片和p只是为了让DOM方法处理它们,那么让DOM方法来创建它们才是最合适的选择。我们需要做的就是1)将这些元素从文档里删掉。2)动态的将这些元素创建出来。3)把新创建的文档元素插入到文档中。
涉及到的方法有,创建元素节点createElement,设置属性setAttribute,创建文本节点createTextNode。
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
document.getElementsByTagName("body")[0].appendChild(placeholder);
document.getElementsByTagName("body")[0].appendChild(description);
-------------------------------------------HTML-DOM属性
document.body.appendChild(placeholder);
document.body.appendChild(description);
PS:这段代码有个问题,假如在body里面还会动态元素的话,它就会处于文档的中间而不会一直保持在最后的位置。
在已有元素前插入一个新元素
DOM提供了名为insertBefore()方法,这个方法将把一个新元素插入到一个现有元素的前面。调用此方法你必须告诉它三件事情。
- 新元素:你想插入的元素(newElement)
- 目标元素:你想把这个新元素插入到哪个元素(targetElement)之前
- 父元素:目标元素的父元素(parentElement)
语法是:parentElement.insertBefore(newElement,targetElement)
我们不必搞清楚父元素到底是哪个,因为targetElement元素的parentNode就是它。既targetElement.parentNode=parentElement
比如说将图片插入到清单之前,可以使用以下语句。
var gallery=document.getElementById("imagegallery");
gallery.parentNode.insertBefore(placeholder,gallery);
但是,现在我们需要将图片插入到清单的后面,用下面的方法。
在现有方法后插入一个新元素
PS:我想当然的认为,既然有在insertBefore方法,那肯定也有insertAfter方法,可是没有。但是我们可以自己实现。
function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){//目标元素是parent的最后一个元素,直接追加到parent上。
parent.appendChild(newElement);
}else{//把新元素插入到目标元素和目标元素的下一个兄弟元素之间
parent.insertBefore(newElement,targetElement.nextSibling);
}
}
目标元素的下一个兄弟元素既目标元素targetElement.nextSibling
现在我们使用insertAfter函数,使用该方法改写最后两句话。
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
整个代码改写如下,需要注意的是,我需要增加一些判断,判断浏览器是否支持我使用的属性,能保证平稳的退化:
function preparePlaceholder(){
if(!document.createElement)return false;
if(!document.createTextNode)return false;
if(!document.getElementById)return false;
if(!document.getElementById("imagegallery"))return false;
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(placeholder,placeholder);
}
图片库二次改进版
- addLoadEvent函数
- insertAfter函数
- preparePlaceholder函数
- prepareGallery函数
- showPic函数
function showPic(whichpic) {
if(!document.getElementById("placeholder"))return false;
var source=whichpic.getAttribute("href");
var placeholder=document.getElementById("placeholder");
placeholder.setAttribute("src",source);
if(document.getElementById("description")){
var text=whichpic.getAttribute("title");
var description=document.getElementById("description");
description.firstChild.nodeValue=text;
}
return true;
}
function prepareGallery(){
if(!document.getElementsByTagName) return false;
if(!document.getElementById)return false;
if(!document.getElementById("imagegallery"))return false;
var gallery=document.getElementById("imagegallery");
var links=gallery.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
links[i].onclick=function(){
return !showPic(this);
}
}
}
function addLoadEvent(func){
var oldonload=window.onload;
if(typeof window.onload!='function'){
window.onload=func;
}else{
window.onload=function(){
oldonload();
func();
}
}
}
function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
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("imagegallery"))return false;
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var descText=document.createTextNode("Choose an image");
description.appendChild(descText);
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(description,placeholder);
}
addLoadEvent(preparePlaceholder);
addLoadEvent(prepareGallery);
Ajax
上面的例子,将某些节点放到JS中动态的添加。假如页面只更新了一小部分,那怎样才能真正得到原来并不存在于初始页面中的内容呢?
使用Ajax就可以做到只更新页面中的一小部分内容。它的主要优势就是对页面的请求以异步方式发送到服务器。Ajax技术的核心就是XMLHttpRequest对象,这个对象充当着浏览器中的脚本(客户端)与服务器间的中间人的角色。以往的请求都由浏览器发出,而JS通过这个对象可以自己发送请求,同时也自己处理响应。
虽然XMLHttpRequest得到了几乎所有现代浏览器的支持。但问题是,不同浏览器实现XMLHttpRequest对象的方式不太一样。为了保证跨浏览器,你不得不为同样的事情写不同的代码分支。
不同的IE版本中使用的XMLHTTP对象也不完全相同。为了兼容所有的浏览器,getHTTPObject函数要这样写:
function getHTTPObject(){
if(typeof XMLHttpRequest == "undefined")
XMLHttpRequest=function(){
try{
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}catch(e){}
try{
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}catch(e){}
try{
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){}
return false;
}
return new XMLHttpRequest();
}
当你脚本要使用XMLHttpRequest对象时,可以将这个新对象直接赋值给一个变量。var request=getHttpObject();
在这之前,我需要搞懂两点。
- typeof的用法:可以使用 typeof 操作符来检测变量的数据类型。typeof 一个没有值的变量会返回 undefined。
- XMLHttpRequest=function(){}是什么用法?相当于检测XMLHttpRequest对象是否存在。
XMLHttpRequest有很多方法,最有用的是open方法,用来指定服务器上将要访问的文件,指定请求类型:GET、POST或SEND。
function getNewContent(){
var request=getHTTPObject():
if(request){
//发送GET请求,请求与ajax.html文件位于同一目录的example.txt文件
request.open("GET","example.txt",true);
//onreadystatechange是一个事件处理函数,服务器响应时被触发
request.onreadystatechange=function(){
if(request.readyState==4){
var para=document.createElement("p");
var txt=document.createTextNode(request.responseText);
para.appendChild(txt);
document.getElementById("new").appendChild(para);
}
};
request.send(null);
}else{
alert("Sorry,your browser doesn\'t support XMLHttpRequest");
}
}
addLoadEvent(getNewContent);
服务器在向XMLHttpRequest对象发回响应时,该对象有许多属性可用,readyState有5个可能的值。
- 0表示未初始化
- 1表示正在加载
- 2表示加载完毕
- 3 表示正在交互
- 4表示完成 // 可以直接使用服务器发送回来的数据。
可以通过responseText和responseXML属性来访问发送回来的数据。
- responseText用于保存文本字符串形式的数据
- responseXML用于保存Content-Type头部中指定“text/xml”的数据,其实是一个DocumentFragment对象,可以使用DOM来处理这个对象。
重做下例子,在谷歌运行发出现以下错误,safari就可以:
屏幕快照 2017-02-07 下午10.39.12.pngPS:原因是有些浏览器限制Ajax的使用协议,在Chrome中,如果你使用了file://协议从自己的硬盘中加载example.txt,就会看到Cross origin requests are only supported for protocol schemes的错误。
注意一点:异步请求有个容易忽略的点就是脚本在发送XMLHttpRequest请求后,仍然会继续执行,不会等待响应返回。
Ajax的特色就是减少重复加载页面的次数,但是缺少状态记录的技术会与浏览器的一些使用惯例产生冲突,导致用户无法使用后退按钮或者无法为特定状态下的页面添加书签。
渐进增强与Ajax
构建Ajax网站,从一开始就从构建一个常规网站开始,然后再使用Ajax。
Hijax
Hijax指渐进增强的使用Ajax。
Ajax应用主要依赖后台服务器,实际上是服务器端的脚本语言完成了绝大部分工作。XMLHttpRequest对象作为浏览器与服务器之间的“中间人”,它只是负责传递请求和响应。如果把这个中间人请开。浏览器与服务器之间的请求和响应应该继续完成。
构建表单的传统方式。
- 让表单把整个页面都提交到服务器,然后服务器再发回来包含反馈的新页面。所有处理操作都在服务器上完成,用户在表单中输入的数据则由服务器负责取得并保存在数据库里的数据进行比较,看是不是真的存在这个用户。
Hijax的方式
- 拦截提交表单的请求,让XMLHttpRequest请求来代为发送。提交表单触发的是submit事件,因此只要通过onsubmit事件处理函数捕获该事件,就可以取消它的默认操作,然后代之以一个新的操作:通过XMLHttpRequest对象向服务器发送数据。
Ajax应用主要依赖于服务器端处理,而非客户端处理。
具体的Hijax实例参考笔记9: