JavaScript(XML)

学习要点:1.IE 中的 XML、2.DOM2 中的 XML、3.跨浏览器处理 XML

随着互联网的发展,Web 应用程序的丰富,开发人员越来越希望能够使用客户端来操作 XML 技术。而 XML 技术一度成为存储和传输结构化数据的标准。所以,本章就详细探讨一下 JavaScript 中使用 XML 的技术。

对于什么是 XML可以理解成一个微型的结构化的数据库,保存一些小型数据用的。

一、IE中的XML

在统一的正式规范出来以前,浏览器对于 XML 的解决方案各不相同。DOM2 级提出了动态创建 XML DOM 规范,DOM3 进一步增强了 XML DOM。所以,在不同的浏览器实现XML 的处理是一件比较麻烦的事情。

1.1、创建 XMLDOM 对象

IE 浏览器是第一个原生支持 XML 的浏览器,而它是通过 ActiveX 对象实现的。这个对象,只有 IE 有,一般是 IE9 之前采用。微软当年为了开发人员方便的处理 XML,创建了MSXML 库,但却没有让 Web 开发人员通过浏览器访问相同的对象。

var xmlDom = new ActiveXObject('MSXML2.DOMDocument');
JavaScript(XML)_第1张图片
xml1.jpg

PS:在这六个版本中微软只推荐三种:

​ 1.MSXML2.DOMDocument.6.0 最可靠最新的版本

​ 2.MSXML2.DOMDocument.3.0 兼容性较好的版本

​ 3.MSXML2.DOMDocument 仅针对 IE5.5 之前的版本

PS:这三个版本在不同的 windows 平台和浏览器下会有不同的支持,那么为了实现兼容,我们应该考虑这样操作:从 6.0->3.0->备用版本这条路线进行实现。

function createXMLDOM() {
   var version = [
      'MSXML2.DOMDocument.6.0',
      'MSXML2.DOMDocument.3.0',
      'MSXML2.DOMDocument'
   ];
for (var i = 0; i < version.length; i ++) {
    try {
       var xmlDom = new ActiveXObject(version[i]);
       return xmlDom;
    } catch (e) {
      //跳过
} 
}
throw new Error('您的系统或浏览器不支持 MSXML!'); //循环后抛出错误
}

1.2、载入 XML

如果已经获取了 XMLDOM 对象,那么可以使用 loadXML()和 load()这两个方法可以分别载入 XML 字符串或 XML 文件。

xmlDom.loadXML('Lee');
alert(xmlDom.xml);

PS:loadXML 参数直接就是 XML 字符串,如果想效果更好,可以添加换行符\n。.xml属性可以序列化 XML,获取整个 XML 字符串。

xmlDom.load('test.xml'); //载入一个 XML 文件
alert(xmlDom.xml);

当你已经可以加载了 XML,那么你就可以用之前学习的 DOM 来获取 XML 数据,比如标签内的某个文本。

var user = xmlDom.getElementsByTagName('user')[0]; //获取节点
alert(user.tagName); //获取元素标签
alert(user.firstChild.nodeValue); //获取里的值 Lee

DOM 不单单可以获取 XML 节点,也可以创建。

var email= xmlDom.createElement('email');
xmlDom.documentElement.appendChild(email);

1.3.同步及异步

load()方法是用于服务器端载入 XML 的,并且限制在同一台服务器上的 XML 文件。那么在载入的时候有两种模式:同步和异步。

所谓同步:就是在加载 XML 完成之前,代码不会继续执行,直到完全加载了 XML 再返回。好处就是简单方便、坏处就是如果加载的数据停止响应或延迟太久,浏览器会一直堵塞从而造成假死状态。

xmlDom.async = false; //设置同步,false,可以用 PHP 测试假死

所谓异步:就是在加载 XML 时,JavaScript 会把任务丢给浏览器内部后台去处理,不会造成堵塞,但要配合 readystatechange 事件使用,所以,通常我们都使用异步方式。

xmlDom.async = true; //设置异步,默认

通过异步加载,我们发现获取不到 XML 的信息。原因是,它并没有完全加载 XML 就返回了,也就是说,在浏览器内部加载一点,返回一点,加载一点,返回一点。这个时候,我们需要判断是否完全加载,并且可以使用了,再进行获取输出。

