# 动态创建标记

此前见过的绝大多数DOM方法只能用来查找元素。getElementById和getElementsByTagName都可以方便快捷地找到文档中的某个或某些特定的元素节点，这些元素随后可以用诸如setAttribute和nodeValue之类的方法和属性来处理。在这两种情况里，都是对已经存在的元素做出修改。这是绝大多数JavaScript函数的工作原理。网页的结构有标记负责，JavaScript函数只是来改变某些细节而不改变其底层结构。不过JavaScript也可以通过创建新元素和修改现有元素来改变网页结构。

## 一些传统方法

### document.write

document对象的write()方法可以方便快捷地把字符串插入到文档里。 示例：

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Test</title>
</head>
<body>
  <script>
    document.write("<p>This is inserted.</p>");
  </script>
</body>
</html>
```

将这个html文件加载到web浏览器，你将看到“This is inserted.”的文本段落。 document.write的最大缺点是它违背了“行为应该与表现分离”的原则。从某种意义上讲，使用document.write方法有点像使用\<font>标签去设定字体和颜色。虽然这两种技术在HTML文档里都工作得不错，但它们不够优雅。把结构、行为和样式分开永远是一个好主意。只要有可能，就应该用外部CSS文件代替\<font>标签去设定和管理网页的样式信息，最好用外部JavaScript文件去控制网页的行为。应该避免在\<body>部分乱用\<script>标签，避免使用document.write方法。

### innerHTML属性

innerHTML属性可以用来读、写某给定元素里的**HTML内容**。 例如下面一段代码：

```html
<div id="testdiv">
  <p>This is <em>my</em> content.</p>
</div>
```

用DOM的眼睛看testdiv内的标记结构： ![testdiv结构](http://ofolh8dcq.bkt.clouddn.com/testdiv.PNG) 使用DOM提供的方法和属性可以对任何一个节点进行单独的访问。但从innerHTML属性的角度看，id为testdiv的标记里面只有一个值为\<p>This is \<em>my\</em> content.\</p>的HTML字符串。 ![innerHTML结构](http://ofolh8dcq.bkt.clouddn.com/innerHTML%E7%BB%93%E6%9E%84.PNG) 很明显，innerHTML属性无细节可言。要想获得细节，就必须使用DOM方法和属性。 innerHTML属性不仅可以读出元素的HTML内容，还可以把HTML内容写入元素。编辑文件，让id属性值为testdiv的元素变成空白：

```html
<div id="testdiv">
</div>
```

把下面这段JavaScript代码放入.js文件，就可以把一段HTML内容插入这个\<div>标签：

```javascript
window.onload=function(){
		var testdiv=document.getElementById("testdiv");
		testdiv.innerHTML="<p>I inserted <em>this</em> content.</p>";
	}
```

testdiv元素里有没有HTML内容无关紧要：**一旦你使用了innerHTML属性，它的内容就将全部替换。** 在需要把一大段HTML内容插入一份文档时，innerHTML属性可以让你又快又简单地完成这一任务。不过，innerHTML属性不会返回任何对刚插入内容的引用。如果想对那些内容进行处理，则需要使用DOM提供的那些精确的方法和属性。

## DOM方法

DOM是文档的表示。DOM所包含的信息与文档里的信息一一对应。它是一条双向车道，不仅可以获取文档的内容，还可以更新文档的内容。例如setAttribute方法，用这个方法可以改变文档的某个属性节点，不过setAttribute方法并未改变文档的物理内容，如果用文本编辑器打开，我们将看不到任何变化，这是因为浏览器实际显示的是那颗DOM节点树。在浏览器看来，DOM节点树才是文档 一旦明白这个，以动态方式实时创建标记就不那么难理解了。你并不是在创建标记，而是在改变DOM节点树。

### createElement方法

还是让id等于testdiv的那个\<div>标签内容变成空白：

```html
<div id="testdiv">
</div>
```

我想把一段文本插入testdiv元素。这项任务需分两个步骤完成：

* 创建一个新的元素
* 把这个新元素插入节点树

第一个步骤要用DOM方法createElement来完成。

```javascript
document.createElement(nodeName)
```

### appendChild方法

appendChild方法是把创建的节点变成某个节点的子节点。

```javascript
parent.appendChild(child)
```

创建一个p元素然后将其变成testdiv元素的一个子节点：

```javascript
var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
```

### createTextNode方法

你已经创建出了一个元素节点并把它插入了文档的节点树，这个节点是一个空白的p元素。你想把一些文本放入这个p元素，但createElement方法帮不上忙，它只能创建元素节点。而你需要创建一个文本节点，此时可以用createTextNode方法来实现它。

```javascript
document.createTextNode(text)
```

最终代码：

```javascript
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元素节点，然后再把p元素节点追加到文档中的testdiv元素节点：

