使用SenchaTouch开发跨平台移动Web应用精品资料.doc
Sencha Touch 是由 Sencha 公司开发的移动 Web 应用开发框架,用以提升主流移动设备在浏览器上的触碰操作,增强用户体验。该框架以久负盛名的 Ext JS 富客户端框架为基础,并支持最新的 HTML5 及 CSS3 标准,与流行的 Apple iOS 和 Andriod 设备兼容。一方面,它以 Webkit 浏览器引擎为基础,提供了出色的性能和用户体验;另一方面,它提供了基于 GPL V3 许可的开源版本和详尽的 API 文档,体现了良好的开放性和易用性。因此,该框架可帮助移动应用开发人员提升开发效率,从而创造出更多富有创意的移动应用。随着智能移动设备的普及和 3G 通讯技术的发展,将会有越来越多的传统应用部署为移动 Web 应用,而良好兼容性和操控性是 Web 应用成功的关键。本文将分为以下四个部分介绍 Sencha Touch 的独特之处,并结合示例为相关移动应用的开发人员编写良好兼容性和操控性的 Web 程序提供借鉴。v 功能与特性v 界面组件v 动画效果v 应用开发环境的搭建及相关 JavaScript API 用法与众不同的 Sencha Touch:功能和特性Sencha Touch 是世界上第一个支持 HTML5 和 CSS3 标准的移动应用框架,你可以使用 HTML5 来编写音频和视频组件,还可以使用 LocalStorage Proxy 来存储离线数据,同时,大量 CSS3 样式表为你提供了创建健壮样式层的可能。该框架在提供丰富功能的基础上对 JavaScript 库文件进行合理优化,使得经过 gzipped 压缩后的库文件在 120kb 以下,最大限度地提升了 Web 应用在浏览器中的加载速度,增强了用户体验。除了对最新标准的支持,该框架最大的特色正如其名,增强了对手持移动设备触控操作的支持,除了支持浏览器标准的触摸事件,还额外添加了如 tap, double tap, swipe, tap-hold, pinch 和 rotate 等富有吸引力的操作事件,使用户体验到与原生程序一样的效果。Sencha Touch 另一大优势在于其跨平台性,由于 Apple iOS 和 Andriod 设备有其独立的开发、测试和运行环境,针对某一平台开发的应用在另一平台是不兼容的,这大大增加了移动应用的开发成本。而基于 Sencha Touch 开发的 Web 应用具有与原生应用相同的用户体验,同时兼容 Apple iOS、Andriod 和黑莓 RIM 6 设备,可以满足大部分的市场需求。此外,借助 Ext JS 多年来对 Ajax 数据集成的经验,该框架提供了丰富的数据处理功能。开发人员能够方便地处理各种格式的数据如 XML、JSON,并能灵活地绑定到可视化组件加以展示。个性鲜明的 UI 组件表单是用户与应用程序交互的基本媒介,如用户信息注册、应用程序配置、个人评论的发表这些常见的 Web 应用场景都需要表单组件的支持。Sencha Touch 为我们提供了形式多样、操作简单的 表单组件 。图 1 中第一个界面所展示的是基本表单元素,包括多种类型的输入框,如纯文本、密码、邮件、URL 地址等类型,并能根据用户输入的文本进行有效性验证,以减少开发者对用户输入格式的验证代码,同时,基本表单还支持单选、多选、日历选择、多行文本输入等控件类型。图 1 第二个界面展示了触控屏手持设备所特有的滑动条组件,适合调整一些连续性的数值和作为切换的开关按钮。图 1 中第三个界面展示了建立在工具条上的表单控件,非常适用于搜索和文本过滤的应用场景。图 1 表单组件列表是移动 Web 应用展示信息最为常见的组件,其中比较有特色的是分组列表(Grouped List)(如 图 2 中第一个界面),它可以根据所列项目的首字母进行排序分组,当用户触摸屏幕右侧字母索引时,屏幕可快速滚动并定位至对应分组,非常适合于列表信息较多的情况,如联系人列表,歌曲列表等。图 2 中第二个界面所示的嵌套列表(Nested List)则非常适合于展示信息有层级关系的情况,如浏览论坛时的“讨论区 -> 主题帖 -> 原帖及回复内容”这样的层次关系。图 2 列表组件精致形象的图标和布置合理的工具栏是 Apple iOS 原生应用引以为傲的部分,而 Sencha Touch 也可以做到这一点。图 3 中第一个界面所示的是框架内置的图标样式,已可以满足大部分应用的需要,开发人员还可以通过自定义图标样式来扩展出更多更丰富的图标。如 图 3 中第二个界面所示,图标所在的工具栏布置方式也灵活多样,即可在屏幕顶部或者底部,也可以多个层叠,并可以在工具栏上布置形状各异的按钮。图 3 中第三个界面所展示的是根据底部 Tab 标签页而进行切换的面板,不同的面板中可以包含不同的主题内容。图 3 图标、工具栏和标签页如果你以为 Sencha Touch 只能做到以上这些小儿科,那就错了,下面展示了一些高级的 UI 功能。图 4 中第一个界面类似于 Apple iOS 设备上的 SpringBoard 操作,可以通过手指的左右或者上下滑动,来旋转切换界面窗口;图 4 中第二个界面显示了一个窗口重叠的效果,当上层弹出窗口激活时,下层窗口的操作是被屏蔽的,在提醒用户执行一些重要操作的场景中(如删除或者保存),这样的 UI 组件是非常好用的。图 4 旋转切换与窗口的重叠效果酷炫的动画效果一直以来,基于浏览器的 Web 程序动画效果常被人诟病,尤其是基于 JavaScript 的动画效果库相对于原生的应用程序来说,还是存在一定的差距,而刚发布的 Sencha Touch 1.1.0 版本就支持多达六种动画效果,分别是 cube、fade、flip、pop、slide 和 wipe。以最为酷炫的 3D 旋转 Cube 动画 为例,它将当前显示的界面面板(称之为 Card)想象为立方体的一个面,而即将展示的 Card 作为相邻的另外一个面,以左上方的顶点作为旋转基点进行旋转,从而得到 Card 之间切换的动画效果。大家一定很好奇它是如何做到这一点的,我们通过查看该动画效果的源代码即可找到答案。图 5 Cube 动画效果示例在文件 sencha-touch-1.1.0srccoreAnim.more.js 中,可以看到以下代码片段清单 1this.from = '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg)' + (showTranslateZ ? ' translateZ(' + fromZ + 'px)': '') + fromTranslate, '-webkit-transform-origin': origin ; this.to = '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) translateZ(' + toZ + 'px)' + toTranslate, '-webkit-transform-origin': origin ;由于 Sencha Touch 的动画组件是基于 Webkit 核心的浏览器,所以其动画效果实际上是基于 Webkit 的 3D 转换引擎,代码中 this.from 指的是当前 Card 如何旋转消失的属性,而 this.to 指的是要目标的 Card 如何旋转得以呈现,具体 CSS 属性的含义可参考 官方文档 。基于 Web 的博客浏览示例:应用开发环境的搭建、代码结构及测试随时随地获取自己想要关注的信息是移动计算环境最直接的用途。本文将以一个简单的博客订阅与浏览程序为例,展示基于 Sencha Touch 进行移动 Web 应用开发的流程,帮助开发人员更快的熟悉该编程框架。博客订阅与浏览应用的主要功能是订阅自己关注的博客 RSS 源,浏览对应博客的主题列表,查看博文内容。为了实现以上需求,开发人员需要完成以下几个步骤的工作。搭建开发环境第一,下载 Sencha Touch 库文件 ,并将其解压到本地目录 %sencha-touch-home%;第二,进入 Eclipse Java EE IDE,创建一个名为 myblog 动态 Web 工程;第三,在本地安装 Apache Tomcat 6.0.x ,在 eclipse 中将其配置为 Web server 并将 myblog 工程部署其中以备测试;第四,安装 Andriod Virtul Machine 环境,用以启动一个虚拟设备来测试 Web 应用的效果。需要说明的是,对于静态的 Sencha Touch 工程,Tomcat 并不是必须的,可使用任意 HTTP Server 来部署应用,但由于本例中使用了 Servlet 解析 RSS 源来降低客户端负载,因此采用了 Servlet 容器 Tomcat。创建代码结构一个典型的 Sencha Touch 工程主要由几个部分组成:sencha-touch 库文件,JavaScript 文件,CSS 文件,图标文件以及静态 HTML 文件。sencha-touch 库文件至少要包含默认的 CSS 文件 sencha-touch.css 和默认的 JavaScript 文件 sencha-touch.js,值得一提的是,为了便于在开发调试阶段更准确地定位和解决问题,开发包中还包含了 CSS 和 JavaScript 对应的 debug 版本,开发人员可在开发阶段使用该版本,而在产品部署阶段再替换为对应的正式版。图 6 工程目录结构创建 HTML 和 JavaScript 文件创建 Sencha Touch 应用的第一步就是创建一个 HTML 首页文件用于链接 Sencha Touch 库的 CSS 和 JavaScript 文件。我们博客浏览示例的 HTML 文件是 index.html,其内容如下:清单 2<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>My BLOG</title> <link rel="stylesheet" href="sencha-touch/resources/css/sencha-touch.css" type="text/css"> <link rel="stylesheet" href="css/index.css" type="text/css"> <script type="text/javascript" src="sencha-touch/sencha-touch-debug.js"></script> <script type="text/javascript" src="js/index.js"></script> </head> <body></body> </html>当创建好 HTML 文件之后,接下来就需要创建应用程序的 JavaScript 文件 index.js,由于该示例是以浏览为主,因此选用 NestedList 组件作为 UI 界面的主体,相关代码如下:清单 3Ext.setup( icon : 'img/icon.png', tabletStartupScreen : 'img/tablet_startup.png', phoneStartupScreen : 'img/phone_startup.png', glossOnIcon : false, onReady : function() . var nestedList = new Ext.NestedList( fullscreen : true, title : '我的订阅博客', displayField : 'text', dockedItems : topbar, bottombar , store : store, getDetailCard : function(record, parentRecord) return new Ext.ux.DescBox( value : 'Loading.', scroll : direction : 'both', eventTarget : 'parent' ); ); . ); );可以看到,index.js 的第一行代码调用了 Ext.setup() 方法,用以建立一个触控设备的 Web 页面,该方法可以为你的应用设置不同的启动属性和行为,例如示例代码中的:v icon,设置该应用默认的图标;v tabletStartupScreen,该属性设置在平板电脑上的启动图标;v phoneStartupScreen,该属性设置在智能手机上的启动图标;v glossOnIcon,该属性设置是否在默认图标上呈现光环效果;v onReady,该方法会在页面加载完毕,浏览器中的 DOM 模型已经建立完成时被调用。由于为了保证程序在运行时所依赖的 JavaScript 文件都已经加载完毕,我们一般将应用启动的逻辑置于该方法内,类似于 Java 程序的 main 方法。在定义 NestedList 组件时,有四点值得我们注意:1. 界面布局:通过 dockedItems 属性,指明了 NestedList 顶部和底部分别放置了工具栏 topbar 和 bottombar,topbar 主要用来便于用户登录和设置偏好信息,bottombar 主要是用来提供浏览博客时的一些常用操作,如订阅新的 RSS 源,删除选择的博客,刷新博客列表,给好的博文加星推荐以及回复功能。为了生成工具栏,需要生成一个 Ext.Toolbar 对象的实例,以 bottombar 为例,其代码如下:清单 4var bottombar = new Ext.Toolbar( dock : 'bottom', defaults : ui : 'plain', iconMask : true , scroll : 'horizontal', sortable : true, layout : pack : 'center' , items : iconCls : 'add', handler : function(btn, event) addform.setCentered(true); addform.show(); , iconCls : 'trash' , iconCls : 'refresh' , iconCls : 'favorites' , iconCls : 'action' );该对象中主要定义了以下属性:Ø dock,工具栏的放置位置,可选值有 top 和 bottom;Ø defaults,默认图标的 UI 效果,其中 ui 指背景颜色的样式,可选值有 dark,light 和 plain;Ø scroll,滚动方向,可选值有 horizon,vertical 和 both;Ø layout,表示工具栏图标的布局方式,示例中表示的是居中排列。值得注意的是该属性应该由一个 Object 对象来指定而不是 string;Ø items,该属性用于指定一个数组,来表示工具栏中的图标元素的集合,每个图标对象至少需要有一个 iconCls 属性来指定其样式,而 handler 属性则用于指定处理图标点击事件的方法,该方法回调时会传入两个参数 function(btn, event),第一个指当前被触发事件的对象,第二个指被触发的事件类型,本例中通过该方法弹出一个表单窗口用于提供给用户输入感兴趣的博客 RSS 订阅源。 图 7 RSS 订阅源添加表单2. 获取数据:从后台通过相关 API 获取数据并展示在 UI 组件上,是实现 Web 应用的核心问题,Sencha Touch 组件一般都是通过指定 store 数据源对象来实现的。例如在本例中,采用 Ext.data.TreeStore 对象来定义在 NestedList 中层次化数据的获取,其相关代码如下:清单 5Ext.regModel('ListItem', idProperty : 'text', fields : name : 'text', type : 'string' , name : 'link', type : 'string' , name : 'description', type : 'string' ); var store = new Ext.data.TreeStore( model : 'ListItem', proxy : type : 'ajax', url : '/myblog/list', reader : type : 'tree', root : 'items' );首先,通过 model 属性来指明返回数据的模型,该模型是通过 Ext.regModel() 方法来建立的,主要是为了告诉程序返回数据是什么结构;其次,通过 proxy 属性来指明返回数据的获取方式,该框架中主要有两种 Proxy,Client proxy 和 Server proxy,Client Proxy 主要用于存储本地数据,其子类有三个:Ø LocalStorageProxy,在浏览器支持的情况下将数据保存至 localStorage;Ø SessionStorageProxy,在浏览器支持的情况下将数据保存至 sessionStorage;Ø MemoryProxy,将数据保存在内存中,但是当页面刷新时,数据都将会丢失。Server proxy 主要用于存储一些通过远程请求服务器而获取的数据,它包括:Ø AjaxProxy,发送一个 HTTP 请求到相同域的服务器;Ø ScriptTagProxy,使用 JSON-P 发送请求到不同域的服务器。本例中采用的是最为常用的 Ajax 方式通过请求 servlet URL(/myblog/list) 来获取 JSON 数据。3. 自定义组件:使用 NestedList 时,开发者要注意的是我们需要自己实现 getDetailCard() 方法,用于定义对叶子节点数据的查看 UI 组件。非常幸运的是,Sencha Touch 框架为我们提供了良好的扩展机制用于自定义组件,这为我们构建结构清晰、面向对象的 JavaScript 程序打下了基础,示例中展示了如何扩展出一个自定义组件,代码片段如下:清单 6Ext.ux.DescBox = Ext.extend(Ext.Component, . afterRender : function() Ext.ux.DescBox.superclass.afterRender.apply(this, arguments); this.description = this.getTargetEl().createChild( tag : 'pre', html : this.value ); , getValue : function() return this.value; , setValue : function(description) this.value = description; if (this.rendered) this.description.update(this.value); );我们定义了一个博客内容描述信息展示组件 Ext.ux.DescBox,它继承自 Ext.Component 组件,并且自定义了 Get 和 Set 方法,同时重写了父类的 afterRender 方法,其中第一行的代码 Ext.ux.DescBox.superclass.afterRender.apply(this, arguments);必须调用,指的是将子类的参数应用到父类的构造方法中,类似于 Java 程序中的 super() 方法;第二行代码 this.description = this.getTargetEl().createChild(.)指在 Component 组件中创建一个 HTML 标签 pre, 并将 value 的值放置于 pre 标签中。4. 事件处理:人机交互和 UI 组件之间的切换,需要事件来驱动或触发,因此各 UI 组件都支持一系列特定的事件及其处理方法,可参阅 API 文档 。示例中需要依靠叶子节点触摸事件 leafitemtap 将显示界面切换为 Detail Card,就需要在 NestedList 组件上注册处理方法,其代码为:清单 7nestedList.on('leafitemtap', function(subList, subIdx, el, e, detailCard) var ds = subList.getStore(), r = ds.getAt(subIdx); detailCard.setValue(r.get("description"); );其回调函数的参数依次代表“触控条目(item)所在 List 组件”,“触控条目的 ID”,“触控条目 element 对象”,“触控事件对象”和“接下来要显示的 Detail Card 组件对象”。部署到 Apache Tomcat 6.x 进行测试编写好对应的 HTML、JavaScript 和后台处理的 Servlet 之后,可将动态 Web 工程打包成为标准的 WAR 包,部署至 Tomcat 的 webapp 文件夹,启动服务器;随后打开 Android Virtual Devices,启动其中的浏览器程序,并在 URL 地址栏输入 http:/<localhost IP address>:8080/myblog/,便可以对该应用进行测试了,运行的画面如 图 8 所示。由于 Sencha Touch 应用的跨平台性,使用其他任意一款基于 Webkit 内核的浏览器,如 iPhone4 的 Safari,也能得到相关的测试结果,而不仅限于文中示例的 Android 设备浏览器。图 8 应用运行及测试画面总结开发与原生程序一样酷炫界面的 Web 移动应用,一直是 Web 开发者的梦想,Sencha Touch 移动 Web 开发框架使得这一梦想不再遥远。该框架以其丰富的 UI 组件,个性化的动画效果,稳定的数据及事件处理机制,易扩展的编程模型,在移动 Web 应用这个新领域崭露头角。本文利用一个博客浏览程序简要介绍了利用该框架编写程序的流程及基本方法,然而距离一个成熟的 Web 应用还有相当的距离,如用户登录的安全性、多用户并发时后台的伸缩性以及客户端 JavaScript 的性能都有待提高,因此值得广大开发者深入的学习及实践。附录资料:从 XML 生成可与 Ajax 共同使用的 JSON时下,非常流行使用 JavaScript 代码为数据驱动的 Web 应用程序添加互动性。若能将数据编码成 JavaScript Object Notation(JSON)的格式,您就可以更轻松地通过 JavaScript 语言使用它。通过本文,发掘使用 XSLT V2 从 XML 数据生成 JSON 的几种不同方法。几年前,许多开发人员很看好 XML、XSLT、Extensible HTML (XHTML)和其他一些基于标记的语言。现在,Asynchronous JavaScript and XML(AJAX)成了新的热点,人们又将目光转向了使用 JavaScript 代码的数据驱动的富 Internet 应用程序。但是开发人员是否已经消除了 XML 和这一新技术之间的鸿沟呢?当然,您可以在 Web 客户机中使用 XML 解析器来读取数据,但这种做法会带来两个问题。第一,出于安全方面的原因,XML 数据只能从与此页面相同的那个域中读取。这虽然不是什么大的限制因素,但它的确会引起部署方面的问题,还会阻碍 DHTML 小部件的创建。第二,读取和解析 XML 会非常慢。另一种做法是让服务器执行 XML 的解析工作,方法是设置服务器,使之向浏览器发送以 JavaScript 代码或时下流行的 JavaScript Object Notation(JSON)编码的数据。本文将展示如下三种使用 XSLT V2 语言和 Saxon XSLT V2 处理器从 XML 数据生成 JSON 的技巧: l 简单编码 l 通过函数调用加载数据 l 编码对象 JSON 简介要学习如何将数据编码成 JSON(它只是 JavaScript 的一个子集),最好的方法是从数据开始。清单 1 显示了书籍列表的一个示例 XML 数据集。清单 1. 基本的图形化图书馆<?xml version="1.0" encoding="UTF-8"?><books> <book id="1"> <title>Code Generation in Action</title> <author><first>Jack</first><last>Herrington</last></author> <publisher>Manning</publisher> </book> <book id="2"> <title>PHP Hacks</title> <author><first>Jack</first><last>Herrington</last></author> <publisher>O'Reilly</publisher> </book> <book id="3"> <title>Podcasting Hacks</title> <author><first>Jack</first><last>Herrington</last></author> <publisher>O'Reilly</publisher> </book></books>这个数据集很简单,只包含三本书,每本书都具有惟一的 ID、书名、作者姓名及出版商的名字。(没错,我只选择了我自己的书作为数据集,但能怨我吗?这些书实在是不可多得的节日和生日礼物。)清单 2 显示了这些数据在 JSON 中的效果。清单 2. JSON 中的示例数据集 id: 1, title: 'Code Generation in Action', first: 'Jack', last: 'Herrington', publisher: 'Manning' , . 方括号 () 表明这是一个数组。大括号 () 则表明这是一个散列表,该散列表由一组名称和值对组成。在本例中,我创建了一个散列表的数组 用来存储这类结构式数据的一种常见方法。另外一点值得注意的是字符串是通过单引号或双引号被编码的。所以,如果我想用单引号编码 O'Reilly,我就必须使用反斜杠对它进行转义:'O'Reilly'。 这让我编写的这个 XSLT 样式表更为有趣了一些。我并未在本例中放上任何日期,但您也可以通过如下两种方法来编码日期。第一种方法是将日期作为字符串,该字符串必须在后面被解析。第二种方法是将日期作为一个对象,比如:publishdate: new Date( 2006, 6, 16, 17, 45, 0 )这段代码将 publishdate 的值设置为6/16/2006 5:45:00 p.m.。简单编码接下来我将陆续介绍 JSON 编码的几种技巧。第一种也是其中最简单的一种,此样式表如 清单 3 所示。清单 3. simple.xsl 样式表<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet xmlns:xsl="http:/www.w3.org/1999/XSL/Transform" version="2.0" xmlns:js=""> <xsl:output method="text" /><xsl:function name="js:escape"><xsl:param name="text" /><xsl:value-of select='replace( $text, "'", "'" )' /></xsl:function><xsl:template match="/">var g_books = <xsl:for-each select="books/book"><xsl:if test="position() > 1">,</xsl:if> id: <xsl:value-of select="id" />,name: '<xsl:value-of select="js:escape(title)" />',first: '<xsl:value-of select="js:escape(author/first)" />',last: '<xsl:value-of select="js:escape(author/last)" />',publisher: '<xsl:value-of select="js:escape( publisher )" />'</xsl:for-each></xsl:template></xsl:stylesheet>要理解此样式表,不妨先来看一下 清单 4 所示的输出。清单 4. simple.xsl 的输出var g_books = id: 1,name: 'Code Generation in Action',first: 'Jack',last: 'Herrington',publisher: 'Manning', id: 2,name: 'PHP Hacks',first: 'Jack',last: 'Herrington',publisher: 'O'Reilly', id: 3,name: 'Podcasting Hacks',first: 'Jack',last: 'Herrington',publisher: 'O'Reilly'这里,我将名为 g_books 的变量设置为一个包含三个散列表的数组,每个散列表包含关于该书的信息。再回过头来看看 清单 3,您会发现第一个模板匹配 "/" 路径,它也是首先应用到输入数据集的模板,该模板使用 for-each 循环来遍历每本书。之后,它使用 <value-of> 标记来将文本从该数据输出到 JavaScript 输出代码。对于字符串,我使用名为 js:escape() 的定制函数,它在模板之前定义。该函数使用一个正则表达式将一个单引号标记更改为带有反斜杠的单引号标记。最后一个重要的元素是 <xsl:output> 标记,它告知处理器要输出的是文本而不是 XML。要检验此过程是否可以正常工作,我加入了一个 simple .html 文件,该文件引用我在 simple.js 保存的 XSL 样式表的输出。这个 HTML 文件如 清单 5 所示。清单 5. simple.html 文件<html><head><title>Simple JS loader</title><script src="simple.js"></script></head><body><script>document.write( "Found "+g_books.length+" books" );</script></body></html>.html 文件使用 <script> 标记简单地加载已编码了的 JavaScript 代码。之后,第二个 <script> 标记将数组的长度写出到浏览器页面,如 图 1 所示。图 1. simple.html 的输出好了!数据文件包含三本书,相应的 JavaScript 文件也包含三本书。它真的可以工作!通过函数加载上述第一个示例很简单,而且在大多数情况下可以发挥其作用,但它存在一些问题。第一个问题是对于数据何时被加载没有任何提示。如果数据是像页面那样被静态加载的,这不成问题。但是如果页面动态创建了一个 <script> 标记来按需加载数据,那么就很有必要知道 <script> 标记是何时完成的。实现此功能的最好的方法是让编码了的数据调用一个 JavaScript 函数,而不是只设置数据。这个概念很重要,所以我将花一些时间来介绍一下为什么您必须要通过动态生成的 <script> 标记来加载数据。页面加载后,从服务器获得数据是 Web 2.0 的核心功能。一种方法是使用 AJAX 机制通过到服务器的调用来加载 XML。然而,出于安全性的原因,AJAX 机制只限于从单一域获取数据。这在大多数情况下都没有问题,但有时,您可能需要 JavaScript 代码运行在他人的页面上(例如,Google Maps)。在这种情况下从服务器获得数据的