JavaScript(XML)_第2张图片
xml2.jpg
var xmlDom = createXMLDOM();
  xmlDom.async = true; //异步,可以不写
  xmlDom.onreadystatechange = function () {
  if (xmlDom.readyState == 4) { //完全加载了,再去获取 XML
    alert(xmlDom.xml);
  } 
}
xmlDom.load('test.xml'); //放在后面重点体现异步的作用

PS:可以通过 readyState 来了解事件的执行次数,将 load()方法放到最后不会因为代码的顺序而导致没有加载。并且 load()方法必须放在 onreadystatechange 之后,才能保证就绪状态变化时调用该事件处理程序,因为要先触发。用 PHP 来测试,在浏览器内部执行时,是否能操作,是否会假死。

PS:不能够使用 this,不能够用 IE 的事件处理函数,原因是 ActiveX 控件为了预防安全性问题。

PS:虽然可以通过 XML DOM 文档加载 XML 文件,但公认的还是 XMLHttpRequest对象比较好。这方面内容,我们在 Ajax 章节详细了解。

1.4、解析错误

在加载 XML 时,无论使用 loadXML()或 load()方法,都有可能遇到 XML 格式不正确的情况。为了解决这个问题,微软的 XML DOM 提供了 parseError 属性。

JavaScript(XML)_第3张图片
xml3.jpg
if (xmlDom.parseError == 0) {
   alert(xmlDom.xml);
} else {
   throw new Error('错误行号:' + xmlDom.parseError.line +
   '\n 错误代号:' + xmlDom.parseError.errorCode +
   '\n 错误解释:' + xmlDom.parseError.reason);
}

二、DOM2中的XML

IE 可以实现了对 XML 字符串或 XML 文件的读取,其他浏览器也各自实现了对 XML处理功能。DOM2 级在 document.implementaion 中引入了 createDocument()方法。IE9、Firefox、Opera、Chrome 和 Safari 都支持这个方法。

2.1、创建 XMLDOM 对象

var xmlDom = document.implementation.createDocument('','root',null); //创建 xmlDom
var user = xmlDom.createElement('user'); //创建 user 元素
xmlDom.getElementsByTagName('root')[0].appendChild(user); //添加到 root 下
var value = xmlDom.createTextNode('Lee'); //创建文本
xmlDom.getElementsByTagName('user')[0].appendChild(value); //添加到 user 下
alert(xmlDom.getElementsByTagName('root')[0].tagName);
alert(xmlDom.getElementsByTagName('user')[0].tagName);
alert(xmlDom.getElementsByTagName('user')[0].firstChild.nodeValue);

PS:由于 DOM2 中不支持 loadXML()方法,所以,无法简易的直接创建 XML 字符串。所以,只能采用以上的做法。

PS:createDocument()方法需要传递三个参数,命名空间,根标签名和文档声明,由于JavaScript 管理命名空间比较困难,所以留空即可。文档声明一般根本用不到,直接 null 即可。命名空间和文档声明留空,表示创建 XMLDOM 对象不需要命名空间和文档声明。

PS:命名空间的用途是防止太多的重名而进行的分类,文档类型表明此文档符合哪种规范,而这里创建 XMLDOM 不需要使用这两个参数,所以留空即可。

2.2、载入 XML

DOM2 只支持 load()方法,载入一个同一台服务器的外部 XML 文件。当然,DOM2 也有 async 属性,来表面同步或异步,默认异步。

//同步情况下
var xmlDom = document.implementation.createDocument('','root',null);
xmlDom.async = false;
xmlDom.load('test.xml');
alert(xmlDom.getElementsByTagName('user')[0].tagName);
//异步情况下
var xmlDom = document.implementation.createDocument('','root',null);
xmlDom.async = true;
addEvent(xmlDom, 'load', function () { //异步直接用 onload 即可
alert(this.getElementsByTagName('user')[0].tagName);
});
xmlDom.load('test.xml');

PS:不管在同步或异步来获取 load()方法只有 Mozilla 的 Firefox 才能支持,只不过新版的 Opera 也是支持的,其他浏览器则不支持。

2.3、DOMParser 类型

由于 DOM2 没有 loadXML()方法直接解析 XML 字符串,所以提供了 DOMParser 类型来创建 XML DOM 对象。IE9、Safari、Chrome 和 Opera 都支持这个类型。

var xmlParser = new DOMParser(); //创建 DOMParser 对象
var xmlStr = 'Lee'; //XML 字符串
var xmlDom = xmlParser.parseFromString(xmlStr, 'text/xml'); //创建 XML DOM 对象
alert(xmlDom.getElementsByTagName('user')[0].tagName); //获取 user 元素标签名