```javascript
window.onload=function(){
		var para=document.createElement("p");
		var txt=document.createTextNode("hello world");
		para.appendChild(txt);

		var testdiv=document.getElementById("testdiv");
		testdiv.appendChild(para);
	}
```

## 重回图片库

回顾[案例研究：JavaScript图片库](http://zlw.poker/2016/11/01/%E6%A1%88%E4%BE%8B%E7%A0%94%E7%A9%B6%EF%BC%9AJavaScript%E5%9B%BE%E7%89%87%E5%BA%93/)gallery.html文件中的标记（可以将链接文字改为缩略图）。这个文件有一个图片（placeholder）和一段文字（description）仅仅是位showPic脚本服务的。既然这些元素的存在只是为了让DOM方法处理它们，那么用DOM方法来创建它们才是最合适的选择。 先把这些元素从gallery.html中删除。然后编写一个函数preparePlaceholder并把它放进showPic.js文件，然后在文档加载时调用这个函数。下面是这个函数要完成的任务： 1.创建一个img元素节点 2.设置这个节点的id属性 3.设置这个节点的src属性 4.设置这个节点的alt属性 5.创建一个p元素节点 6.设置这个节点的id属性 7.创建一个文本节点 8.把这个文本节点追加到p元素上 9.把p元素和img元素插入到gallery.html文档

创建这些元素和设置各有关属性的工作：

```javascript
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.jpg");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desctxt=document.createTextNode("choose an image");
```

把创建的文本节点插入p元素：

```javascript
description.appendChild(desctxt)
```

最后是把新创建的元素插入文档：

```javascript
document.getElementsByTagName("body")[0].appendChild(placeholder);
document.getElementsByTagName("body")[0].appendChild(description);

//也可以使用HTML-DOM提供的属性body
document.body.appendChild(placeholder);
document.body.appendChild(description);
```

以上代码工作得很好，但这一切都依赖于一个细节：图片清单（\<ul>...\</ul>）刚好是\<body>部分的最后一个元素。但如果这个图片清单的后面还要一些其他的内容，而我们想把新创建的元素紧跟在图片清单后面怎么办呢？

### 在已有元素前插入一个新元素

DOM提供了名为insertBefore()方法，这个方法将把一个新元素插入到一个现有元素的前面。

```javascript
parentElement.insertBefore(newElement,targetElement)
```

* 新元素：你想插入的元素（newElement）
* 目标元素：你想把这个新元素插入到哪个元素（targetElement）之前
* 父元素：目标元素的父元素（parentElement）

\*\*我们不必搞清楚父元素到底是哪个，因为targetElement元素的parentNode属性值就是它。\*\*在DOM里，元素节点的父元素必须是另一个元素节点（属性节点和文本节点的子元素不允许是元素节点）。

### 在现有元素后插入一个新元素

DOM没有提供这样一个函数。

#### 编写insertAfter函数

虽然DOM本身没有提供insertAfter方法，但它确实提供了把一个节点插入到另一个节点之后所需的所有工具。我们可以利用已有的DOM方法和属性编写一个insertAfter函数：

```javascript
function insertAfter(newElement,targetElement){
		var parent=targetElement.parentNode;
		if(parent.lastChild==targetElement){
				parent.appendChild(newElement);
			}else{
					parent.insertBefore(newElement,targetElement.nextSibling);
				}
	}
```

targetElement.nextSibling是目标元素的下一个兄弟元素。

#### 使用insertAfter函数

使用insertAfter函数后preparePlaceholder函数现在的样子：（记住测试浏览器是否支持这些DOM方法）

```javascript
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.jpg");
		placeholder.setAttribute("alt","my image gallery");
		var description=document.createElement("p");
		description.setAttribute("id","description");
		var desctxt=document.createTextNode("choose an image");
		description.appendChild(desctxt);
		var gallery=document.getElementById("imagegallery");
		insertAfter(placeholder,gallery);
		insertAfter(description,placeholder);
	}
```

### 图片库二次改进版

现在showPic.js文件包含5个不同的函数，分别是：

* addLoadEvent函数
* insertAfter函数
* preparePlaceholder函数
* prepareGallery函数
* showPic函数

preparePlaceholder函数负责创建一个img元素和一个p元素。这个函数将把这些新创建的元素插入到节点树里图片库清单的后面。prepareGallery函数负责处理事件。这个函数将遍历处理图片库清单里的每个链接。当用户点击这些链接中的某一个时，就会调用showPic函数。showPic函数负责把“占位符图片”切换为目标图片。 下面是最终完成的showPic.js文件：

```javascript
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.jpg");
		placeholder.setAttribute("alt","my image gallery");
		var description=document.createElement("p");
		description.setAttribute("id","description");
		var desctxt=document.createTextNode("choose an image");
		description.appendChild(desctxt);
		var gallery=document.getElementById("imagegallery");
		insertAfter(placeholder,gallery);
		insertAfter(description,placeholder);
	}

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)?false:true;
				}
		}
}

function showPic(whichpic){
	if(!document.getElementById("placeholder")) return false;
	var source = whichpic.getAttribute("href");
	var placeholder = document.getElementById("placeholder");
	if(placeholder.nodeName!="IMG") return false;
	placeholder.setAttribute("src",source);
	if(document.getElementById("description")){	
		var text=whichpic.getAttribute("title")?whichpic.getAttribute("title"):""; 
		var description=document.getElementById("description");
		description.firstChild.nodeValue=text;
	}
	return true;               
}

addloadEvent(preparePlaceholder);
addloadEvent(prepareGallery);
```

JavaScript代码增加了，但标记相应的减少了。gallery.html文件现在只包含一个由JavaScript脚本和CSS样式表共用的“挂钩”。这个“挂钩”就是图片清单的id属性。

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Image Gallery</title>
  <link rel="stylesheet" href="styles/layout.css" media="screen"/>
</head>
<body>
  <h1>Snapshots</h1>
  <ul id="imagegallery">
    <li>
    <a href="images/fireworks.jpg" title="a firework display">fireworks</a>
    </li>
    <li>
    <a href="images/coffee.png" title="a cup of coffee">coffee</a>
    </li>
    <li>
    <a href="images/rose.jpg" title="a red rose">rose</a>
    </li>
    <li>
    <a href="images/bigben.jpeg" title="the famouse clock">bigben</a>
    </li>
  </ul>
  <script src="scripts/showPic.js"></script>
</body>
</html>
```

## Ajax

以前，web应用都要涉及大量的页面刷新：用户点击了某个链接，请求发送回服务器，然后服务器根据用户的操作再返回新页面。即便用户看到的只是页面的一小部分发生变化，也要刷新和重新加载整个页面。使用Ajax就可以做到只更新页面中的一小部分。 Ajax的主要优势就是对页面的请求以异步方式发送到服务器，而服务器不会用整个页面来响应请求它会在后台处理，与此同时用户还能继续浏览页面并与页面交互。你的脚本则可以按需加载和创建页面内容，而不会打断用户的浏览体验。

### XMLHttpRequest对象

Ajax技术的核心就是XMLHttpRequest对象。这个对象充当着浏览器中的脚本（客户端）与服务器之间的中间人的角色。以往的请求都由浏览器发出，而JavaScript通过这个对象可以自己发出请求，同时也自己处理响应。 创建XMLHttpRequest对象：微软最早在IE5中以ActiveX对象的形式实现了一个名叫XMLHTTP的对象。在IE中创建新的对象要使用以下代码：

```javascript
var request=new ActiveXObject("Msxml2.XMLHTTP.3.0");
```

更麻烦的是不同IE版本中使用的XMLHTTP对象也不完全相同。 其他浏览器则基于XMLHttpRequest来创建新对象：

```javascript
var request=new XMLHttpRequest();
```

为了兼容所有浏览器，我们要写个函数getHTTPObject()达到目的：

```javascript
function getHTTPObject(){
	if (typeof XMLHttpRequest=="undefined")
		XMLHttpRequest=function(){
			try {return new ActiveXObject("Msxml2.XMLHTTP.6.0");}
				catch(e){}
			try{return new ActiveXObject("Msxml2.XMLHTTP3.0");}
				catch(e){}
			try{return new ActiveXObject("Msxml2.XMLHTTP");}
				catch(e){}
			return false;
		}
		return new XMLHttpRequest();
}
```

getHTTPObject通过对象检测技术使其能在不同浏览器实现兼容。用getHTTPObject函数创建XMLHttpRequest对象：

```javascript
var request=getHTTPObject();
```

XMLHttpRequest对象有许多方法。其中最有用的是open方法，它用来**指定**服务器上将要访问的文件，指定请求的类型有：GET、POST和SEND。 例如：

```javascript
function getNewContent(){
	var request=getHTTPObject();
	if(request){
		request.open("GET","example.txt",true);   //第三个参数用于指定请求是否以异步方式发送和处理
		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 does not support XMLHttpRequest');
		}
}
```

当页面加载完成后，以上代码会发起一个GET请求，请求与.html文件位于同一目录的example.txt文件。代码中onreadystatechange是一个事件处理函数，它会在服务器给XMLHttpRequest对象送回响应的时候被触发执行。在指定了请求的目标，也明确了如何响应之后，就可以用send方法来发送请求了：request.send(null);不发送任何数据的请求。 服务器在向XMLHttpRequest对象发回响应时，该对象有许多属性可用，浏览器会在不同阶段更新readyState属性的值：

* 0：未初始化
* 1：正在加载
* 2：加载完毕
* 3：正在交互
* 4：完成

只要readyState属性的值变成了4，就可以访问服务器发送回来的数据了。访问服务器发送回来的数据要通过两个属性完成。一个是responseText属性，用于保存文本字符串形式的数据。另一个是responseXML属性，用于保存Content-Type头部中指定为“text/xml”的数据，其实是一个DocumentFragment对象。

> 注意：在使用Ajax时，千万要注意同源策略。使用XMLHttpRequest对象发送的请求只能访问与其所在的HTML处于同一个域中的数据，不能向其他域发送请求。此外，有些浏览器还会限制Ajax请求使用的协议，比如Chrome。

异步请求有一个容易被忽略的问题是异步性，就是脚本在发送XMLHttpRequest请求之后，仍然会继续执行，不会等待响应返回。为此，如果其他脚本依赖于服务器的响应，那么就得把相应的代码都转移到指定给onreadystatechange属性的那个函数中。

### 渐进增强与Ajax

构建Ajax网站的最好方法，也是先构建一个常规的网站，然后Hijax它。

### Hijax

Hijax是指渐进增强地使用Ajax。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lewiszlw.gitbook.io/notebooks/blogs/history/dong-tai-chuang-jian-biao-ji.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
