node.js和coroutine

很久之前,我就琢磨着要用js来写服务器端的东西,那时候只有netscape有整套的基于js的解决方案,当然那是要钱的。我用了asp+jscript做了一个wiki,但是asp毕竟不是开源的东西,又局限于windows平台,没有足够的组件可用,后来放弃了这条路子。然后曾经想自己来用Firefox的Spidermonkey的js引擎来写一个服务器端的js平台(其实确实也有一些),但一直没能够闲下来好好研究这个问题。而且那时候根据很多测试,Spidermonkey的性能和其他语言相比十分一般,也就和php差不多。

再后来Google推出了Chrome,其js引擎v8,把js的速度推上了一个新的层次,我当时就认为,v8可以用来搞服务器端脚本。然后就听说基于v8的node.js,而且听说node.js是针对高并发的。我满心欢喜,前几天终于有空研究一下node.js了。

继续阅读“node.js和coroutine”

Flash恐惧症

我发现我在开发上对Flash有恐惧症。Flash/Flex现在作为不可多的的RIA应用的开发平台,有着许多优势。但是我总是排斥使用Flash/Flex。虽然我在Flash5/MX的时代也做过些动画,那个时候还没有成形的ActionScript,还要用telltarget,Flash也并非开放标准的。当时能用Flash做RIA应用是非常了不起的,因为那个时候没有Flex这种套件框架,也没有适用的IDE,虽然我也很想尝试用Flash做一个社区,但是后来失败了。再后来我就再也没有碰过Flash。

然而我倒并不排斥Flash应用,包括AIR,我觉得很多优秀的应用用起来非常不错。究竟是什么令我在开发上如此排斥Flash呢?我仔细分析了一下,想到以下问题:

当时Flash不是开放的。当时Flash只能使用Macromedia Flash来制作,几乎没有其他够用的工具(我记得有个Swiss)。随着Flash的标准开放,现在情况改变了,有不少的第三方工具。不过因为当时Flash的不开放,让我错过了。

当时Flash在其他平台上的支持非常差。当然,现在已经好很多了。不过即使现在,Flash在Linux下的实现还是有些问题,CPU占用率非常高。Flash曾经是没有Linux开发工具的,现在也没有多少,当然后来Adobe推出了Flex for Linux,结果最近看一则新闻说因为Flex for Linux用得太少,所以Adobe要停止维护Flex for Linux了。这样看来,如果要跨平台,无论是开发时还是运行时,Flash比起html+js还是差一些。

过去Flash主要作为动画来使用,文件非常庞大,那时候也很少使用动态加载。现在用Flex做客户端应用非常方便,但是Flex相对于HTML来说,还是过于庞大。同样的,Flex开发套件也太庞大了,尤其是如果把Java也算进去的话。而我使用HTML/JS可以需要用文本编辑器和一个浏览器。

Flash并非事实上的标准,网站可以不使用Flash,却不能不使用HTML。现在主流的浏览器,都支持JavaScript,却不一定有Flash。

其实我认为我害怕使用Flash最关键的是在于我在开发上的思维转变,过去我很喜欢很酷很眩的特效,觉得有了这些东西,程序肯定就很牛B。但后来我意识到这种想法是幼稚的,这些特效都是浮云,要先抓住问题本质,要去掉这些复杂的外观,直接呈现内容。而如果不需要特效,Flash比起html + js就没有明显的优势了,而我更喜欢html+js的简洁快速。

~~~~~~~~~~~~~~~~~~~~~~~~

Flash作为RIA,目前是如日中天,AIR也是相当流行,但实际上是杀机四伏。

微软也推出了Silverlight与Flash直面竞争。

由于JavaScript的发展,各种JavaScript特效框架的出现,过去需要使用Flash才能实现的很多特效现在可以直接使用js实现,直接消除了一部分对于Flash的需求。

JavaScript的一些工具包,也能实现很多窗体控件,使开发简单,如extjs dojo jquery-ui等,除了IDE上支持不够。这对于Flash的Flex框架也是一种威胁。

Adobe的AIR还比较聪明,没有完全把赌注押在Flash上,兼容了html+js的开发,Mozilla已经推出了Prism来竞争,而Prism的最强大的地方莫过于既有的Web应用可以直接运行在Prism上。

其实对Flash最大的威胁来自于HTML5:

  • 过去JavaScript一直有跨域的问题,而且对长连接支持有些问题,HTML5中的Websocket为Ajax提供了更强大的通讯能力,Flash的优势又进一步减弱了。
  • 利用HTML5中canvas,JavaScript就可以进行高速的2D图形绘制,这也是抢了Flash的市场。
  • HTML5支持离线存储Dom Stoarge,Flash的又一个特性被直接支持

以上这些标准组件在某些浏览器中已经被支持了,Flash对于浏览器毕竟只是第三方支持,但是HTML5的这些东西将来可都是直接在浏览器核心中的。

我想还有一个不得不提的就是3D了,Flash现在也可以实现3D,但是Flash的3D效果只能算普通,效率也比较低,做复杂的3D应用是不适合的。在这方面,Java Applet却可以调用OpenGL,效率就不在同一个层次上了。而最近Google为Chrome推出的o3d,也是Flash强劲的对手。

所以,我觉得Flash未来的好日子有限。

在SpiderMonkey中产生可调用的对象

我应该对标题做一个更详细的解释:用C语言在SpiderMonkey中产生一个在JavaScript中可以当成函数被调用的对象,换句话说,就是一个非Function的对象,在JavaScript中可以被当成Function进行调用。例如,我有一个Hash对象,当我在var h = new Hash()之后,可以直接调用h(key),h并非一个函数对象,却可以以这种函数调用的方式来获取键key对应的值。

首先,必须要在创建这个Hash类的结构时,将JSClass中的”call”字段设置为相应的函数,如下:

现在,这里有一个很关键的问题便是如何在SpiderMonkey调用call_hash函数的时候,能够让call_hash函数知道被调用的对象(callee)是谁。然而,Mozilla的官方文档并没有对此作出任何解释。于是我在邮件列表中问了这个问题,有人给出了一个很特别的技巧——引擎调用call函数的时候,argv[-2]便是被调用者本身。


在把玩了Spidermonkey一段时间之后,我还是打算放弃spidermonkey,虽然这是一个很成熟很强大的脚本引擎,但是他的API还是有些混乱的,从本文的这个问题的解决方案就可以看得出来。

函数式JavaScript编程指南

函数式JavaScript编程指南

简介

你是否知道JavaScript其实也是一个函数式编程语言呢?本指南将教你如何利用JavaScript的函数式特性。

要求:你应当已经对JavaScript和DOM有了一个基本的了解。

写这篇指南的目的是因为关于JavaScript编程的资料太多了但是极少的资料提到了JavaScript的函数式特性。在本指南中,我只会讲解这些基本知识而不会深入其它的函数式语言或这是Lambda算子。

你可以点击所有的例子然后你所看到的代码就会被执行,这样就可以令指南变得具有交互性。你也可以使用这个沙箱来尝试。

第一课 —— 匿名函数

我们将首先介绍匿名函数。一个匿名函数就是一个没有名字的函数。
你可以认为他们是一次性函数。当你只需要用一次某个函数式,他们就特别有用。通过使用匿名函数,没有必要把函数一直放在内存中,所以使用匿名函数更加有效率。

例Example:

下面两个函数处理同样的事情,而average在给z赋值结束之后一直保留——但匿名函数则不会。

这很自然得引出了我们下面的一节课函数作为值

第二课 – 函数作为值

事实上,我们一般在JavaScript中声明函数的方式可以看作是一个简化了的语法(也就是语法糖syntactic sugar)。

例:

下面两个表达式其实完全一样。所以左边的表达式仅仅是右边的简写。

从这里可以得出一个结论,函数是一个值就像字符串、数字或数组一样。这还出现几个问题:

我是否可以把函数作为参数传递?
可以,见下面的例子。
是否可以实时生成函数?
当然了,这是一个高级的主题,它可以通过eval函数来完成。小提示:看看本页面的源代码。

例:

这个例子演示了如何把函数作为参数传递。

第三课 – 两种方式调用函数

在JavaScript中,有两种调用函数的方式。一般的方式是把参数放在括号中,如alert(42)。另一种方式是同时把函数和参数都放在括号中,如(alert)(42)

例:

为什么函数两边的括号很重要:如果你写了括号,那么在括号中的代码就会被先计算。在计算之后,括号所在的地方就会有一个值。这个值可能是一个字符串、一个数字或一个函数。

第四课 – “短路”条件调用

现在我们将学习如何使用“短路”条件调用。使用这个方法可以缩短源代码同时代码也变得更加可读。

例:

这个语法并不是用在左表达式上,而是用在右表达式上。