PS:XML DOM 对象是通过 DOMParser 对象中的 parseFromString 方法来创建的,两个参数:XML 字符串和内容类型 text/xml。

2.4、XMLSerializer 类型

由于 DOM2 没有序列化 XML 的属性,所以提供了 XMLSerializer 类型来帮助序列化XML 字符串。IE9、Safari、Chrome 和 Opera 都支持这个类型。

var serializer = new XMLSerializer(); //创建 XMLSerializer 对象
var xml = serializer.serializeToString(xmlDom); //序列化 XML
alert(xml);

2.5、解析错误

在 DOM2 级处理 XML 发生错误时,并没有提供特有的对象来捕获错误,而是直接生成另一个错误的 XML 文档,通过这个文档可以获取错误信息。

var errors = xmlDom.getElementsByTagName('parsererror');
if (errors.length > 0) {
   throw new Error('XML 格式有误:' + errors[0].textContent);
}

PS:errors[0].firstChild.nodeValue 也可以使用 errors[0].textContent 来代替。

三、跨浏览器处理XML

如果要实现跨浏览器就要思考几个个问题:1.load()只有 IE、Firefox、Opera 支持,所以无法跨浏览器;2.获取 XML DOM 对象顺序问题,先判断先进的 DOM2 的,然后再去判断落后的 IE;3.针对不同的 IE 和 DOM2 级要使用不同的序列化。4.针对不同的报错进行不同的报错机制。

//首先,我们需要跨浏览器获取 XML DOM
function getXMLDOM(xmlStr) {
var xmlDom = null;
if (typeof window.DOMParser != 'undefined') { //W3C
xmlDom = (new DOMParser()).parseFromString(xmlStr, 'text/xml');
var errors = xmlDom.getElementsByTagName('parsererror');
if (errors.length > 0) {
throw new Error('XML 解析错误:' + errors[0].firstChild.nodeValue);
} } else if (typeof window.ActiveXObject != 'undefined') { //IE
var version = [
'MSXML2.DOMDocument.6.0',
'MSXML2.DOMDocument.3.0',
'MSXML2.DOMDocument'
];
for (var i = 0; i < version.length; i ++) {
try {
xmlDom = new ActiveXObject(version[i]);
} catch (e) {
//跳过
} }
xmlDom.loadXML(xmlStr);
if (xmlDom.parseError != 0) {
throw new Error('XML 解析错误:' + xmlDom.parseError.reason);
} } else {
throw new Error('您所使用的系统或浏览器不支持 XML DOM!');
}
return xmlDom;
}
//其次,我们还必须跨浏览器序列化 XML
function serializeXML(xmlDom) {
var xml = '';
if (typeof XMLSerializer != 'undefined') {
xml = (new XMLSerializer()).serializeToString(xmlDom);
} else if (typeof xmlDom.xml != 'undefined') {
xml = xmlDom.xml;
} else {
throw new Error('无法解析 XML!');
}
return xml;
}

PS:由于兼容性序列化过程有一定的差异,可能返回的结果字符串可能会有一些不同。至于 load()加载 XML 文件则因为只有部分浏览器支持而无法跨浏览器。

JavaScript(XPATH)

学习要点:1.IE 中的 XPath、2.W3C 中的 XPath、3.XPath 跨浏览器兼容

XPath 是一种节点查找手段,对比之前使用标准 DOM 去查找 XML 中的节点方式,大大降低了查找难度,方便开发者使用。但是,DOM3 级以前的标准并没有就 XPath 做出规范;直到 DOM3 在首次推荐到标准规范行列。大部分浏览器实现了这个标准,IE 则以自己的方式实现了 XPath。

一、IE中的Xpath

IE8 及之前的浏览器,XPath 是采用内置基于 ActiveX 的 XML DOM 文档对象实现的。在每一个节点上提供了两个方法:selectSingleNode()和 selectNodes()。

selectSingleNode()方法接受一个 XPath 模式(也就是查找路径),找到匹配的第一个节点并将它返回,没有则返回 null。

var user = xmlDom.selectSingleNode('root/user'); //得到第一个 user 节点
alert(user.xml); //查看 xml 序列
alert(user.tagName); //节点元素名
alert(user.firstChild.nodeValue); //节点内的值

