创世记:使用 WinJS 和 XML 创建动态对象






4.33/5 (2投票s)
操作指南:在运行时根据 XML 文件创建 JavaScript 对象。
引言
欢迎阅读最新的“创建论”系列文章。该系列文章关注使用常见的微软技术创建的各种构造。本文将介绍如何基于 XML 文件创建 JavaScript 对象。文章将假定您具备 JavaScript、WinJS 和 XML 的基础知识。
问题所在
我正在开发一个 Windows 8 应用,需要一组易于维护的值供 JavaScript 使用。我最初的尝试是创建一个包含所需值的 Constants 对象。随着常量数量的增加,这很快变得难以管理。为了解决这个问题,一个类似于 ASP.Net 的 web.config 文件会很棒。但问题是我不想像 ASP.NET 网站那样,受限于通过某种 AppSettings
属性来访问它。我更倾向于一个 JavaScript 对象,它包含我需要的属性,但数据来自配置文件。我该如何实现呢?
解决方案
解决我问题的办法是在运行时创建一个 JavaScript 对象,该对象根据 XML 文件包含属性和值。JavaScript 的一些特性使得这个解决方案可行:
- JavaScript 是一种高度动态的语言。考虑到这一点,在运行时创建对象应该就像 Eddie Van Halen 弹吉他一样得心应手。
- 可以使用普通的数组语法来访问和操作对象属性。
- JavaScript 可以访问服务器数据。AJAX 就是这个缩写的缩写。
- 既然已经获得了 XML 文件,就可以像解析普通 HTML 文档一样对其进行解析。
结合这四点实现,我就可以在运行时使用 XML 文件的数据来创建一个对象。下一节将解释如何做到这一点。
实现
实现这个功能有三个基本步骤。WinJS 提供的机制将非常有帮助。首先,需要获取 XML 文件。这可以通过 WinJS.xhr
函数来实现。更多关于此函数的信息可以在 MSDN 上找到:“WinJS.xhr 函数 (Windows)”。下一步是解析 XML 文件,然后根据从文件中检索到的数据创建 Constants
对象。这些步骤将在以下各节中更详细地解释。
配置文件
要使用该文件,需要先创建它。下面是文件中的示例内容:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<Constant1>
<add name="String" value="Value 1" />
<add name="Integer" value="1" />
</Constant1>
<Constant2>
<Boolean>
<add name="IsTrue" value="true" />
<add name="IsFalse" value="false" />
</Boolean>
</Constant2>
<Constant3>
<add name="SubConstant" value="SubValue" />
<NextSubConstant>
<add name="NextSub" value="NextSubValue" />
</NextSubConstant>
</Constant3>
</Configuration>
可以看到,这只是一个普通的 XML 文件。有各种嵌套的节点和属性,以展示此解决方案支持的一些可能的节点结构。在这篇文章中,文件名是“config.xml”,根节点是“Configuration”。
数据
WinJS.xhr
函数用于获取 XML 文件。此函数接受一个对象来配置请求,并返回一个 Promise
。如下所示:
WinJS.xhr({
url: "config.xml",
headers: {
"If-Modified-Since": "Wed, 2 Feb 2000 11:11:11 GMT"
},
responseType: "document"
}).done(function(result) {
if (result.status === 200) {
var data = result.responseXML;
var root = data.querySelector("Configuration");
WinJS.Namespace.define("ST.Constants");
iterate(root, ST.Constants);
} else {
/* ... Handle missing document ... */
}
});
概述
上面展示的内容可以被认为是 jQuery AJAX 的升级版。调用 xhr
函数,传入一个对象来配置 XMLHttpRequest
。唯一必需的属性是 url
。其他可选属性可用于进一步配置 XMLHttpRequest
。这很像 jQuery 的 ajax
方法,但在这种情况下,会返回一个 Promise
对象。返回的 Promise
的 done
方法用于处理返回的 XML 文档。
配置 XMLHttpRequest
WinJS.xhr({
url: "config.xml",
headers: {
"If-Modified-Since": "Wed, 2 Feb 2000 11:11:11 GMT"
},
responseType: "document"
})
传递给 xhr
函数的参数是一个对象。如前所述,此对象用于配置 XMLHttpRequest
。这里将 url
设置为 XML 文件的路径。然后,使用 headers
属性,其中包含 “If-Modified-Since”
日期。这确保了 XML 文件的获取。最后一个属性是 responseType
,用于确保此调用的结果是一个可以被 JavaScript 解析的文档。
处理 XML 文件
.done(function(result) {
if (result.status === 200) {
var data = result.responseXML;
var root = data.querySelector("Configuration");
WinJS.Namespace.define("ST.Constants");
iterate(root, ST.Constants);
} else {
/* ... Handle missing document ... */
}
});
下一步是处理返回的 XML 文件。xhr
函数返回一个 Promise
对象。该对象包含一个 done
方法,可用于处理 xhr
函数调用的结果。与通常使用
s 的情况一样,第一步是确保请求成功。使用传递到 done 处理程序的 result,可以确定请求的状态。此 result 对象就是 XMLHttpRequest
XMLHttpRequest
。它包含一些有用的属性,其中有 status
和 responseXML
。成功的状态是 200。如果 status
是 200,函数将继续处理结果,首先从 result 对象获取 responeXML
。由于 responseType
设置为“document”,responseXML
的内容是可以通过 JavaScript 解析的 XML 文档。该文档的迭代将在下面更详细地描述,但在此之前还有两个步骤。迭代方法接受两个参数。第一个参数是要处理的节点。要开始,需要处理根节点,所以这是检索到的内容。第二个参数是要与节点一起处理的对象。该对象旨在是静态的,因此使用 WinJS.Namespace.define
方法来创建它。将这两个参数传递给 iterate
方法。下一节将提供更多详细信息。
迭代
有毛发、牙齿、肉体和骨骼,但 iterate
方法才是精髓。该方法内包含了 XML 文件的遍历以及对 Constants
对象的添加。下面是实现:
function iterate(node, ns) {
if (node && node.childNodes) {
if (node.childNodes.length > 0) {
var nodes = node.childNodes;
var len = nodes.length;
for (var i = 0; i < len; i++) {
var sub = nodes[i];
if (sub.nodeType === sub.ELEMENT_NODE) {
var name = sub.nodeName;
if (sub.childNodes.length > 0) {
ns[name] = {};
iterate(sub, ns[name]);
} else {
var attName = sub.getAttribute("name");
var attValue = sub.getAttribute("value");
//Key/Value Attribute
if (attName && attValue) {
ns[attName] = attValue;
//Node Name/Value
} else if (attValue && node.nodeName && !attName) {
ns[name] = attValue;
} else {
/* ... Handle Data Not Available ... */
}
}
}
}
} else if (node) {
var name = node.nodeName;
var attName = node.getAttribute("name");
var attValue = node.getAttribute("value");
//Key/Value Attribute
if (attName && attValue) {
ns[attName] = attValue;
//Node Name/Value
} else if (attValue && node.nodeName && !attName) {
ns[name] = attValue;
//Log Warning
} else {
/* ... Handle Data Not Available ... */
}
}
}
}
上面是 iterate
方法的实现。该方法对当前节点、子节点和属性进行各种验证检查。还有处理丢失数据的空间。
处理无子节点
该方法首先检查给定的节点及其子节点是否存在。如果存在任何子节点,则继续处理子节点。如果没有子节点,该方法会检查确保存在一个节点,并在成功时尝试获取 name
和 value
属性。如下所示:
else if (node) {
var name = node.nodeName;
var attName = node.getAttribute("name");
var attValue = node.getAttribute("value");
//Key/Value Attribute
if (attName && attValue) {
ns[attName] = attValue;
//Node Name/Value
} else if (attValue && node.nodeName && !attName) {
ns[name] = attValue;
//Log Warning
} else {
/* ... Handle Data Not Available ... */
}
}
这里有三种可能的路径可供选择:
- 如果
name
和value
属性都存在,则更新Constants
对象。将一个新属性添加到对象中。这部分利用了 JavaScript 对象属性可以通过普通数组语法访问和操作的优势。新属性以name
属性的值创建,并被赋值为value
属性的值。 - 如果存在
value
属性和节点名称,但不存在name
属性,则使用节点名称更新Constants
对象。新属性的名称是节点名称。此新属性的值是value
属性的值。 - 如果以上两种情况都不存在,则没有可用数据,因为此解决方案目前是这样实现的。这是一个处理这种情况的好地方,可以记录发生的事件、添加默认数据,或者以任何其他必要的方式进行处理。
带子节点的处理
如果给定的节点存在子节点,iterate
方法将继续处理子节点。下面是相关代码片段:
if (node.childNodes.length > 0) {
var nodes = node.childNodes;
var len = nodes.length;
for (var i = 0; i < len; i++) {
var sub = nodes[i];
if (sub.nodeType === sub.ELEMENT_NODE) {
var name = sub.nodeName;
if (sub.childNodes.length > 0) {
ns[name] = {};
iterate(sub, ns[name]);
} else {
var attName = sub.getAttribute("name");
var attValue = sub.getAttribute("value");
//Key/Value Attribute
if (attName && attValue) {
ns[attName] = attValue;
//Node Name/Value
} else if (attValue && node.nodeName && !attName) {
ns[name] = attValue;
} else {
/* ... Handle Data Not Available ... */
}
}
}
}
}
检查节点以确保它有子节点。如果确实有,则在简单的 for 循环中处理每个子节点。将当前子节点存储起来,然后检查它是否是一个元素节点。如果是,该方法会检查此节点的子节点。如果它们存在,则使用子节点的名称为给定的 Constants
对象创建一个新属性。然后再次调用 iterate
方法,这次传入当前子节点和 Constants
对象的新属性。这为添加 XML 文件中的嵌套节点提供了支持。
如果当前子节点没有子节点,它将按照上一节“处理无子节点”中的描述进行处理。
结果
正如您可能已经意识到的,此解决方案会在运行时创建一个对象。在调试对象结构时,可以使用 Watch 窗口进行查看:
这表明 Constants
对象的结构反映了 XML 文件的结构。
最初设想的增强功能
使用此解决方案提供了许多好处。它灵活、可扩展且性能良好。它在 XML 文件中提供了几乎无限级别的嵌套(除了在创建原型链时可能出现的调用堆栈限制)。由于配置文件是本地于应用程序的,因此没有跨网络造成的性能损失。除了这些优点之外,仍然可以进行一些改进。
属性值
到目前为止,常量的值已设置为 XML 文件中的属性值。也可以使用节点值。
数据类型
并非所有值都适合作为 String
。可以在迭代中添加额外的逻辑,使值具有适当的数据类型(例如,如果值应为 Integer
,则将其转换为 Integer
;如果值应为 Date
,则将其转换为 Date
)。
缓存
这会在运行时创建一个对象。不要每次应用启动时都创建同一个对象。首次创建对象时,将其存储起来。然后,当应用再次加载时,从存储中检索它。只在发生更改时创建此对象。
结论
本文介绍了一种通过更新 XML 文件中的值来轻松配置对象的方法。像大多数事情一样,这个解决方案有利有弊,但它能完成工作,并且做得很好。不用说,乐队很开心,对象也已栩栩如生。
感谢您的阅读,请查看我的其他创建论文章。
历史
- 2012-11-17:首次创建。
- 2013-03-03:更新了 Flickr 图片。