第五课 – 它好在哪里

OK,现在我们已经学习了一些函数式JavaScript的内容。那么它好在哪里?函数式JavaScript编程之所以很重要有三条主要的理由:

  1. 它有助于写出模块化和可服用的代码。
  2. 它对事件处理程序非常有效。
  3. 它很有趣!

在下面的篇幅中,我会给出更多关于前两条理由的信息

1. 模块化和可复用的代码

现在你已经知道如何将函数作为值使用,那么你也应该试试!一个很好的例子是数组内建的sort方法。预定义的sort()把所有的对象转换成字符串并把他们按照词语的顺序排序。但如果我们有用户自定义的对象或者数字那么它就不是很有用了。于是这个函数可以让你给他一个进行比较的函数作为参数,如sort(compareFunction)。这个方法让我们甚至不用接触实际的sort方法。

例:

2. 事件处理程序

对事件处理程序使用函数式编程也许是最直观的函数作为值得应用了。既然这样我们马上就演示一个例子。

简单的例子:;ie

现在有一个Button类,带一个自定义的onclick行为。

练习: 为什么我们要把alert包裹在一个匿名函数中?

高级例子:

现在我们想改进我们的Button类。每一个按钮都被分配了一个值当按钮被点击时显示该值。首先我们调整我们的类:

下面你也许要尝试写下面的代码:

如果你执行它你就会发现提示框中间是空的。为什么会这样呢?其实原因在于JavaScript的可见性规则。当onclick函数被执行时this指向的是按钮的DOM节点而非自定义的按钮对象。

我们如何解决这个问题? 使用函数式编程:

这种情况下执行该匿名函数会将v绑定到this.value上。

沙箱

在此处输入你的代码,并按下计算.


更多信息

下面是关于函数式JavaScript编程的一些有趣的链接:

展望

本节给大家展示一下JavaScript的未来。一个非常振奋人心的JavaScript特性——E4X,一个JavaScript中直接的XML支持。

JavaScript高级程序设计

profession-javascript-cover.jpg

基本信息

  • 【英文名】Professional JavaScript for Web Developers
  • 【作者】Nicholas C. Zakas
  • 【译者】曹力 张欣
  • 【ISBN】7115152098
  • 【出版时间】2006-9-15
  • 【页码】670
  • 【原出版社】Wrox
  • 【简介】本书从最早期Netscape浏览器中的Javascript开始讲起,直到当前它对XML和Web服务的具体支持,展示了如何充分利用这种功能强大的语言开发自己的应用程序,以解决当今Web开发者面对的商业问题。
  • 活动主页

译者序

继续阅读“JavaScript高级程序设计”

JavaScript = C + Lisp

作者: William Taysom

原文地址:http://www.jadetower.org/muses/archives/000307.html

翻译:ShiningRay

我在过去的几周内一直在写JavaScript代码——使用我们的对话框系统来个性化Mozilla。假设你要求:“嘿,电脑,我要教你如何在Amazon.com上找书。首先你象这样进入Amazon,然后在这里输入你要的书的名字。点击“Go”然后……”我的困难在于对Mozilla编码使我的对话框系统可以“看”浏览器中正在进行什么然后自己可以执行这些动作。

由于Mozilla中较高的层次是用JavaScript实现的。所以我一直在废寝忘食研究它(我的Rhino book里面全是我做的书签)我写的越多,我越觉得它像Lisp。

考虑以下代码:

这里SemTree是一个对象,它允许你从一个HTML DOM 树中选出某些你感兴趣的节点,去掉那些你不感兴趣的节点。(根本上说,这是一个TreeWalker 类的包装器。)若要建立一个 SemTree ,你要给出一个接受器。一个接受器只是一个判断给定节点是否能被接受的一个函数:

一旦有了一些基本的接受器和筛选器,很容易就可以定义组合筛选器──一种将筛选器以特殊形式组合起来的函数:

当它被调用的时候,以接受器作为参数,acceptAny返回一个新的接受器,可以接受只要是 disjuncts 能接受的那些给定节点 n。所以,semanticAccepter 中出现的acceptAny 能接受文本、链接、表单和链接中的图像。相反地,acceptOnlyIf只能接受被所有接受器组件接受的节点。acceptOnlyIf的定义类似于acceptAny

acceptOnlyIfacceptAny如此相似让我纳闷是否有一个通用的方法可以将任一个组合器(像否定、短路与、短路或)变成一个组合筛选器(象acceptNotacceptOnlyIfacceptAny)。的确有,但JavaScript不胜任这个任务。要完成这个,我们需要更强大的武器。

将函数的能力定义得和单子的功能一样微小

通过提供第一类型函数,JavaScript迈出了进入更广阔世界的第一步。而这个世界中最具影响的便是Haskell和它的一些变体。由于接受器告诉我们是否要接受一个节点,它们应该是一个从节点到布尔值的函数。在Haskell中,我们这样写:

这样,否定是一个布尔到另一布尔,合取和析取是从一个布尔的列表到一个单个布尔值,这样写:

组合接受器有类似的形式:

我们给semanticAccepter下的定义和JavaScript版的类似:

我们怎样定义一个类似acceptOnlyIf的组合器?Haskell没有像一些语言中的命令结构。取代的是递归:

由于某些原因,短变量名是Haskell中的标准。n是一个节点,a是一个接受器,as是一个接受器的列表。(以’s’结尾是代表某个变量是列表的标准形式。)你上面看到的定义是正确的,但我不常用这个方法。我会使用一个优美的小函数叫map

当传了一个函数和一个列表,map返回对列表中的每个元素执行函数后生成的列表。有了map后,acceptOnlyIf
就可以这样定义。

这里语法 (\a -> a n) 基本意思和JavaScript下面的一样:

整个acceptOnlyIf的定义本质上说明了,“给出一个节点n,找出每个接受器对n的值,然后返回这些值的合取值(和值AND)。”有了这种定义函数的优美方法之后,它们之间的相似之处立刻显现出来了:

这样,泛化就是一些琐碎的事了:

现在我们是否可以更进一步了呢?我们是否可以也将 not搞成 acceptNot呢?andnot的主要区别在于 and参数是一个布尔值的列表,而not只能针对单个布尔值。要更进一步泛化 liftCombiner,我们必须:

  1. 找出可以描绘出基本值和列表的共同特性的结构。
  2. 将这个结构应用到合成组合器的问题中.

Haskell 正好有我们所需要的。它称为单子Monad.

什么是单子?

之前就有人问过这个问题。单子到处可见。大多数结构和过程/进程像数据类型、函数、对象、、异常、I/O、副作用、同步、事务、分析器和编程语言,都可以接受单独的、原子的操作。组对(Pair)、元组(tuple)、列表、树、图——这些数据结构都有一个单子级的解释,是常常不止一个。由于是单子的东西太多了,所以很难对它们进行描述。不过我还是可以给你一个数学上的定义。但是正如“A continuation is the rest of the computation ”所说的,给出单子的定义只有在你已经对它有了一些感性的认识才有用。否则直接给出定义只会混淆你的观点。那先让我们研究一下这个谦虚的列表作为我们受到单子启发的途径。

检验结构有很多方法。其中一个方法是查看各部分是如何一起工作来组成整体的。当以这种方法分析列表的时候,我们发现它们是连接的解码:一个列表要么是空的要么是一个由一个head和一个tail组成的Cons(一种构造函数,返回一个新的列表)。head是一个列表的元素,tail则是列表的其它部分。

若要定义列表[1, 2, 3],我们这样写:To define the list [1, 2, 3], we write:

Haskell中的列表就是这样工作的。除了用[]代表 Nil和用: 代表 Cons。逗号可以用来分隔条目,可以写成这样:

链表有一个很长很有名的历史。不幸的是分解材料并不会显露出单子。

另一种分析列表的方法是通过研究它是如何和其它东西相关、进行交互的。Haskell提供了“class”机制根据和它们相关联的方法来定义对象。类似于抽象数据类型和接口:

这是一个看似完美的列表类,但它几乎没有从分解材料中抽象出什么东西。所有的方法,除了 map,都是特定于列表的逻辑结构的,map抓住了一个较抽象的概念。它将一个函数作为参数,然后返回一个被函数处理过新的列表。回忆一下 map 对定义 acceptAny acceptOnlyIf 起了多大的帮助?这很明确是个值得研究的函数。

还有哪些其它函数对列表作为一个独立于它特定实现和形态的数据结构来说是至关重要的呢?好吧,还应该有一个方法 unit来把一个单独的元素放入一个列表,而且我们还需要一个 join 来将一个列表的列表组成一个长的列表。这个类定义像这样:

以下是链表的实现:

这些方法一目了然:unit将参数放入列表,map对列表中的每个元素执行函数,join将一个列表的列表连成一串。让我们确定一下串连接符(++)的定义吧:

这些函数都十分简单而且十分有用。它们确实是列表的标准成分,但就好比火药一样,如果你把它们正确地组合起来,你就能引爆一些东西:一个单子。我们等不及这个式子了:

就是它了。两个函数:return将一些东西放入单子中,同时(>>=),称为“绑定”,将一个单子变成另一个。我们立即来看看单子到底能做什么。不过首先为了明确起见,我们先将我们的列表实现扩展成应用一个单子:

return定义和 unit一样:将它自己放入一个列表中。绑定 (>>=)将mapjoin 组合成一个操作。首先你将函数k映射到列表 ls ,然后由于 f返回一个列表的列表,你再用 join 将结果组成单个列表。

但单子这东西有什么好处呢?设想你想要将一个列表中的所有值和另一个列表中的值相加来产生一个大的列表:

JavaScript代码应该像:

应用了单子我们可以这样写:

代码看来很简练,但没什么特别的。幸运的是,由于单子是如此有用,所以更好的语法形式也出现很久了。我们应该这样写:

这称为“do notation做标记”为了明显起见。还有另一种变体,我个人喜欢叫“列表包含语法”或者“我无法相信这没有成为一个学说”:

不论哪种方法,都是所有的单子之上的一种较好语法。这种较好语法也许看上去像某种命令语言或什么蛇的东西(指Python语言──译注)。但我不接受其它替代语法,在Haskell中任何单子都能使用两者之一。

我们看看一个列表单子是如何工作的,但我们仅有的单子是 LinkedList。对于not,我们只需要一个直接变换值的单子就行了:一个一致单子。它不会对值做特殊的改变,仅仅直接返回值:

现在我们终于可以完整地定义 liftCombiner了:

现在“让组合器能工作在所有接受器上”的这个想法已经不难实现了:

最后的思考

今天我们看了如何处理组合的接受器(从节点到布尔的函数),组合器可以是任何单子。结果接受器也是单子。你认为是否有一种方法,可以让组合器组合任意单子和其它单子(除了接受器之外)?如果有,怎样做?如果没有,单子之间要有怎样的关联才能这样?

在Java2平台企业版中应用异步JavaScript技术和XML(AJAX)

作者Greg Murray, 2005年6月9日  翻译:ShiningRay@Nirvana Studio2005年9月9日
任何试过过Flickr、GMail、Google Suggest或者是Google Maps的人都会意识到一种新型的动态Web应用正在逐渐浮出水面。这些应用外观和表现都和传统的桌面应用程序很像,而他们不需要依赖于插件或者是特定于浏览器的功能。过去Web应用只是一系列HTML页面,他们任意一部份内容的更改都必须重新载入页面。像JavaScript编程语言和层叠样式表 (CSS)之类的技术已经成熟,可以有效地应用他们来创建高动态的Web应用,而且可以运行在所有的主流浏览器中。本文将会详细介绍你马上就可以使用的一些技术,让他们使你的Web应用像桌面应用更加丰富和更有交互性。

介绍异步JavaScript技术和XML(AJAX)


使用JavaScript技术,一个HTML页面可以异步地对服务器(一般是载入页面的服务器)发送请求并获取XML文档。然后JavaScript可以使用XML文档来更新或改动HTML页面的文档对象模型(DOM)。最近形成了一个术语AJAX(Asynchronous JavaScript Technology and XML)来描述这种交互模型。

AJAX其实不是很新的东西。这些技术对于Windows平台上专注于Internet Explorer的开发人员来说,已经存在好几年了。直到最近,这个技术才被作为Web远程技术或者远程脚本技术被大家了解。Web开发人员也有一段时间曾经使用过插件、Java applet和隐藏框架来模拟这种交互模型。最近发生的变化是,对XMLHttpRequest对象的支持已经成为所有平台上的主流浏览器都包括的特性了。JavaScript技术的XMLHttpRequest对象是。尽管在正式的JavaScript技术标准中并没有提到这种对象,然而今天主流的浏览器都对他提供了支持。而当代的浏览器如Firefox、Internet Explorer以及Safari在JavaScript技术和CSS的支持上有些细微的差别,但是这种差别是可以处理的。如果你要考虑支持较老的浏览器,AJAX也许就不能成为你的解决方法。

基于AJAX的客户端之所以独特的原因是客户端包含了用JavaScript嵌入的特定于页面的控制逻辑。应用JavaScript技术的页面基于事件进行交互,如文档载入、鼠标点击、焦点改变甚至是定时器。AJAX交互使得表现层逻辑更加清晰地与数据分离。一个HTML页面也可以根据需要每次读入适当的数据,而不是每次需要显示一个更改时都重新载入整个页面。AJAX要求一种不同的服务器架构来支持它这种交互模型。以前,服务器端Web应用关注于对每个导致服务器调用的客户端事件都生成HTML文档。然后客户端对每个回应都要重新读入并重新渲染完整的HTML页面。富Web应用(Rich Web Application)关注于,让一个客户端获取一个HTML文档让它表现为一个模板或者是一个容器,可以基于事件并使用从服务器端组件中获取的XML 数据来对文档注入内容。

一些AJAX交互的应用如:

  • 实时表单数据检验:像用户ID、序列号、邮政编码或者是特殊的票据代码这类需要服务器端验证的数据也可以在用户提交表单之前进行验证。
  • 自动补全:像电子邮件地址、姓名或城市名之类的表单数据都可以根据用户情况自动补全。
  • 处理细节操作:根据一个客户端事件,一个HTML页面可以根据现存的一些数据再去获取更多详细的信息,如现在有一个产品列表,客户端可以控制查看单独的产品信息而无需刷新页面。
  • 复杂的用户界面控件:像树型控件、菜单和进度条之类不要求页面刷新的控件也能实现。
  • 页面内刷新数据:HTML页面可以从服务器上查询最新的数据如分数、股指、天气还有其它的特定于应用的数据。
  • 服务器端通知:一个HTML页面可以通过对服务器进行定时查询来模拟一个服务器的事件通知推送,实现像通知客户端一个消息、刷新页面数据或将客户端重定向到另一个页面。

这个列表并未把所有的应用都列出来,但它已经显示了AJAX交互可以让Web应用比从前能做更多的事情。但尽管这些好处是值得关注的,这种方式也有一些缺点:

  • 复杂度:服务器端开发人员必需理解,HTML客户端页面中的表现层逻辑以及生成HTML客户端页面所需的XML内容的服务器端逻辑。HTML页面开发人员必须了解JavaScript技术。如果开发新的框架和发展已有的框架来支持这种交互模型,那么AJAX应用的创建就会越来越简单。
  • XMLHttpRequest对象的标准化:XMLHttpRequest对象还不是JavaScript技术标准的一部分,这就意味着根据客户端的不同,应用的行为也有所会不同。
  • JavaScript技术的实现:AJAX交互极大地依赖于JavaScript技术,而由于客户端的原因JavaScript还有一些细微的差别。见QuirksMode.org来了解更多关于浏览器之间区别的内容。
  • 调试:AJAX应用也难于调试,因为流程逻辑是同时嵌在客户端中和服务器上的。
  • 代码可见:客户端的JavaScript可以很容易通过“查看源代码”被人看见。一个没有良好设计的AJAX应用很可能被黑客攻击或被他人剽窃。

当开发人员在使用AJAX交互模型上获得更多的经验后,AJAX技术的框架和模式就会慢慢浮现出来。现在就关注于完全通用的AJAX交互框架,还为时过早。本文和相关的解决方案将关注于在现有的Java 2平台企业版(J2EE)上如何对AJAX进行支持,像servlet,JavaServer Page(JSP)软件、JavaServer Face应用和Java标准标签库(JSTL)。

AJAX交互剖析


现在我们已经讨论了AJAX是什么以及一些高层次的问题。那现在让我们把所有的零件放在一起来展示一个具有AJAX的J2EE应用。

首先考虑一个例子。一个Web应用包括了一个静态HTML页面,或者是一个由JSP生成的HTML页面,这个JSP中还包括了一个HTML表单,它需要服务器端逻辑来对表单中的数据进行检验,而不用刷新页面。一个名为ValidateServlet服务器端组件(servlet)用来提供这种验证逻辑。图一描述了这种具有验证逻辑的AJAX交互的细节。

AJAX Interaction
图1: 一个提供验证逻辑的AJAX交互

以下条目代表了图1中出来AJAX交互的过程:

  1. 发生一个客户端事件
  2. 创建和配置一个XMLHttpRequest对象。
  3. XMLHttpRequest对象进行一个调用。
  4. ValidateServlet对请求进行处理。
  5. ValidateServlet返回一个包含了结果的XML文档。
  6. XMLHttpRequest对象调用callback()函数并处理结果。
  7. 更新 HTML DOM。