上下文节点:我们通过 xmlDom 这个对象实例调用方法,而 xmlDom 这个对象实例其实就是一个上下文节点,这个节点指针指向的是根,也就是 root 元素之前。那么如果我们把这个指针指向 user 元素之前,那么结果就会有所变化。

//通过 xmlDom,并且使用 root/user 的路径
var user = xmlDom.selectSingleNode('root/user');
alert(user.tagName); //user

//通过 xmlDom.documentElement,并且使用 user 路径,省去了 root
var user = xmlDom.documentElement.selectSingleNode('user');
alert(user.tagName); //user

//通过 xmlDom,并且使用 user 路径,省去了 root
var user = xmlDom.selectSingleNode('user');
alert(user.tagName); //找不到了,出错

PS:xmlDom 和 xmlDom.documentElement 都是上下文节点,主要就是定位当前路径查找的指针,而 xmlDom 对象实例的指针就是在最根上。

XPath 常用语法

//通过 user[n]来获取第 n+1 条节点,PS:XPath 其实是按 1 为起始值的
var user = xmlDom.selectSingleNode('root/user[1]');
alert(user.xml);
//通过 text()获取节点内的值
var user = xmlDom.selectSingleNode('root/user/text()');
alert(user.xml);
alert(user.nodeValue);
//通过//user 表示在整个 xml 获取到 user 节点,不关心任何层次
var user = xmlDom.selectSingleNode('//user');
alert(user.xml);
//通过 root//user 表示在 root 包含的层次下获取到 user 节点,在 root 内不关心任何层次
var user = xmlDom.selectSingleNode('root//user');
alert(user.tagName);
//通过 root/user[@id=6]表示获取 user 中 id=6 的节点
var user = xmlDom.selectSingleNode('root/user[@id=6]');
alert(user.xml);

PS:更多的 XPath 语法,可以参考 XPath 手册或者 XML DOM 手册进行参考,这里只提供了最常用的语法。

selectSingleNode()方法是获取单一节点,而 selectNodes()方法则是获取一个节点集合。

var users = xmlDom.selectNodes('root/user'); //获取 user 节点集合
alert(users.length);
alert(users[1].xml);

二.W3C下的XPath

在 DOM3 级 XPath 规范定义的类型中,最重要的两个类型是 XPathEvaluator 和XPathResult。其中,XPathEvaluator 用于在特定上下文对 XPath 表达式求值。

JavaScript(XML)_第4张图片
xml4.jpg

W3C 实现 XPath 查询节点比 IE 来的复杂,首先第一步就是需要得到 XPathResult 对象的实例。得到这个对象实例有两种方法,一种是通过创建 XPathEvaluator 对象执行 evaluate()方法,另一种是直接通过上下文节点对象(比如 xmlDom)来执行 evaluate()方法。