现在让我们逐个研究这个AJAX模型的每一步。

1.发生一个客户端事件。

在一个事件发生时可以调用相应的JavaScript函数。在这里,validate()函数可以被映射到一个链接或者是表单组件的onkeyup事件上去。

<input type="text"
size="20"
id="userid"
name="id"
onkeyup="validate();">

每次用户在表单域中按下一个键时,表单元素将都调用validate()函数。 

2. 建立和配置一个XMLHttpRequest对象

创建和配置一个XMLHttpRequest对象

var req;

function validate() {
var idField = document.getElementById("idField");
var url = "validate?id=" + escape(idField.value);
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
} else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("GET", url, true);
req.onreadystatechange = callback;
req.send(null);
}

validate()函数建立了一个XMLHttpRequest对象并对象中的open函数。open函数需要两个参数:HTTP方法,可以是GETPOST; 和对象进行交互的服务器端组件的URL;一个布尔变量,表示是否要进行异步调用。API是XMLHttpRequest.open(String method, String URL, boolean asynchronous)。如果一个交互被设置为异步, (true) 那就必须指明一个回调函数。可以使用req.onreadystatechange = callback;来设置这个交互的回调函数。详细内容见第六节。

3.XMLHttpRequest对象进行调用

当收到了语句req.send(null);,就会进行一次调用。HTTPGET的情况下,内容可以是null或者留空。当调用XMLHttpRequest的这个函数时,也会对已经配置了的URL进行调用。在下面这个例子中,要发送的数据(id)将作为一个URL参数。

使用HTTPGET,两个重复的请求将返回同样的结果。当使用HTTPGET方法时,要注意URL的长度,包括已经转义的URL参数,可能会受到某些浏览器和服务器端的Web容器的限制。当发送的数据会影响到服务器端的应用程序的状态时,就应该使用HTTPPOST方法。使用HTTPPOST必须要对XMLHttpRequest对象设置一个Content-Type头,使用以下语句:

req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("id=" + escape(idTextField.value));

当从JavaScript中发送表单值得时候,你应该考虑对字段值进行编码。JavaScript中有一个函数escape(),应该用他来确保区域化的内容被正确编码,同时特殊字符也被正确转义。

4. ValidateServlet对请求进行处理.

一个映射到URI “validate” 的servlet将检验user ID是不是已经在数据库中存在了。

一个servlet处理一个XMLHttpRequest ,就像对待其它的HTTP请求一样。下面的例子显示了服务器从请求中抽取出id参数并检验是否被占用了。

public class ValidateServlet extends HttpServlet {

private ServletContext context;
private HashMap users = new HashMap();

public void init(ServletConfig config) throws ServletException {
this.context = config.getServletContext();
users.put("greg","account data");
users.put("duke","account data");
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {

String targetId = request.getParameter("id");

if ((targetId != null) && !users.containsKey(targetId.trim())) {
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>valid</message>");
} else {
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>invalid</message>");
}
}
}

在这个例子中,一个简单的HashMap用来存放存在的用户名。在这个例子中,我们假设用户的ID是duke

5.ValidateServlet返回一个包含结果的XML文档

用户ID “duke” 在users HashMap的用户ID列表中出现了。将在应答中写一个包含值为invalidmessage元素的XML文档。更复杂的用例将要求DOM、XSLT或其他API来生成这个应答。

response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>invalid</message>");

开发人员必须注意两个事情。第一,Content-Type必须设为text/xml。第二,Cache-Control必须设为no-cacheXMLHttpRequest对象只会处理Content-Typetext/xml的应答,同时把将Cache-Control设为no-cache将确保浏览器不会从缓存相同的URL(包括参数)返回的应答。

6.XMLHttpRequest对象调用callback()函数并处理结果。

XMLHttpRequest对象已经配置为当有readyState改变的时候就调用callback()函数。让我们假设已经ValidateServlet调用了而且ValidateServlet4,表示XMLHttpRequest的调用已经完成。HTTP状态代码200表示一个成功的HTTP交互。

function callback() {
if (req.readyState == 4) {
if (req.status == 200) {
// update the HTML DOM based on whether or not message is valid
}
}
}

浏览器维护了一个所显示的文档的对象形式(也就是所谓的Docuemt Object Model或DOM)。HTML页面中的JavaScript可以访问DOM,同时在页面载入完之后,可以使用API来修改DOM。

根据成功的请求,JavaScript代码可以修改HTML页面的DOM。从ValidateServlet获得的对象形式的XML文档可以通过req.responseXML在JavaScript中获得,req是一个XMLHttpRequest对象。DOM API给JavaScript提供了获取这个文档中的内容以及修改HTML页面的DOM的方法。所返回的字符串形式的XML文档可以通过req.responseText获得。现在我们看看如何在JavaScript中使用DOM API,先看以下从ValidateServlet返回的XML文档。

<message>
valid
</message>

这个例子是一个简单的只包含了一个message元素的XML片断,里面只有一个简单的字符串validinvalid。一个更高级的例子可以包含多于一个的消息和可以给用户看的有效的名字:

function parseMessage() {
var message = req.responseXML.getElementsByTagName("message")[0];
setMessage(message.childNodes[0].nodeValue);
}

parseMessages()函数将处理一个从ValidateServlet获取的XML文档。这个函数会调用setMessage()with the,并给出message作为参数来更新HTML DOM。

7.更新HTML DOM

JavaScript技术可以使用很多API从HTML DOM中获得任何元素对象的引用。推荐的获得元素引用的方法是调用document.getElementById("userIdMessage")"userIdMessage"是HTML文档中出现的一个元素的ID属性。有了这个元素的引用,就可以使用JavaScript来修改元素的属性、修改元素的样式、添加、删除或修改子元素。

一个常见的改变元素主体内容的方法是设置元素的innerHTML属性,如下所示:

<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
userMessageElement.innerHTML = "<font color=\"red\">" + message + " </font>";
}
</script>
<body>
<div id="userIdMessage"></div>
</body>

受到影响的那部分HTML页面会立刻根据innerHTML的设置重新渲染。如果innerHTML属性包含类似<image>或者是<iframe>之类的元素,那么由那些元素所指定的内容同样会被获取并渲染。

这种途径的主要缺点是HTML元素是作为字符串硬编码在JavaScript中的。JavaScript中硬编码的HTML标记不是一种好的实践,因为它使代码难于阅读、维护和修改。我们应该考虑在JavaScript中使用DOM API来创建和修改HTML元素。把显示和JavaScript代码的字符串混在一起只会让页面更难于阅读和编辑。

另一种修改HTML DOM的方法是动态地产生新的元素并把他们作为子元素追加到目标元素,如下面的例子所示:

<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
var userIdMessageFont = document.getElementById("userIdMessageFont");
var messageElement = document.createTextNode(message);

if (userMessageElement.childNodes[0]) {
// 更新元素
userIdMessageFont.replaceChild(messageElement, userIdMessageFont.childNodes[0]);
} else {
// 建立一个新的元素
var fontElement = document.createTextNode("font");
fontElement.setAtribute("id", "userIdMessageFont");
fontElement.setAtribute("color", "red");
userMessageElement.appendChild(fontElement);
fontElement.appendChild(messageElement);
}
}
</script>
<body>
<div id="userIdMessage"></div>
</body>

这个范例展示了JavaScript技术的DOM API可以用来更有目的地建立或改变一个元素。当然JavaScript的DOM AP在不同的浏览器上也可能有差别,所以你必须在开发应用程序时小心。

Java BluePrint的解决方案目录

TheJava Blueprints Solutions Catalog是用来收集J2EE技术上AJAX的最佳实践的。每个解决方案包含一个问题和方法的描述、一个设计文档和可运行的源码。这些解决方案是为了让你根据需要在自己的应用程序中复用。以下是已经提供的AJAX交互:

自动补全

自动补全提供了当用户在一个HTML表单中输入一个请求时对数据浏览的简化方式。当用户面对一大片数据时,可以在输入数据时把可能的完整形式显示给用户。然后选择其中一个完整形式可以保证用户输入的数据已经存在在服务器上。

考虑一个大公司的一个名字查找的Web应用。如图2所示,只要输入姓或名的开头几个字母就可以得到人的列表。用户可以然后就只要点击一下就可以浏览用户的详细信息。

Autocompletion of a Name
图2:名字自动补全

进度条

在Web应用中,一个服务器端任务也可能要花一段时间去完成。这段时间很可能会超过HTTP交互的时间上限(超时)。当用户不知道这个任务什么时候才能完成时,用户很可能会重新提交一次表单或直接退出会话状态。一般来说,Web应用使用页面刷新来跟踪服务器端操作的状态,这种方式可能会让人厌烦而且也不准确。AJAX可以用来仅在一个HTML页面中跟踪服务器端操作的状态而无需刷新页面。用户可以以图形方式看到服务器端操作的进度,如图3。

Progress Bar
图3:进度条 

刷新数据

向一个HTML页面提供最新的数据或服务器消息提醒在现在的Web世界中也是十分重要的,因为现在的Web世界中数据一直不停变化。尽管它不是一个实实在在的推送技术,但它可以通过使用AJAX交互不断进行查询来模拟。当数据需要更新或者要进行提醒,HTML页面将会动态地改变。图4显示了HTML页面中的一个服务器端计数器。这个计数器会在页面后台自动更新。

Server-side Counter Shows Refreshing Data
图4:服务器端计数器在刷新数据

实时检验

不是所有的表单域都可以单独用JavaScript技术在客户端完成。某些表单数据要求服务器端的验证逻辑。传统和Web应用曾使用页面刷新来完成这种验证,但这可能有些让人烦。

考虑一个需要一个唯一用户ID的Web应用。使用AJAX交互,用户可以在输入的时候就知道ID是否有效(图5)。

Invalidating the ID as User Types
图5:指出用户ID无效

当一个用户输入了一个无效的用户ID,应用程序禁止了提交按钮并且向用户显示了一个信息(图6)。

Validating the ID as User Types
图6:用户ID通过验证

用户马上就能知道用户ID是可用的也是有效的。

最后的思考

我们已经看到AJAX交互可以解决很多问题。配合HTTP处理、数据库、Web服务、XML处理和业务对象等API,J2EE技术已经提供了一个开发和部属基于AJAX应用的一个良好的基础。有了对于这个交互模型的更好的理解,今天的应用程序可以变得更加有交互性,给最终用户更好的体验。

使用AJAX要求你使用支持XMLHttpRequest对象的最新浏览器版本。使用AJAX还要求大量对JavaScript技术和CSS的应用。作为一个应用程序架构师或是一个开发人员,你要会针对浏览器支持、架构复杂度和对开发人员的培训等方面来衡量开发一个富应用的需要。当AJAX编程模型不断地发展,现有的技术和框架会让这种转变更加容易。

很明显的是,突出的Web应用都越来越有交互性了。那么你的呢?

更多信息

关于作者


Greg Murray 是is a Sun Microsystems 的一名工程师,是servlet标准的领导人,BluePrint小组的前成员,在这个小组时他已经开始关注Web层次问题。他也是《Enterprise Applications With the Java 2 Platform,Enterprise EditionDesigning Web Services With the J2EE 1.4 Platform(Addison-Wesley)》一书的协助编撰者。

嵌入JavaScript引擎梗概教程

嵌入JavaScript引擎

梗概教程

作者:Brendan Eich

2000年2月21日

翻译:ShiningRay @ NirvanaStudio

如何启动VM并执行一个脚本

如果不使用任何错误检查这样:

JS_起头的返回指针的函数会返回空(null)

JS_起头的返回布尔值的函数会返回假(false)
(错误照例会被保存在一个JSBool变量ok中)。

如何从JavaScript中调用C函数

假设有个C函数叫diot,他在被调用时需要至少两个实参(如果调用者少提供了几个,JS引擎需要保证undefined值传给了那些缺少的参数):

然后把它和JS连接起来,你要写:

或者,如果你有一堆的本地函数要调用,你可以把它们放在一个表格中:

(最后,全为0的函数表示表格结束)并且用:

ok = JS_DefineFunctions(cx, global, my_functions);

如何从C中调用JavaScript函数(像“onClick”)

假设点击事件是发生在最顶层或者是有焦点的UI元素,位置为(x,y):

再次声明,我省略了错误检查(例如在调用之后检查!ok),同时我伪造了一些C的事件管理程序来模拟DOM的协议——如果他的处理程序返回假就取消这个事件。


原文地址:http://www.mozilla.org/js/spidermonkey/tutorial.html

JavaScript-C引擎嵌入开发指南

JavaScript-C引擎嵌入开发指南

翻译:ShiningRay@Nirvana Studio原文地址:http://www.mozilla.org/js/spidermonkey/apidoc/jsguide.html


JavaScript-C引擎概览

本文档提供了一个JavaScript(JS)引擎的C语言实现的概述,他介绍了你如何在你的应用程序中嵌入脚本引擎来让它们可以使用JS。有两大理由让你在应用程序中嵌入JS引擎:使用脚本来自动操作你的应用程序;同时使用JS引擎和脚本无论何时都可以提供跨平台的功能并消除了应用程序解决方案对平台的依赖性。

受支持的JavaScript版本

本JS引擎支持从JS 1.0版到JS1.4。JS 1.3和更高版本符合ECMAScript-262规范。JS引擎解析、编译和执行包含JS语句和函数的脚本。这个引擎可以处理要用来执行脚本的JS数据类型和对象内存分配,同时它可以清除——垃圾回收——内存中已经不需要的数据类型和对象。

你如何使用这个引擎?

通常,你将JS引擎作为一个共享的资源进行构建。例如,在Windows和Windows NT上,这个引擎是一个DLL文件,在Unix上是一个共享库。然后你把你的应用程序和他连接,同时嵌入式JS引擎应用程序编程接口(API)就可以在你的应用程序中调用了。JS引擎的API提供了以下几种分类的函数:

  • 数据类型操作

  • 运行时控制

  • 类和对象创生的维护

  • 函数和脚本执行

  • 字符串处理

  • 错误处理

  • 安全性控制

  • 调试支持

你在每个嵌入了JS调用的应用程序中将会用到这些功能分类中的某些部分,象运行时控制和数据类型操作。例如,在你调用其他JS功能之前,你必须通过调用JS_NewRuntime函数来新建和初始化JS引擎。其他功能分类,像安全控制,提供一些可选的特性,你可以根据需要在你的应用程序中使用它们。

这个引擎和应用程序有什么关系?

从概念上来讲,JS引擎在你的系统上是一个共享资源。通过在你的应用程序中嵌入引擎API命令你可以向JS引擎传递处理的请求。这个引擎,反过来,处理你的请求,并把返回值或者状态信息返回给你的应用程序。图1.1描述了它们一般的关系:

图 1.1

图1.1

例如,假设你正在使用JS引擎来使你的应用程序能通过JS脚本自动运行,同时假设你的应用程序运行一个脚本来对一个用户进行身份验证并且设置一个用户对这个应用程序的访问权限。首先,你的应用程序可能新建一个代表用户的自定义JS对象,包括了用户的名字、ID、访问权限和一个可能的用户拥有权限在应用程序中使用的函数的列表。

在这个情况下,你的应用程序给JS引擎发送的的第一个请求可能是对JS_NewObject的调用来新建一个自定义对象。当JS引擎新建了这个对象,他返回一个指针给你的应用程序。你的应用程序可以再次调用JS引擎来执行使用这个对象的脚本。例如,在建立了用户对象之后,你的应用程序会立刻给JS_EvaluateScript传递一个脚本来立刻编译执行。那个脚本可以获得并验证用户信息,然后建立用户对其他应用程序特性的访问权限。

事实上,你的应用程序和JS引擎之间的关系远比图1.1中显示的要复杂的多。例如,它假设你已经为你的平台构建了JS引擎。它还假设你的应用程序包含了jsapi.h还假设应用程序对引擎进行的第一个调用已经初始化了JS运行时。

当JS引擎接受到了一个初始化的请求时,他会为JS运行时分配内存。图1.2描述了这个过程:

图 1.2

图 1.2

这个运行时是一个内存空间,在其中可以维护你的应用程序所使用的变量、对象和上下文。一个上下文是指,针对JS引擎所使用的线程的脚本执行状态。每个同时存在的脚本或者线程都必须有它自己的上下文。一个单独的JS运行时可以包含很多上下文、对象和变量。

几乎所有的JS引擎调用都要求有一个上下文的参数,所以在创建了运行时之后你的应用程序首先要做的一件事情是调用JS_NewContext来至少创建一个上下文。实际你需要的上下文数量由你的应用程序中所期望同时运行的脚本的数量决定。从另一方面说,如果同一时间只有一个脚本被编译执行,那么你就知需要建立单独的一个上下文,你可以对每个脚本重复使用它。

在你新建了上下文之后,你会通常想要初始化引擎内置的JS对象,可以通过调用JS_InitStandardClasses实现。内置的对象有Array,Boolean,Date,Math,Number,和String字符串对象,大多数脚本都会用到。