//使用 XPathEvaluator 对象创建 XPathResult
var eva = new XPathEvaluator();
var result = eva.evaluate('root/user', xmlDom, null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
alert(result);
//使用上下文节点对象(xmlDom)创建 XPathResult
var result = xmlDom.evaluate('root/user', xmlDom, null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
alert(result);

相对而言,第二种简单方便一点,但 evaluate 方法有五个属性:1.XPath 路径、2.上下文节点对象、3.命名空间求解器(通常是 null)、4.返回结果类型、5 保存结果的 XPathResult对象(通常是 null)。


JavaScript(XML)_第5张图片
xml5.jpg

PS:上面的常量过于繁重,对于我们只需要学习了解,其实也就需要两个:1.获取一个单一节、2.获取一个节点集合。

1.获取一个单一节点

var result = xmlDom.evaluate('root/user', xmlDom, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  if (result !== null) {
    alert(result.singleNodeValue.tagName); //singleNodeValue 属性得到节点对象
  }

2.获取节点集合

var result = xmlDom.evaluate('root/user', xmlDom, null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
var nodes = [];
   if (result !== null) {
     while ((node = result.iterateNext()) !== null) {
     nodes.push(node);
   } 
 }

PS:节点集合的获取方式,是通过迭代器遍历而来的,我们保存到数据中就模拟出 IE相似的风格。

三、XPath 跨浏览器兼容

如果要做 W3C 和 IE 的跨浏览器兼容,我们要思考几个问题:1.如果传递一个节点的下标,IE 是从 0 开始计算,W3C 从 1 开始计算,可以通过传递获取下标进行增 1 减 1 的操作来进行。2.独有的功能放弃,为了保证跨浏览器。3.只获取单一节点和节点列表即可,基本可以完成所有的操作。

//跨浏览器获取单一节点
function selectSingleNode(xmlDom, xpath) {
var node = null;
if (typeof xmlDom.evaluate != 'undefined') {
var patten = /\[(\d+)\]/g;
var flag = xpath.match(patten);
var num = 0;
if (flag !== null) {
num = parseInt(RegExp.$1) + 1;
xpath = xpath.replace(patten, '[' + num + ']');
}
var result = xmlDom.evaluate(xpath, xmlDom, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (result !== null) {
node = result.singleNodeValue;
} } else if (typeof xmlDom.selectSingleNode != 'undefined') {
node = xmlDom.selectSingleNode(xpath);
}
return node;
}
//跨浏览器获取节点集合
function selectNodes(xmlDom, xpath) {
var nodes = [];
if (typeof xmlDom.evaluate != 'undefined') {
var patten = /\[(\d+)\]/g;
var flag = xpath.match(patten);
var num = 0;
if (flag !== null) {
num = parseInt(RegExp.$1) + 1;
xpath = xpath.replace(patten, '[' + num + ']');
}
var node = null;
var result = xmlDom.evaluate('root/user', xmlDom, null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
if (result !== null) {
while ((node = result.iterateNext()) !== null) {
nodes.push(node);
} } } else if (typeof xmlDom.selectNodes != 'undefined') {
nodes = xmlDom.selectNodes(xpath);
}
return nodes;
}

PS:在传递 xpath 路径时,没有做验证判断是否合法,有兴趣的同学可以自行完成。在XML 还有一个重要章节是 XSLT 和 EX4,由于在使用频率的缘故,我们暂且搁置。

JavaScript(JSON)

学习要点:1.JSON 语法、2.解析和序列化

前两章我们探讨了 XML 的结构化数据,但开发人员还是觉得这种微型的数据结构还是过于烦琐、冗长。为了解决这个问题,JSON 的结构化数据出现了。JSON 是 JavaScript 的一个严格的子集,利用 JavaScript 中的一些模式来表示结构化数据。

一、JSON语法

JSON 和 XML 类型,都是一种结构化的数据表示方式。所以,JSON 并不是 JavaScript独有的数据格式,其他很多语言都可以对 JSON 进行解析和序列化。

JSON 的语法可以表示三种类型的值:

1.简单值:可以在 JSON 中表示字符串、数值、布尔值和 null。但 JSON 不支持 JavaScript

中的特殊值 undefined。

2.对象:顾名思义。

3.数组:顾名思义

简单值

100、"Lee" 这两个量就是 JSON 的表示方法,一个是 JSON 数值,一个是 JSON 字符串。布尔值和 null 也是有效的形式。但实际运用中要结合对象或数组。

对象

JavaScript 对象字面量表示法:

var box = {
   name : 'Lee',
   age : 100
};

而 JSON 中的对象表示法需要加上双引号,并且不存在赋值运算和分号:

{ 
    "name" : "Lee", //使用双引号,否则转换会出错
    "age" : 100
}

数组

JavaScript 数组字面量表示法:

var box = [100, 'Lee', true];

而 JSON 中的数组表示法同样没有变量赋值和分号:

[100, "Lee", true]

一般比较常用的一种复杂形式是数组结合对象的形式:

[ 
    { "title" : "a", "num" : 1},
    { "title" : "b", "num" : 2},
    { "title" : "c", "num" : 3 } 
]

PS:一般情况下,我们可以把 JSON 结构数据保存到一个文本文件里,然后通过XMLHttpRequest 对象去加载它,得到这串结构数据字符串(XMLHttpRequest 对象将在 Aajx章节中详细探讨)。所以,我们可以模拟这种过程。

模拟加载 JSON 文本文件的数据,并且赋值给变量。

var box = '[{"name" : "a","age" : 1},{"name" : "b","age" : 2}]';

PS;上面这短代码模拟了 var box = load('demo.json');赋值过程。因为通过 load 加载的文本文件,不管内容是什么,都必须是字符串。所以两边要加上双引号。

其实 JSON 就是比普通数组多了两边的双引号,普通数组如下:

var box = [{name : 'a', age : 1},{name : 'b', age : 2}];

二、解析和序列化

如果是载入的 JSON 文件,我们需要对其进行使用,那么就必须对 JSON 字符串解析成原生的 JavaScript 值。当然,如果是原生的 JavaScript 对象或数组,也可以转换成 JSON 字符串。

对于讲 JSON 字符串解析为 JavaScript 原生值,早期采用的是 eval()函数。但这种方法既不安全,可能会执行一些恶意代码。

var box = '[{"name" : "a","age" : 1},{"name" : "b","age" : 2}]';
alert(box); //JSON 字符串
var json = eval(box); //使用 eval()函数解析
alert(json); //得到 JavaScript 原生值

ECMAScript5 对解析 JSON 的行为进行规范,定义了全局对象 JSON。支持这个对象的浏览器有 IE8+、Firefox3.5+、Safari4+、Chrome 和 Opera10.5+。不支持的浏览器也可以通过一个开源库 json.js 来模拟执行。JSON 对象提供了两个方法,一个是将原生 JavaScript 值转换为 JSON 字符串:stringify();另一个是将 JSON 字符串转换为 JavaScript 原生值:parse()。

var box = '[{"name" : "a","age" : 1},{"name" : "b","age" : 2}]'; //特别注意,键要用双引号
alert(box);
var json = JSON.parse(box); //不是双引号,会报错
alert(json);
var box = [{name : 'a', age : 1},{name : 'b', age : 2}]; //JavaScript 原生值
var json = JSON.stringify(box); //转换成 JSON 字符串
alert(json); //自动双引号

在序列化 JSON 的过程中,stringify()方法还提供了第二个参数。第一个参数可以是一个数组,也可以是一个函数,用于过滤结果。第二个参数则表示是否在 JSON 字符串中保留缩进。

var box = [{name : 'a', age : 1, height : 177},{name : 'b', age : 2, height : 188}];
var json = JSON.stringify(box, ['name', 'age'], 4);
alert(json);

PS:如果不需要保留缩进,则不填即可;如果不需要过滤结果,但又要保留缩进,则讲过滤结果的参数设置为 null。如果采用函数,可以进行复杂的过滤。

var box = [{name : 'a', age : 1, height : 177},{name : 'b', age : 2, height : 188}];
var json = JSON.stringify(box, function (key, value) {
   switch (key) {
       case 'name' :
           return 'Mr. ' + value;
       case 'age' :
           return value + '岁';
       default :
           return value;
}
}, 4);
alert(json);

PS:保留缩进除了是普通的数字,也可以是字符。

还有一种方法可以自定义过滤一些数据,使用 toJSON()方法,可以将某一组对象里指定返回某个值。

var box = [{name : 'a', age : 1, height : 177, toJSON : function () {
       return this.name;
}},{name : 'b',age : 2, height : 188, toJSON : function () {
       return this.name;
}}];
var json = JSON.stringify(box);
alert(json);

PS:由此可见序列化也有执行顺序,首先先执行 toJSON()方法;如果应用了第二个过滤参数,则执行这个方法;然后执行序列化过程,比如将键值对组成合法的 JSON 字符串,比如加上双引号。如果提供了缩进,再执行缩进操作。

解析 JSON 字符串方法 parse()也可以接受第二个参数,这样可以在还原出 JavaScript 值的时候替换成自己想要的值。

var box = '[{"name" : "a","age" : 1},{"name" : "b","age" : 2}]';
var json = JSON.parse(box, function (key, value) {
if (key == 'name') {
     return 'Mr. ' + value;
} else {
     return value;
}
});
alert(json[0].name);

JavaScript(Ajax)

学习要点:1.XMLHttpRequest、2.GET 与 POST、3.封装 Ajax

2005 年 Jesse James Garrett 发表了一篇文章,标题为:“Ajax:A new Approach to WebApplications”。他在这篇文章里介绍了一种技术,用他的话说,就叫:Ajax,是 AsynchronousJavaScript + XML 的简写。这种技术能够想服务器请求额外的数据而无须卸载页面(即刷新),会带来更好的用户体验。一时间,席卷全球。

一.XMLHttpRequest

Ajax 技术核心是 XMLHttpRequest 对象(简称 XHR),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。在 XHR 出现之前,Ajax 式的通信必须借助一些 hack 手段来实现,大多数是使用隐藏的框架或内嵌框架。

XHR 的出现,提供了向服务器发送请求和解析服务器响应提供了流畅的接口。能够以异步方式从服务器获取更多的信息,这就意味着,用户只要触发某一事件,在不刷新网页的情况下,更新服务器最新的数据。

虽然 Ajax 中的 x 代表的是 XML,但 Ajax 通信和数据格式无关,也就是说这种技术不一定使用 XML。

IE7+、Firefox、Opera、Chrome 和 Safari 都支持原生的 XHR 对象,在这些浏览器中创建 XHR 对象可以直接实例化 XMLHttpRequest 即可。

var xhr = new XMLHttpRequest();
alert(xhr); //XMLHttpRequest

如果是 IE6 及以下,那么我们必须还需要使用 ActiveX 对象通过 MSXML 库来实现。在低版本 IE 浏览器可能会遇到三种不同版本的 XHR 对象,即 MSXML2.XMLHttp、MSXML2.XMLHttp.3.0、MSXML2.XMLHttp.6.0。我们可以编写一个函数。

function createXHR() {
   if (typeof XMLHttpRequest != 'undefined') {
      return new XMLHttpRequest();
} else if (typeof ActiveXObject != 'undefined') {
   var versions = [
        'MSXML2.XMLHttp.6.0',
        'MSXML2.XMLHttp.3.0',
        'MSXML2.XMLHttp'
   ];
   for (var i = 0; i < versions.length; i ++) {
      try {
        return new ActiveXObject(version[i]);
      } catch (e) {
         //跳过
      } 
   } 
} else {
     throw new Error('您的浏览器不支持 XHR 对象!');
} 
}
var xhr = new createXHR();

在使用 XHR 对象时,先必须调用 open()方法,它接受三个参数:要发送的请求类型(get、post)、请求的 URL 和表示是否异步。

xhr.open('get', 'demo.php', false); //对于 demo.php 的 get 请求,false 同步

PS:demo.php 的代码如下:

 //一个时间

open()方法并不会真正发送请求,而只是启动一个请求以备发送。通过 send()方法进行发送请求,send()方法接受一个参数,作为请求主体发送的数据。如果不需要则,必须填 null。执行 send()方法之后,请求就会发送到服务器上。

xhr.send(null); //发送请求

当请求发送到服务器端,收到响应后,响应的数据会自动填充 XHR 对象的属性。那么一共有四个属性:

JavaScript(XML)_第6张图片
xml6.jpg

接受响应之后,第一步检查 status 属性,以确定响应已经成功返回。一般而已 HTTP 状态代码为 200 作为成功的标志。除了成功的状态代码,还有一些别的:

对于返回的结果类型,有 10 中不同的类型
HTT状态码 状态字符串 说明
200 ok 服务器成功返回了页面
400 Bad Request 语法错误导致服务器不识别
401 Unauthorized 请求需要用户认证
404 Not found 指定的 URL 在服务器上找不到
500 Internal Server Error 服务器遇到意外错误,无法完成请求
503 ServiceUnavailable 由于服务器过载或维护导致无法完成请求

我们判断 HTTP 状态值即可,不建议使用 HTTP 状态说明,因为在跨浏览器的时候,可能会不太一致。

addEvent(document, 'click', function () {
    var xhr = new createXHR();
    xhr.open('get', 'demo.php?rand=' + Math.random(), false); //设置了同步
    xhr.send(null);
    if (xhr.status == 200) { //如果返回成功了
       alert(xhr.responseText); //调出服务器返回的数据
    } else {
       alert('数据返回失败!状态代码:' + xhr.status + '状态信息:' +       xhr.statusText);
}
});

以上的代码每次点击页面的时候,返回的时间都是时时的,不同的,说明都是通过服务器及时加载回的数据。那么我们也可以测试一下在非 Ajax 情况下的情况,创建一个demo2.php 文件,使用非 Ajax



同步调用固然简单,但使用异步调用才是我们真正常用的手段。使用异步调用的时候,需要触发 readystatechange 事件,然后检测 readyState 属性即可。这个属性有五个值:

状态 说明
0 未初始化 尚未调用 open()方法
1 启动 已经调用 open()方法,但尚未调用 send()方法
2 发送 已经调用 send()方法,但尚未接受响应
3 接受 已经接受到部分响应数据
4 完成 已经接受到全部响应数据,而且可以使用
addEvent(document, 'click', function () {
var xhr = new createXHR();
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        alert(xhr.responseText);
      } else {
        alert('数据返回失败!状态代码:' + xhr.status + '状态信息:' +          xhr.statusText);
} }
};
xhr.open('get', 'demo.php?rand=' + Math.random(), true);
xhr.send(null);
});

PS:使用 abort()方法可以取消异步请求,放在 send()方法之前会报错。放在 responseText之前会得到一个空值。

二.GET与POST

在提供服务器请求的过程中,有两种方式,分别是:GET 和 POST。在 Ajax 使用的过程中,GET 的使用频率要比 POST 高。

在了解这两种请求方式前,我们先了解一下 HTTP 头部信息,包含服务器返回的响应头信息和客户端发送出去的请求头信息。我们可以获取响应头信息或者设置请求头信息。我们可以在 Firefox 浏览器的 firebug 查看这些信息。

//使用 getResponseHeader()获取单个响应头信息
alert(xhr.getResponseHeader('Content-Type'));
//使用 getAllResponseHeaders()获取整个响应头信息
alert(xhr.getAllResponseHeaders());
//使用 setRequestHeader()设置单个请求头信息
xhr.setRequestHeader('MyHeader', 'Lee'); //放在 open 方法之后,send 方法之前

PS:我们只可以获取服务器返回回来响应头信息,无法获取向服务器提交的请求头信息,自然自定义的请求头,在 JavaScript 端是无法获取到的。

2.1、GET 请求

GET 请求是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查询字符串参数追加到 URL 的末尾,以便提交给服务器。

xhr.open('get', 'demo.php?rand=' + Math.random() + '&name=Koo', true);

通过 URL 后的问号给服务器传递键值对数据,服务器接收到返回响应数据。特殊字符传参产生的问题可以使用 encodeURIComponent()进行编码处理,中文字符的返回及传参,可以讲页面保存和设置为 utf-8 格式即可。

//一个通用的 URL 提交函数
function addURLParam(url, name, value) {
   url += (url.indexOf('?') == -1 ? '?' : '&'); //判断的 url 是否有已有参数
   url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
   alert(url);
   return url;
}

PS:当没有 encodeURIComponent()方法时,在一些特殊字符比如“&”,会出现错误导致无法获取。

2.2、POST 请求

POST 请求可以包含非常多的数据,我们在使用表单提交的时候,很多就是使用的 POST传输方式。

xhr.open('post', 'demo.php', true);

而发送 POST 请求的数据,不会跟在 URL 的尾巴上,而是通过 send()方法向服务器提交数据。

xhr.send('name=Lee&age=100');

一般来说,向服务器发送 POST 请求由于解析机制的原因,需要进行特别的处理。因为POST 请求和 Web 表单提交是不同的,需要使用 XHR 来模仿表单提交。

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

PS:从性能上来讲 POST 请求比 GET 请求消耗更多一些,用相同数据比较,GET 最多比 POST 快两倍。

上一节课的 JSON 也可以使用 Ajax 来回调访问。

var url = 'demo.json?rand=' + Math.random();
var box = JSON.parse(xhr.responseText);

2.3、封装Ajax

因为 Ajax 使用起来比较麻烦,主要就是参数问题,比如到底使用 GET 还是 POST;到底是使用同步还是异步等等,我们需要封装一个 Ajax 函数,来方便我们调用。

function ajax(obj) {
    var xhr = new createXHR();
    obj.url = obj.url + '?rand=' + Math.random();
    obj.data = params(obj.data);
    if (obj.method === 'get') obj.url = obj.url.indexOf('?') == -1 ?
obj.url + '?' + obj.data : obj.url + '&' + obj.data;
    if (obj.async === true) {
        xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) callback();
        };
    }
xhr.open(obj.method, obj.url, obj.async);
if (obj.method === 'post') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(obj.data);
} else {
xhr.send(null);
}
if (obj.async === false) {
callback();
}
function callback () {
if (xhr.status == 200) {
obj.success(xhr.responseText); //回调
} else {
alert('数据返回失败!状态代码:' + xhr.status + ',
状态信息:' + xhr.statusText);
} } }
//调用 ajax
addEvent(document, 'click', function () { //IE6 需要重写 addEvent
ajax({
method : 'get',
url : 'demo.php',
data : {
'name' : 'Lee',
'age' : 100
},
success : function (text) {
alert(text);
},
async : true
});
});
//名值对编码
function params(data) {
var arr = [];
for (var i in data) {
arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(data[i]));
}
return arr.join('&');
}

PS:封装 Ajax 并不是一开始就形成以上的形态,需要经过多次变化而成。

你可能感兴趣的:(JavaScript(XML))