大多数应用程序也要用到自定义JS对象。这些对象是特定于你的应用程序的。他们通常代表了数据结构和应用程序中脚本使用的方法。要新建一个自定义对象,你要组装一个JS类来生成这个对象,调用JS_InitClass来在运行时设立这个类,然后调用JS_NewObject来在引擎中新建你这个自定义对象的实例。最后,如果你的对象有一些属性,你也许要通过调用JS_SetProperty来设置他们的默认值。

即使你在创建一个对象的时候给JS引擎传递了一个特定的上下文,最后这个对象还是独立于这个上下文存在的。任何脚本都可以和任意上下文相关联来访问任何对象。图1.3描述了脚本和运行时、上下文以及对象之间的关系。

图 1.3

图1.3

如图1.3所示,脚本和上下文完全是互相独立存在的及时他们可以访问相同的对象。在给定的运行时中,一个应用程序可以任意未分配的上下文来访问任何对象。也可能有时你想确保能为独占的使用而保留某些上下文和对象。在这些情况下,给你的应用程序新建单独的运行时:一个针对共享上下文和对象,另一个(或者更多的,取决于你的应用程序的需求)针对私有的运行时和对象。

注意:同一时间只能有一个线程被授权访问特定上下文。

构建引擎

在你可以在你的应用程序中使用JS之前,你必须将JS引擎构建成一个可共享的库。在大多数情况下,引擎代码已经包括了Make文件来自动构建。

例如,在Unix下,js源代码目录包括了一个基本的Gnu Make文件——Makefile.ref和一个config目录。config目录包括了平台特定的.mk文件来配合Makefile.ref对你的环境进行构建。在Windows NT下,NMake文件是js.mak

请阅读源代码目录中任何的readme文件,也许其中包括了和更新的编译指导或者其他信息。

嵌入引擎有什么必要条件?

如果要让你的应用程序可以执行JS,就要在你的应用程序代码中嵌入合适的引擎。嵌入一般有五步:

  1. 在你的C模块中加入#include jsapi.h来确保编译器知道有哪些引擎的API可以调用。极少部分特殊的JS引擎工作时会要求你包含额外的头文件。例如,要在你的应用程序中调用JS调试器,你要在合适的模块里面包含jsdbgapi.h

    大部分在JS源代码中的其它的头文件应该被引用。这样做可能会使你的程序依赖于引擎内部的接口,而这些接口可能随着版本发布而更改。

  1. 在你的应用程序中提供支持结构和变量声明。例如,如果你打算给JS引擎传递一个脚本呢,提供一个字符串变量保存了你的应用程序的脚本的版本的文字信息。使用jsapi.h中定义的JS数据类型来声明结构和变量。

  2. 使用JavaScript编写特定应用的对象。这些对象常常会与操作在你C程序中的结构的结构和方法进行通讯,特别是如果你在使用JS引擎来自动操作你的应用程序。

  3. 在程序代码中嵌入合适的JS引擎API调用和变量引用,包括初始化内置JS对象,和创建组成任何应用程序要用的自定义对象。

  4. 大多数JS引擎调用都会返回一个值。如果这个值是零或者空,它通常表示一个错误的情况发生了。如果值非零,它一般表示成功;在这些情况下,返回的值常常会是你的程序需要使用的指针,或者存起来留以后引用。很重要的是,你的程序至少应该每次检查JS调用返回的值。

以下代码片断描述了嵌入使用的大部分过程,除了JS脚本的建立,这点也不在本文的介绍范围之内。如要查询有关创建脚本的信息——JavaScript这个语言——请看客户端JavaScript指导,如果要得到关于编写服务器端对象,见服务器端JavaScript指导

.
.
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 包含JS引擎API头文件 */
#include "jsapi.h"
.
.
.

/* 主程序建立全局JS变量,包括运行时,
* 一个上下文,和一个全局对象,然后初始化JS运行时,
* 并创建一个上下文. */

int main(int argc, char **argv)
{
int c, i;
/*建立全局的JS变量,包括全局和自定义对象 */

JSVersion version;
JSRuntime *rt;
JSContext *cx;
JSObject *glob, *it;
JSBool builtins;

/* 初始化JS运行时,并返回结果给rt */
rt = JS_NewRuntime(8L * 1024L * 1024L);

/* 如果rt没有值,结束程序 */
if (!rt)
return 1;

/* 新建一个上下文并且把它和JS运行时相关联 */
cx = JS_NewContext(rt, 8192);

/* 如果cx没有值,在此结束程序 */
if (cx == NULL)
return 1;

/* 新建全局对象 */
glob = JS_NewObject(cx, clasp, NULL, NULL);

/* 初始化内置JS对象和全局对象 */
builtins = JS_InitStandardClasses(cx, glob);

.
.
.

return 0;

}

这个范例代码十分简单,它描述了嵌入JS引擎调用所必须的关键元素。如果想要更完整的例子——也就是以上这段代码片断的出处——参见js.c,这个范例应用的源代码是包含在JS引擎的源代码中的。

理解关键嵌入开发概念

大多数你要创建的JavaScript应用,你都会想要遵循一些权威的的JS API嵌入实践。以下部分讲述了任何程序中都要使用到的API调用。

在很多情况下,嵌入某些特定API调用的顺序决定这个程序的成功。例如,你必须在调用其它JS引擎函数之前初始化一个JS运行时。相应的,你要在关闭程序之前释放JS运行时。因此,典型的程序的main函数像一种三明治,在任何你提供的功能前后先初始化最后释放JS运行时:

int main(int argc, char **argv)
{
int c, i;

/*建立全局JS变量,包括全局对象global和自定义对象 */
JSVersion version;
JSRuntime *rt;
JSContext *cx;
JSObject *glob, *it;

.
.
.

/* 初始化JS运行时,并把返回的结果放在rt中 */
rt = JS_NewRuntime(8L * 1024L * 1024L);

/* 如果rt没有值,程序结束。 */
if (!rt)
return 1;

.
.
.

/* 建立一个上下文 */
cx = JS_NewContext(rt, 8192);

/* 如果cx值为空,则结束程序 */
if (cx == NULL)
return 1;

/* 初始化内置JS对象和全局对象 */
builtins = JS_InitStandardClasses(cx, glob);

.
.
.

/* 把你的代码扔这里,包括你要用来创建自定义JS对象的JS API函数调用。
* JS对象模型在这里开始。 */

.
.
.

/* 在退出应用程序之前,释放JS运行时 */
JS_DestroyRuntime(rt);

正如这个例子所描述的,嵌入了JS引擎的函数的应用程序要负责建立JS运行时,这是他最先要完成的动作之一,同时它还要负责在退出之前释放运行时。一般来说,确保运行时被初始化和释放的最佳位置是在中央JS调度程序的模块中嵌入必要的调用,无论你将使用哪一个模块作为在应用程序的中央调度模块。

在你初始化了运行时之后,你可以建立应用程序的JS对象模型。这个对象模型决定了你的JS对象互相之间的关系。JS对象本质上是分层次的。所有的JS对象都是默认与全局(global)对象相关的,他们都是全局对象的子孙。当你初始化标准JS类的时候,你会自动获得一个全局对象:

builtins = JS_InitStandardClasses(cx, glob);

全局对象会建立一些基本属性和方法,其他对象都会继承这些属性和方法。当你创建你自己的对象时,他们可以自动使用这些已经定义在全局对象上的属性和方法。你可以通过在自定义对象上重新对他们进行定义来覆盖这些默认属性和方法,否则可以直接接受默认的赋值。

你也可以基于其他的内置JS对象新建自定义对象,或者基于其他自定义对象来新建对象。无论哪种情况,你新建的对象在继承链中将继承他祖先的所有属性和方法,一直追溯到全局对象。如果要了解更多关于全局和自定义对象地内容,请参见“初始化内置和全局JS对象”以及“创建和初始化自定义对象”。

管理一个运行时

JS运行时是一块内存空间,在这里面JS引擎可以管理与JS函数和脚本相关的上下文、对象和变量。在执行任何JS函数或者是脚本之前,你必须初始化一个运行时。初始化运行时的API调用是JS_NewRuntimeJS_NewRuntime有一个参数,是一个无符号整数,它指明了在垃圾收集发生之前,分配给运行时的内存最大数值,单位是字节。例如:

rt = JS_NewRuntime(8L * 1024L * 1024L);

如上面列举的,JS_NewRuntime也会返回一个值,这个值是一个指向新建的运行时的指针。一个非空的返回值表示运行时被成功创建了。

一般来说,一个应用程序只需要一个运行时。但是,你还是可以创建多个运行时的,我们可以在必要的时候调用JS_NewRuntime并把返回值存在不同的指针中。

当不再需要JS运行时的时候,应该把它销毁来释放他占用的内存资源,以便给其他应用程序来使用。根据你的应用程序中JS的使用范围,你可以选择在JS使用结束立刻销毁运行时,或者,你可以选择一直保留运行时知道你的应用程序即将结束。无论哪种情况,都必须使用JS_DestroyRuntime来释放运行时,当运行时不再需要的时候:

JS_DestroyRuntime(rt);

如果你使用了多个运行时,要确保在结束应用程序前,每一个都被正确释放了。

管理上下文

几乎所有的JS API调用都要求你传送一个上下文作为参数。在JavaScript引擎中,一个上下文唯一对应一个脚本。引擎把上下文信息传送给运行脚本的那个线程。每个同步执行的脚本必须被分配一个唯一的上下文。当一个脚本执行完之后,他的上下文就不再被使用了,这时候这个上下文就可以再次被分配给一个新的脚本,或者可以释放他。

要为一个脚本创建新的上下文,可以使用JS_NewContext函数。该函数有两个参数:一个指针指向上下文所需结合的运行时,和为上下文分配的栈空间的大小,以字节为单位。如果成功,函数返回新建的上下文的指针。例如:

JSContext *cx;
.
.
.
cx = JS_NewContext(rt, 8192);

运行时必须已经存在。你为上下文指定的栈的大小应该足够容纳使用这个上下文的脚本所创建的任何变量和对象。注意和分配和维护上下文相关有一个特定的数量,因为你可能要:

  1. 只创建同一时刻在你的应用程序中所需要的数量一样多的上下文。

  2. 只要上下文有可能被应用程序用到,就保留他们,而不是每当需要的时候再重新新建不需要了就立刻销毁。

当不再需要某一个上下文时,应该把它销毁来释放它占用的内存资源留给其他的应用使用。根据你的应用程序中的JS使用范围,你可以选择在使用完上下文之后,就立刻销毁,或者,更多情况下,你可以考虑为以后重复使用来保留上下文直到应用程序结束为止。不管哪种情况,当他不再需要用到的时候,可以使用JS_DestroyContext来释放上下文。这个函数带一个参数,也就是指向要销毁的上下文的指针:

JS_DestroyContext(cx);

如果你的应用创建了多个运行时的话,应用程序需要了解上下文和哪个运行时相关联。在这种情况下,可以调用JS_GetRuntime,并且把上下文作为参数传递给他。JS_GetRuntime会返回一个指向某个合适的运行时的指针,如果存在的话:

rt = JS_GetRuntime(cx);

当你创建一个上下文的时候,你要给他分配栈空间,这个空间将为那些被使用这个上下文的脚本所创建的变量和对象所使用。你也可以用给定的上下文仅仅用来储存大量数据,只要分配所需的最小的栈空间。调用JS_SetContextPrivate函数来建立一个指向上下文使用的私有数据的指针,并调用JS_GetContextPrivate函数来获取这个指针,这样就可以访问这些数据了。你的应用程序要负责创建和管理这个可选的私有数据。

若要创建私有数据并把它和一个上下文关联:

  1. 根据需要建立私有数据,可以使用一个普通的 C void 指针变量。

  2. 调用JS_SetContextPrivate,并指明通过哪个上下文来建立私有数据,并给出指向数据的指针。

例如:

如果要在以后获取数据,调用JS_GetContextPrivate函数,并把上下文作为参数传递给他。该函数会返回指向私有数据的指针:

初始化内置的和全局的JS对象

JavaScript引擎提供了一些内置对象,他们会简化你的某些开发任务。例如,内置的Array对象让你更方便地在JS引擎中创建和处理数组结构。类似,Date对象提供了一个统一的处理日期数据的机制。要查阅引擎支持的内置对象的完整列表,请参看JS_InitStandardClasses

JS引擎始终使用函数和全局对象。一般来说,全局对象存在于JS脚本的场景背后,为所有其它JS对象提供了一个默认的空间范围和存储了你在程序中创建和使用的全局变量。在你创建你自己的对象之前,你需要初始化全局对象。函数对象将启用对象支持和构造器调用。

JS_InitStandardClasses, 这个API调用将初始化全局和函数对象还有引擎内置的对象,这样你的应用程序就可以使用他们了:

JS_InitStandardClasses会返回一个JS boolean值来表示初始化是否成功。

你可以为你的应用程序指定一个不同的全局对象。例如,Netscape Navigator就使用了他自己的全局对象window。若要为你的应用程序更改全局对象,可以调用JS_SetGlobalObject函数。详细信息请查阅JS_SetGlobalObject的参考条目。

创建和初始化自定义对象

除了可以使用引擎内置对象之外,你还可以新建、初始化和使用你自己的JS对象。如果你使用JS引擎处理脚本对你的应用进行自动化操作,这点尤其重要。自定义JS对象可以提供最直接的程序服务,另外他们也可以作为你的程序服务的一个接口。例如,一个自定义JS对象提供了某种直接的服务,像处理应用程序所有的网络访问、作为数据服务的中间层。也可以是使用一个JS对象映射到应用程序中以后的数据和函数中,这样能为C代码提供一个面向对象的接口。这样一个自定义对象对应用程序自身来说扮演了一个接口的角色——从应用程序中把值传递给用户,并且接受和处理用户的输入然后再返回给应用程序。这种对象也可以用来对应用程序内部的函数进行访问控制。

有两种方法可以创建自定义对象:

  • 写一个JS脚本,用来创建对象,以及他的属性、方法和构造器,然后把这个脚本在运行时传递给JS引擎。

  • 在你的程序中嵌入定义对象的属性和方法的代码,调用引擎来初始化新对象,然后通过其它的引擎调用来设置对象的属性。这个方法的一个好处是你的程序可以包含直接处理所嵌对象的本地方法。

无论哪种情况,如果你创建了一个对象然后要将他在运行时中持久化,以便在此运行时中可以被其他对象调用,那么你必须通过JS_AddRoot JS_AddNamedRoot调用来确定这个对象的“根”。使用这两个函数会确保JS引擎去跟踪这些对象并在适当的时候通过垃圾收集过程中清理掉他们。

从脚本中建立一个对象

要从脚本中创建自定义JS对象的一个原因是,只需要一个在脚本运行期间存在对象。要创建这种持续在脚本调用期间的对象的话,你也可以直接在你的应用程序中嵌入对象的代码,而不用使用一个脚本。

注意:你同样可以使用脚本创建持久对象。

要使用脚本创建一个自定义对象:

  1. 定义和说明对象。他的目的是什么?他的数据成员(属性)有哪些?他有哪些方法(函数)?他是否需要一个运行时构造函数?

  2. 编写出定义和创建对象的JS脚本。例如:

    function myfun(){
    var x = newObject();
    .
    .
    .
    }

    注意:使用JavaScript编写的对象并不在应用程序嵌入JS引擎的代码中。关于对象编写的更多内容,请参阅《客户端JavaScript指导》和《服务器端JavaScript指导》。

    在应用程序中嵌入合适的JS引擎调用来编译和执行脚本。你有两种选择:1.) 仅使用一个函数调用来编译和执行脚本:JS_EvaluateScript,JS_EvaluateUCScript或者2.) 使用JS_CompileScript或者JS_CompileUCScript,来一次性编译脚本,然后可以用一个独立的函数调用JS_ExecuteScript. 来重复执行已经编译的代码。这些调用的“UC”版可以提供对统一码脚本的支持。

你使用脚本创建的一个对象只可以在脚本的生命周期内启用,或者也可以在脚本运行结束之后持久化。一般来说,一旦脚本运行结束,他的所有对象都会被销毁。在大部分情况下,这种行为正是你的应用程序需要的。然而,在其他的情况下,你可能希望某对象持续在脚本之间,或者你的应用程序的整个生命周期。这样的话你需要直接在你的应用程序中嵌入对象创建代码,或者你必须把对象直接连接到全局对象这样他会一直持续只要全局对象本身存在。

在应用程序中嵌入一个自定义对象

当必须进行对象持久化时,或者你认为需要对几个脚本都可用的对象时,嵌入一个自定义JS对象在应用程序中是很有用的。例如,一个代表了用户的ID和访问权限的自定义对象可能会在应用程序的整个生命期中都会用到。他事先一次性创建和组装了对象,节省了很多时间,而不用每次要检验用户ID或者权限时一遍又一遍用脚本创建对象。

一种在应用程序中嵌入自定义对象的方法是:

  1. 创建一个JSPropertySpec数据类型,并把它和属性的信息组装成对象的属性,包括参数的获取(get)和设置(set)方法的名称。

  2. 创建一个JSFunctionSpec数据类型,并把它和方法的信息组装成对象使用的方法。

  3. 创建一个实际的C函数用来处理对象的方法调用。

  4. 调用JS_NewObject或者JS_ConstructObject来实例化这个对象。

  5. 调用JS_DefineFunctions来创建这个对象的方法。

  6. 调用JS_DefineProperties来创建这个对象的属性。

描述持久的自定义JS对象的代码必须放在应用程序执行的开始部分附近,在任何依赖于该对象的代码之前。嵌入的实例化和组装自定义对象的引擎调用也应该出现在任何依赖这个对象的代码之前。

注意:在大多数情况下还有一个更方便的在程序代码中创建自定义对象的方法是调用JS_DefineObject来创建对象,然后反复调用JS_SetProperty来设置对象的属性。关于定义一个对象的更多的信息,参见JS_DefineObject。关于设置对象属性的更多信息,参见JS_SetProperty

为对象提供私有数据

像上下文那样,你可以把大量的数据和一个对象相关联而无需把数据存储在这个对象中。调用JS_SetPrivate来建立一个指向私有数据的指针,并且调用JS_GetPrivate来获得这个指针这样就可以访问数据了。你的应用程序要对这些可选的私有数据的创建和管理负责。

要创建私有数据并把它和一个对象相关联的话:

  1. 根据需要建立私有数据,可以使用一个普通的 C void 指针变量。

  2. 调用JS_SetPrivate, 制定要为那个对象建立私有数据,并给出指向数据的指针。

例如:

如果要以后再获取数据,调用JS_GetPrivate并且把对象作为一个参数传递。这个函数将返回一个指向对象私有数据的指针:

处理统一码(Unicode)

JS引擎现在提供了很多API函数的支持统一码的版本。这些函数允许你直接给引擎传递使用统一码编码的脚本进行编译和运行。下面的表格列出了标准引擎函数和他们对应的统一码版本:

标准函数

统一码支持函数

JS_DefineProperty

JS_DefineUCProperty

JS_DefinePropertyWithTinyId

JS_DefineUCPropertyWithTinyId

JS_LookupProperty

JS_LookupUCProperty

JS_GetProperty

JS_GetUCProperty

JS_SetProperty

JS_SetUCProperty

JS_DeleteProperty2

JS_DeleteUCProperty2

JS_CompileScript

JS_CompileUCScript

JS_CompileScriptForPrincipals

JS_CompileUCScriptForPrincipals

JS_CompileFunction

JS_CompileUCFunction

JS_CompileFunctionForPrincipals

JS_CompileUCFunctionForPrincipals

JS_EvaluateScript

JS_EvaluateUCScript

JS_EvaluateScriptForPrincipals

JS_EvaluateUCScriptForPrincipals

JS_NewString

JS_NewUCString

JS_NewStringCopyN

JS_NewUCStringCopyN

JS_NewStringCopyZ

JS_NewUCStringCopyZ

JS_InternString

JS_InternUCString

JS_InternUCStringN

处理统一码的函数工作方式与原来的同名函数一样,除了原来的函数使用参数char *,而统一码版本的函数参数为jschar *

操作JS数据类型

JavaScript定义了他自己的数据类型。其中一部分直接对应C中的副本。其他的,诸如JSObject,jsdouble, 和 JSString,对 JavaScript有特殊意义。

一般而言,你在应用程序中声明和使用JS数据类型就和使用标准C数据类型一样。然而,JS引擎对JS数据类型,也就是需要超过一个字空间的变量变量JSObject,jsdouble, 和JSString有不同的跟踪。引擎周期性地检查这些变量,察看他们是否还在使用中。如果不再使用了,就收集他们,释放存储空间来重新使用。

垃圾收集可以有效重复利用堆的资源,但是过分频繁的垃圾收集也会对性能造成影响。你可以根据JS运行时控制垃圾收集的频率,根据你给程序分配的JS运行时的大小和你应用程序使用的JS变量和对象的数量之间的关系。如果你的程序要创建和使用很多JS对象和变量,你可能就要分配足够大的运行时来减少垃圾收集的可能频率。

注意你的应用程序要在任何时候调用同样能JS_GC或者JS_MaybeGC来强制进行垃圾收集。JS_GC将强制进行垃圾收集。JS_MaybeGC则会根据条件进行垃圾收集,如果你调用这个函数时,初始化时分配的空间的特定比例已经被使用的话,就进行垃圾收集。

操作JS值

除了JS数据类型之外,JS引擎也使用JS值,称之为jsval。一个jsval本质上是一个指向任意JS数据类型(除了整型)的一个指针。对于整型,jsval直接包含了他自身的整数值。在其他的情况下,指针还会被编码,添加关于它所指的数据的类型的额外信息。使用可以提高引擎的效率,同时也可以让很多API函数来处理不同类型的数据。

引擎API包含了一组用来测试JS值中的JS数据类型的宏。有:

Besides testing ajsval,你也可以检测他是否属于一个基本JS数据类型 (JSVAL_IS_PRIMITIVE)。基本类型包括未定义(undefined)、空(null)、 布尔(boolean)、数值(numeric)和字符串(string)类型。

你可以测试jsval所指的值是否为NULL(JSVAL_IS_NULL) 或者void(JSVAL_IS_VOID)。

如果jsval指向了一个JS数据类型是JSObject,jsdouble, 或者jsstr,你可以将jsval转换成他的内在的类型,只要相应使用JSVAL_TO_OBJECT,JSVAL_TO_DOUBLEJSVAL_TO_STRING。在某些情况下,你的应用程序或者JS引擎调用要求使用一个特定的数据类型的变量或者参数而非一个jsval时,就很有用了。类似地,你可以使用OBJECT_TO_JSVAL,DOUBLE_TO_JSVAL, 和STRING_TO_JSVAL, 把JSObject,jsdouble, 和jsstr相应地转换成jsval

操作JS字符串

在JavaScript中你的很多工作都回涉及字符串。JS引擎实现了一个JS字符串类型,JSString,一个指向JS字符—jschar—数组的指针,用来处理支持统一码的字符串。引擎也实现了一系列丰富的通用和统一码字符串管理程序。最后,JS引擎提供了对限定字符串的支持,这可以将两个或多个相同的字符串创建时在内存中共享一个单独的实例。对于JSString类型的字符串,引擎会跟踪和管理字符串资源。

通常情况下,当你在处理JS引擎使用的字符串时,你应该使用JS API中的字符串处理函数来创建和复制字符串。还有创建以空字符结尾的和特定长度的字符串的例程,以及获取字符串长度和比较字符串。

统一码字符串支持

使用统一码(Unicode)的API字符串函数的名称和标准的引擎API字符串行数是一一对应的,规则如下:如果标准函数名是JS_NewStringCopyN,相应的统一码版本就是JS_NewUCStringCopyN。同样有针对限定字符串的支持统一码的API字符串函数。

限定字符串支持

为了节省存储空间,JS引擎提供了对共享一个单独的字符串实例支持,这些字符串属于一些独立的不可变化的文字。这种被共享的字符串被称为“限定字符串”(interned string)。当你觉得某个特定的文本会被创建并且反复在程序中使用多次的话,那可以使用限定字符串。

引擎的API提供了几种工作于限定字符串的函数调用:

管理安全性

现在使用JavaScript 1.3,JS引擎加入了安全性增强API函数来编译和执行传送给引擎的脚本和函数。JS安全模型是基于Java的基本安全模型的。该模型提供了一个公共安全接口,但是实际的安全控制由你去实现。

在使用JavaScript的应用中使用安全管理的一个常用的方法是比较脚本的来源和限制脚本的交互。例如,你可能会比较两个或多个脚本的代码源并且只允许来自相同的代码源的脚本修改共享代码源的脚本的属性。

如要实现安全JS,请按照以下几步:

  1. 在代码中声明一个或多个JSPrincipals类型的结构体(struct)。

  2. 把实现了安全信息的函数列表添加到数组中。这些包括了为程序提供原则数组的函数,和使用给定原则的JS对象的引用计数增减机制。

  3. JSPrincipals结构和你的安全信息组装起来。这个信息可以包括一般代码源信息。

  4. 在运行时,使用一些特定的JS API调用来编译和执行所有要应用安全性的脚本和函数,他们将要求传递一个JSPrincipals结构。下面的表格列出了这些API函数和他们的作用:

函数

目的

JS_CompileScriptForPrincipals

编译(但是不执行)一个启用安全控制的脚本。

JS_CompileUCScriptForPrincipals

编译(但不执行)一个启用安全控制、统一码编码的脚本。

JS_CompileFunctionForPrincipals

从一个文本串创建一个启用安全控制的JS函数。

JS_CompileUCFunctionForPrincipals

从一个统一码编码的字符串中创建一个带安全信息的JS函数。

JS_EvaluateScriptForPrincipals

编译和执行一个启用安全控制的脚本。

JS_EvaluateUCScriptForPrincipals

编译并执行一个启用安全控制且用统一码编码的脚本。