Nirvana Studio » JavaScript :: 分享知识,传播技术

Archive for the 'JavaScript' Category

FireBug 控制台函数说明

Posted by Nicholas Ding on 20th 九月 2006

原文地址:http://www.joehewitt.com/software/firebug/docs.php

FireBug 是一个非常实用的JavaScript以及DOM查看调试工具,是 Firefox 的一个插件。使用 FireBug 调试 AJAX 应用非常方便,终于可以告别 alert 时代了!

Console Logging 函数

FireBug 为所有 Web 页面提供了一个 console 对象。这个对象有以下函数:

Logging 基础

console.log(”message” [,objects]) - 将一个字符串打印到控制台。字符串可以包含任何“String Formatting”小节描述的模式。字符串后面的对象应该用来取代之前字符串中的模式。(译者注:大家用过C里面 printf 吧,效果基本是一样的。)

Logging 等级

通常根据不同的等级来区分Logging的严重程度是很有帮助的。FireBug 提供了4个等级。为了达到视觉分离的效果,这些函数与 log 不同的地方就是它们在被调用的时候会自动包含一个指向代码行数的链接。

console.debug(”message” [,objects]) - 记录一个 debug 消息。
console.info(”message” [,objects]) - 记录一个信息.
console.warn(”message” [,objects]) - 记录一个警告.
console.error(”message” [,objects]) - 记录一个错误.

断言

断言是一条确保代码规则的非常好的途径。console 对象包含了一系列各种类型的断言函数,并且允许你编写自己的断言函数。

console.assert(a, “message” [,objects]) - Asserts that an a is true.
console.assertEquals(a, b, “message” [,objects]) - Asserts that a is equal to b.
console.assertNotEquals(a, b, “message” [,objects]) - Asserts that a is not equal to b.
console.assertGreater(a, b, “message” [,objects]) - Asserts that a is greater than b.
console.assertNotGreater(a, b, “message” [,objects]) - Asserts that a is not greater than b.
console.assertLess(a, b, “message” [,objects]) - Asserts that a is less than b.
console.assertNotLess(a, b, “message” [,objects]) - Asserts that a is not less than b.
console.assertContains(a, b, “message” [,objects]) - Asserts that a is in the array b.
console.assertNotContains(a, b, “message” [,objects]) - Asserts that a is not in the array b.
console.assertTrue(a, “message” [,objects]) - Asserts that a is equal to true.
console.assertFalse(a, “message” [,objects]) - Asserts that a is equal to false.
console.assertNull(a, “message” [,objects]) - Asserts that a is equal to null.
console.assertNotNull(a, “message” [,objects]) - Asserts that a is not equal to null.
console.assertUndefined(a, “message” [,objects]) - Asserts that a is equal to undefined.
console.assertNotUndefined(a, “message” [,objects]) - Asserts that a is not equal to undefined.
console.assertInstanceOf(a, b, “message” [,objects]) - Asserts that a is an instance of type b.
console.assertNotInstanceOf(a, b, “message” [,objects]) - Asserts that a is not an instance of type b.
console.assertTypeOf(a, b, “message” [,objects]) - Asserts that the type of a is equal to the string b.
console.assertNotTypeOf(a, b, “message” [,objects]) - Asserts that the type of a is not equal to the string b.

测量(Measurement)

下面的一些函数可以让你方便的测量你的一些代码。

console.trace() - 记录执行点的堆栈信息。
console.time(”name”) - 根据 name 创建一个唯一的计时器。
console.timeEnd(”name”) - 根据 name 停止计时器,并且记录消耗的时间,以毫秒为单位。
console.count(”name”) - 记录该行代码执行的次数。

字符串格式化

所有 console 的 logging 函数都可以通过以下模式格式化字符串:

%s - 将对象格式化为字符串。
%d, %i, %l, %f - 将对象格式化为数字。
%o - 将对象格式化成一个指向 inspector 的超链接。
%1.o, %2.0, etc.. - 将对象格式化成包含自己属性的可交互的表格。
%.o - 将对象格式化成具有自身属性的一个数组。
%x - 将对象格式化成一个可交互的 XML 树形结构。
%1.x, %2.x, etc.. - 将对象格式化成一个可交互的 XML 数型结构,并且展开 n 层节点。

如果你需要一个真实的 % 符号,你可以通过一个转移符号就像这样 “\%”。

命令行函数

内建的命令行函数可以通过以下命令行使用:

$(”id”) - document.getElementById() 的简写。(译者注:跟 prototype.js 学来的吧?)
$$(”css”) - 返回一个符合 CSS 选择器的元素数组。
$x(”xpath”) - 返回一个符合 XPath 选择器的元素数组。
$0 - 返回最近被检查(inspected)的对象。
$1 - 返回最近被检查(inspected)的下一个对象。
$n(5) - 返回最近被检查的第n个对象。
inspect(object) - 将对象显示在 Inspector 中。
dir(object) - 返回一个对象的属性名数组。(译者注:跟 Python 学的?)
clear() - 清除控制台信息。

Posted in Ajax, JavaScript, Web?? | 7 Comments »

JavaScript高级程序设计

Posted by ShiningRay on 8th 九月 2006

profession-javascript-cover.jpg

基本信息

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

译者序

Read the rest of this entry »

Posted in JavaScript, 书籍 | 4 Comments »

编写自己的dojo扩展

Posted by Nicholas Ding on 12th 八月 2006

前言

dojo是时下非常流行的javascript框架,它在自己的Wiki上给自己下了一个定义,dojo是一个用JavaScript编写的开源的DHTML工具箱。

dojo很想做一个“大一统”的工具箱,不仅仅是浏览器层面的,野心还是很大的。不过dojo带来了JavaScript编程的一些新想法,其中引入包机制进行动态加载是一个不错的概念。

理解dojo的包机制

其实dojo只需要一些很小的加载代码就可以用来加载它的各种包,它的官方站点上提供的dojo-ajax下载中包含的dojo.js体积还是比较庞大的,因为它将一些常用的包都包含在了js中,

但是很多时候我们并不需要这么多功能,还是按需加载比较好。

幸好在http://download.dojotoolkit.org/这个地址中我们还可以下载到dojo的各个自定义版本,其实包含的组件都是一样的,只不过dojo.js的大小有很大不同,那么,我们就从minimal版本下手。

下载之后会发现minimal版本包含的dojo.js只有18kb,里面仅仅包含了加载机制,非常不错。这样,我们就可以开始编写自己的dojo扩展。

dojo代码结构

解压缩后的目录里面包含src目录,src目录下存放有dojo的各个组件包,我们在这里面新建一个hello目录。

新建一个名为__package__.js文件,很类似Python的模块命名,这个__package__.js定义了在引入这个命名空间的时候默认导入多少类,以及这个命名空间的名字。

我们的目的是做一个dojo.hello.Echo扩展,那么在__package__.js中的代码应该这样:

// kwCompoundRequire 的作用是当你导入整个dojo.hello包的时候需要默认加载多少类
// 这些定义就在这个函数里面,common在这里表示默认的加载,这个参数不是固定的
// dojo希望自己是一个“大一统”的实现,所以考虑了非浏览器情况,可以有别的,譬如rhino
dojo.kwCompoundRequire({
	common: [
		"dojo.hello.Echo"
	]
});
// 这个定义了包,默认这么写 | 原因嘛,当然是有的,看你的悟性了:-)
dojo.provide("dojo.hello.*");

我们指定了默认加载的类Echo,那么我们就去写Echo类,在hello目录中新建Echo.js,代码如下:

// 类名定义,JavaScript写的变扭,其实就是直接定义类名
dojo.provide("dojo.hello.Echo");
 
// 类定义部分,非常熟悉的代码
dojo.hello.Echo = function() {
	this.name = "dojo.hello.Echo";
	this.sayHello = function(greeting) {
		return greeting;
	}
}

扩展写好了,很简单,接下来就是掉用了,index.html如下。

<html>
<head>
<script language="javascript" src="dojo.js"></script>
<script language="javascript">
dojo.require("dojo.hello.*");
var echo = new dojo.hello.Echo();
document.write(echo.sayHello("Hello World"));
</script>
</head>
<body>

</body>
</html>

注意dojo.require(”dojo.hello.*”)回去请求两个文件,首先是__package__.js,这样一来就得到了之前在dojo.kwCompoundRequire里面指定的类列表,然后去加载Echo.js。你也可以直

接去加载Echo.js,只需要变成dojo.require("dojo.hello.Echo")

更多内容

这个例子非常简单的介绍了一下dojo的包加载机制,当然这个包中的类并没有引用其它类,dojo还允许在代码中动态加载其它类,当然了,这些都是通过XmlHttp来实现的,因为是同步模式,所以请求的类比较多并且都没有包含在dojo.js中的时候会有页面停顿的现象,这点还是需要注意的。

文中的代码下载:,dojo-hello.tar.gz

一些dojo的资源:

Posted in Ajax, JavaScript, Web?? | No Comments »

如何优化JavaScript脚本的性能

Posted by ShiningRay on 5th 四月 2006

作者:ShiningRay
@ Nirvana Studio

随着网络的发展,网速和机器速度的提高,越来越多的网站用到了丰富客户端技术。而现在Ajax则是最为流行的一种方式。JavaScript是一种解释型语言,所以能无法达到和C/Java之类的水平,限制了它能在客户端所做的事情,为了能改进他的性能,我想基于我以前给JavaScript做过的很多测试来谈谈自己的经验,希望能帮助大家改进自己的JavaScript脚本性能。

语言层次方面

循环

循环是很常用的一个控制结构,大部分东西要依靠它来完成,在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以就应该尽量少用。for(;;)和while循环的性能应该说基本(平时使用时)等价。

而事实上,如何使用这两个循环,则有很大讲究。我在测试中有些很有意思的情况,见附录。最后得出的结论是:

  • 如果是循环变量递增或递减,不要单独对循环变量赋值,应该在它最后一次读取的时候使用嵌套的++或—操作符。

  • 如果要与数组的长度作比较,应该事先把数组的length属性放入一个局部变量中,减少查询次数。

局部变量和全局变量

局部变量的速度要比全局变量的访问速度更快,因为全局变量其实是全局对象的成员,而局部变量是放在函数的栈当中的。

不使用Eval

使用eval相当于在运行时再次调用解释引擎对内容进行运行,需要消耗大量时间。这时候使用JavaScript所支持的闭包可以实现函数模版(关于闭包的内容请参考函数式编程的有关内容)

减少对象查找

因为JavaScript的解释性,所以a.b.c.d.e,需要进行至少4次查询操作,先检查a再检查a中的b,再检查b中的c,如此往下。所以如果这样的表达式重复出现,只要可能,应该尽量少出现这样的表达式,可以利用局部变量,把它放入一个临时的地方进行查询。

这一点可以和循环结合起来,因为我们常常要根据字符串、数组的长度进行循环,而通常这个长度是不变的,比如每次查询a.length,就要额外进行一个操作,而预先把var
len=a.length,则就少了一次查询。

字符串连接

如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr。

如果要连接多个字符串,应该少使用+=,如

s+=a;
s+=b;
s+=c;

应该写成

s+=a + b + c;

而如果是收集字符串,比如多次对同一个字符串进行+=操作的话,最好使用一个缓存。怎么用呢?使用JavaScript数组来收集,最后使用join方法连接起来,如下

var buf = new Array();
for(var i = 0; i < 100; i++){
buf.push(i.toString());
}
var all = buf.join("");

类型转换

类型转换是大家常犯的错误,因为JavaScript是动态类型语言,你不能指定变量的类型。

1.
把数字转换成字符串,应用""
+ 1,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:

(""
+) > String() > .toString() > new String()

这条其实和下面的“直接量”有点类似,尽量使用编译时就能使用的内部操作要比运行时使用的用户操作要快。

String()属于内部函数,所以速度很快,而.toString()要查询原型中的函数,所以速度逊色一些,new
String()用于返回一个精确的副本。

2.
浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()。

另外,和第二节的对象查找中的问题不一样,Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。

3.
对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString(),因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高

使用直接量

其实这个影响倒比较小,可以忽略。什么叫使用直接量,比如,JavaScript支持使用[param,param,param,…]来直接表达一个数组,以往我们都使用new
Array(param,param,…),使用前者是引擎直接解释的,后者要调用一个Array内部构造器,所以要略微快一点点。

同样,var
foo = {}的方式也比var
foo = new Object();快,var
reg = /../;要比var
reg=new RegExp()快。

字符串遍历操作

对字符串进行循环操作,譬如替换、查找,应使用正则表达式,因为本身JavaScript的循环速度就比较慢,而正则表达式的操作是用C写成的语言的API,性能很好。

高级对象

自定义高级对象和Date、RegExp对象在构造时都会消耗大量时间。如果可以复用,应采用缓存的方式。

DOM相关

插入HTML

很多人喜欢在JavaScript中使用document.write来给页面生成内容。事实上这样的效率较低,如果需要直接插入HTML,可以找一个容器元素,比如指定一个div或者span,并设置他们的innerHTML来将自己的HTML代码插入到页面中。

对象查询

使用[“”]查询要比.items()更快,这和前面的减少对象查找的思路是一样的,调用.items()增加了一次查询和函数的调用。

创建DOM节点

通常我们可能会使用字符串直接写HTML来创建节点,其实这样做

  1. 无法保证代码的有效性

  2. 字符串操作效率低

所以应该是用document.createElement()方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法,因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数——同样如果需要创建很多元素,应该先准备一个样板节点。

定时器

如果针对的是不断运行的代码,不应该使用setTimeout,而应该是用setInterval。setTimeout每次要重新设置一个定时器。

其他

脚本引擎

据我测试Microsoft的JScript的效率较Mozilla的Spidermonkey要差很多,无论是执行速度还是内存管理上,因为JScript现在基本也不更新了。但SpiderMonkey不能使用ActiveXObject

文件优化

文件优化也是一个很有效的手段,删除所有的空格和注释,把代码放入一行内,可以加快下载的速度,注意,是下载的速度而不是解析的速度,如果是本地,注释和空格并不会影响解释和执行速度。

总结

本文总结了我在JavaScript编程中所找到的提高JavaScript运行性能的一些方法,其实这些经验都基于几条原则:

  1. 直接拿手头现成的东西比较快,如局部变量比全局变量快,直接量比运行时构造对象快等等。

  2. 尽可能少地减少执行次数,比如先缓存需要多次查询的。

  3. 尽可能使用语言内置的功能,比如串链接。

  4. 尽可能使用系统提供的API,因为这些API是编译好的二进制代码,执行效率很高

同时,一些基本的算法上的优化,同样可以用在JavaScript中,比如运算结构的调整,这里就不再赘述了。但是由于JavaScript是解释型的,一般不会在运行时对字节码进行优化,所以这些优化仍然是很重要的。

当然,其实这里的一些技巧同样使用在其他的一些解释型语言中,大家也可以进行参考。

参考

附录1

由于是以前做过的测试,测试代码已经不全,我补充了一部分如下:

var print;

if(typeof document != "undefined" ){
    print = function(){
		document.write(arguments[0]);
	}
}else if(typeof WScript != "undefined" ){
    print = function(){
        WScript.Echo(arguments[0],arguments[1],arguments[2]);
    }
}

function empty(){
}

function benchmark(f){
    var i = 0;
    var start = (new Date()).getTime();

    while(i < pressure){
        f(i++);
    }
    var end = (new Date()).getTime();
    WScript.Echo(end-start);
}

/*
i=0
start = (new Date()).getTime();
while(i < 60000){
    c = [i,i,i,i,i,i,i,i,i,i];
    i++;
}
end = (new Date()).getTime();
WScript.Echo(end-start);
i=0
start = (new Date()).getTime();
while(i < 60000){
    c = new Array(i,i,i,i,i,i,i,i,i,i);
    i++;
}
var end = (new Date()).getTime();
WScript.Echo(end-start);
*/

function internCast(i){
    return "" + i;
}

function StringCast(i){
    return String(i)
}
function newStringCast(i){
    return new String(i)
}
function toStringCast(i){
    return i.toString();
}
function ParseInt(){
    return parseInt(j);
}
function MathFloor(){
    return Math.floor(j);
}
function Floor(){
    return floor(j);
}
var pressure = 50000;
var a  = "";
var floor = Math.floor;
j = 123.123;
print("-------------\nString Conversion Test");
print("The empty:", benchmark(empty));
print("intern:", benchmark(internCast));
print("String:");
benchmark(StringCast);
print("new String:");
benchmark(newStringCast);
print("toString:");
benchmark(toStringCast);
print("-------------\nFloat to Int Conversion Test");
print("parseInt");
benchmark(ParseInt);
print("Math.floor");
benchmark(MathFloor);
print("floor")
benchmark(Floor);

function newObject(){
    return new Object();
}

function internObject(){
    return {};
}
print("------------\nliteral Test");
print("runtime new object", benchmark(newObject));
print("literal object", benchmark(internObject));

附录2

代码1:

    for(var i=0;i<100;i++){
        arr[i]=0;
    }


代码2:

    var i = 0;
    while(i < 100){
        arr[i++]=0;
    }


代码3:

    var i = 0;
    while(i < 100){
        arr[i]=0;
        i++;
    }


在firefox下测试这两段代码,结果是代码2优于代码1和3,而代码1一般优于代码3,有时会被代码3超过;而在IE
6.0下,测试压力较大的时候(如测试10000次以上)代码2和3则有时候优于代码1,有时候就会远远落后代码1,而在测试压力较小(如5000次),则代码2>代码3>代码1。

代码4:

    var i = 0;
    var a;
    while(i < 100){
        a = 0;
        i++;
    }


代码5:

    var a;
    for(var i=0;i<100;i++){
        a = 0;
    }

上面两段代码在Firefox和IE下测试结果都是性能接近的。

代码6:

    var a;
    var i=0;
    while(i<100){
        a=i;
        i++;
    }


代码7:

    var a;
    var i=0;
    while(i<100){
        a=i++;
    }


代码8:

    var a;
    for(var i=0;i<100;i++){
        a = i;
    }


代码9:

    var a;
    for(var i=0;i<100;){
        a = i++;
    }

这四段代码在Firefox下6和8的性能接近,7和9的性能接近,而6,
8 < 7, 9;

最后我们来看一下空循环

代码10:

    for(var i=0;i<100;i++){   }


代码11:

    var i;
    while(i<100){        i++;    }

最后的测试出现了神奇的结果,Firefox下代码10所花的时间与代码11所花的大约是24:1。所以它不具备参考价值,于是我没有放在一开始给大家看。

Posted in JavaScript | 4 Comments »

JavaScript = C + Lisp

Posted by ShiningRay on 3rd 四月 2006

作者: William Taysom

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

翻译:ShiningRay

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

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

考虑以下代码:

semanticAccepter = acceptOnlyIf(
    acceptNot(emptyTextAccepter),
    scriptFilter,
    acceptAny(
        textAccepter,
        linkAccepter,
        formRelatedElementAccepter,
        linkImageAccepter));
usefulContentOfThePage = new SemTree(semanticAccepter);

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

function emptyTextAccepter(n) {
    return (n instanceof Text) && n.data.match(/^\s*$/);
}

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

function acceptAny() {
    var disjuncts = arguments;
    return function(n) {
        for (var i = 0; i < disjuncts.length; ++i) {
            var shouldAccept = disjuncts[i](n);
            if (shouldAccept) return shouldAccept;
        }
        return false;
    }
}

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

function acceptOnlyIf() {
    var conjuncts = arguments;
    return function(n) {
        for (var i = 0; i < conjuncts.length; ++i) {
            var shouldAccept = conjuncts[i](n);
            if (shouldAccept != true) return shouldAccept;
        }
        return true;
    }
}

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

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

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

type Accepter = Node -> Bool

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

not ::  Bool  -> Bool
and :: [Bool] -> Bool
or  :: [Bool] -> Bool

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

acceptNot    ::  Accepter  -> Accepter
acceptOnlyIf :: [Accepter] -> Accepter
acceptAny    :: [Accepter] -> Accepter

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

semanticAccepter = acceptOnlyIf [
    acceptNot emptyTextAccepter,
    scriptFilter,
    acceptAny [
        textAccepter,
        linkAccepter,
        formRelatedElementAccepter,
        linkImageAccepter]];

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

acceptOnlyIf []     n = true
acceptOnlyIf (a:as) n | a n        = true
                      | otherwise  = acceptOnlyIf as n

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

map :: (a -> b) -> ([a] -> [b])
map f []     = []
map f (x:xs) = f x : map xs

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

acceptOnlyIf as n = and (map (\a -> a n) as)

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

function(a) {
    return a(n);
}

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

acceptOnlyIf as n = and (map (\a -> a n) as)
acceptAny    as n = or  (map (\a -> a n) as)

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

liftCombiner :: ([Bool] -> Bool) -> ([Accepter] -> Accepter)
liftCombiner c as n = c (map (\a -> a n) as)
acceptOnlyIf = liftCombiner and
acceptAny    = liftCombiner or

现在我们是否可以更进一步了呢?我们是否可以也将 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则是列表的其它部分。

data LinkedList a = Nil | Cons a (List a)

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

oneTwoThree = Cons 1 (Cons 2 (Cons 3 (Cons Nil)))

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

oneTwoThree = 1 : (2 : (3 : []))

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

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

class List ls where
    nil  :: ls a
    cons :: a -> ls a -> ls a
    head :: ls a -> a
    tail :: ls a -> ls a
    map  :: (a -> b) -> (ls a -> ls b)

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

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

class List ls where
    unit :: a -> ls a
    map  :: (a -> b) -> (ls a -> ls b)
    join :: ls (ls a) -> ls a

以下是链表的实现:

instance List [] where
    unit a = [a]
    map  f []     = []
    map  f (a:as) = f a : map f as
    
    join []       = []
    join [ls:lss] = ls ++ join lss

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

(++) []     ls = ls
(++) (k:ks) ls = k : (ks ++ ls)
[1, 2, 3] ++ [4, 5, 6]        --> [1, 2, 3, 4, 5, 6]
unit 5                        --> [5]
map (\x -> x + x) [1, 2, 3]   --> [2, 4, 6]
join [[1], [2, 3], [4, 5, 6]] --> [1, 2, 3, 4, 5, 6]

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

class Monad m where
    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b

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

instance Monad [] where

    return a   = [a]

    (>>=) ls f = join (map f ls)

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

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

sumTwoLists [1, 2, 3] [4, 5, 6] --> [5, 6, 7, 6, 7, 8, 7, 8, 9]

JavaScript代码应该像:

function sumTwoLists(ks, ls) {
    var result = [];
    for (var i = 0; i < ks.length; ++i) {
        for (var j = 0; j < ls.length; ++j) {
            result.push(ks[i] + ls[j]);
        }
    }
    return result;
}

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

sumTwoLists ks ls = return (ks >>= \k ->
                            ls >>= \l ->
                            k + l)

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

sumTwoLists ks ls = do k <- ks
                       l <- ls
                       return k + l

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

sumTwoLists ks ls = [k + l | k <- ks, l <- ls]

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

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

instance Monad Identity where
    return a  = a
    (>>=) a f = f a

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

liftCombiner c as n = c [a n | a <- as]

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

acceptOnlyIf = liftCombiner and
acceptAny    = liftCombiner or
acceptNot    = liftCombiner not

最后的思考

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

Posted in Haskell, JavaScript, 函数式编程 | 1 Comment »

函数式JavaScript编程指南

Posted by ShiningRay on 2nd 四月 2006

原文地址:http://www.pfeiffer-mediation.de/remast/javascript.php 翻译:ShiningRay

请阅读:
>>Functional_JavaScript_Programming.htm<<

Posted in JavaScript, 函数式编程 | 2 Comments »

嵌入JavaScript引擎梗概教程

Posted by ShiningRay on 2nd 四月 2006

嵌入JavaScript引擎

梗概教程

作者:Brendan Eich

2000年2月21日

翻译:ShiningRay @ NirvanaStudio

 

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

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

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

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

JSRuntime *rt; 
    JSContext *cx; 
    JSObject *global; 
    JSClass global_class = { 
        "global",0, 
        JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub, 
        JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub 
    }; 
 
    /* 
     * 你必须有:You always need: 
     *        每个进程一个运行时(runtime), 
     *        每个线程一个上下文(context), 
     *        每个上下文有一个全局对象(global), 
     *        标准类(如Date)。
     */ 
    rt = JS_NewRuntime(0x100000); 
 
    cx = JS_NewContext(rt, 0x1000); 
    global = JS_NewObject(cx, &global_class, NULL, NULL); 
    JS_InitStandardClasses(cx, global); 
 
    /* 
     * 
现在假设脚本包含了要计算的一些JS,比方说像“22/7”Math.PI的 
     * 不太好的近似值,或者一些更长的东西,像:
     * 
"(function fact(n){if (n <= 1) return 1; return n * 
fact(n-1)})(5)" 
     
* 来计算5!
     
*/ 
    char *script = "..."; 
    jsval rval; 
    JSString *str; 
    JSBool ok; 
 
    ok = JS_EvaluateScript(cx, global, script, strlen(script), 
                           filename, lineno, &rval); 
 
    str = JS_ValueToString(cx, rval); 
    printf("script result: %s\n", JS_GetStringBytes(str));

如何从JavaScript中调用C函数

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

#define DOIT_MINARGS 2 
    static JSBool 
    doit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) 
    { 
        /* 
         * 在argv中查找argc个实参,设置*rval来给调用者返回一个值  
         */ 
        ... 
    }

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

ok = JS_DefineFunction(cx, global, "doit", doit, DOIT_MINARGS, 0);

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

static JSFunctionSpec my_functions[] = { 
        {"doit", doit, DOIT_MINARGS, 0, 0}, 
        etc... 
        {0,0,0,0,0}, 
    };

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

ok = JS_DefineFunctions(cx, global, my_functions);

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

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

JSObject *target, *event; 
    jsval argv[1], rval; 
 
   /* 
    * 找到事件目标并且产生一个事件对象来表示这个点击。
    * 将cx传给NewEventObject这样JS_NewObject就可以被调用了。
    */ 
    target = FindEventTargetAt(cx, global, x, y); 
event = NewEventObject(cx, "click", x, y); 
    argv[0] = OBJECT_TO_JSVAL(event); 
 
    /* 要模拟DOM,你也许还想尝试一下“onclick”*/ 
    ok = JS_CallFunctionName(cx, target, "onClick", 1, argv, &rval); 
 
    /* 现在测试rval来看看我们是否需要取消事件. */ 
    if (JSVAL_IS_BOOLEAN(rval) && !JSVAL_TO_BOOLEAN(rval)) 
        CancelEvent(event);

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


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

Posted in C, JavaScript | No Comments »

JavaScript-C引擎嵌入开发指南

Posted by ShiningRay on 2nd 四月 2006

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_SetContextPrivate(cx, pdata);

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

pdata = JS_GetContextPrivate(cx);

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

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

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

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

JSBool builtins;
.
.
.
builtins = JS_InitStandardClasses(cx, glob);

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_SetPrivate(cx, obj, pdata);

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

pdata =JS_GetPrivate(cx, obj);

处理统一码(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

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

Posted in C, JavaScript | 5 Comments »

JavaScript中的类继承

Posted by ShiningRay on 2nd 四月 2006

JavaScript中的类继承

DouglasCrockford
www.crockford.com

翻译 ShiningRay @ www.nirvanastudio.org

And you thinkyou’re so clever and classless and free
–John Lennon

JavaScript一种没有类的,面向对象的语言,它使用原型继承来代替类继承。这个可能对受过传统的面向对象语言(如C++和Java)训练的程序员来说有点迷惑。JavaScript的原型继承比类继承有更强大的表现力,现在就让我们来看看。

Java

JavaScript

强类型

弱类型

静态

动态

基于类

基于原型

函数

构造器

函数

方法

函数

但首先,为什么我们如此关心继承呢?主要有两个原因。第一个是类型有利。我们希望语言系统可以自动进行类似类型引用的转换cast。小类型安全可以从一个要求程序显示地转换对象引用的类型系统中获得。这是强类型语言最关键的要点,但是这对像JavaScript这样的弱类型语言是无关的,JavaScript中的类引用无须强制转换。

第二个原因是为了代码的复用。在程序中常常会发现很多对象都会实现同一些方法。类让建立单一的一个定义集中建立对象成为可能。在对象中包含其他对象也包含的对象也是很常见的,但是区别仅仅是一小部分方法的添加或者修改。类继承对这个十分有用,但原型继承甚至更有用。

要展示这一点,我们要介绍一个小小的“甜点”可以主我们像一个常规的类语言一样写代码。我们然后会展示一些在类语言中没有的有用的模式。最后,我们会就会解释这些“甜点”。

类继承

首先,我们建立一个Parenizor类,它有成员 valuegetset方法,还有一个会将value包装在括号内的toString方法。

function Parenizor(value) {
    this.setValue(value);
}
 
Parenizor.method('setValue', function (value) {
    this.value = value;
    return this;
});
 
Parenizor.method('getValue', function () {
    return this.value;
});
 
Parenizor.method('toString', function () {
    return '(' + this.getValue() + ')';
});

这个语法可能没什么用,但它很容易看出其中类的形式。method方法接受一个方法名和一个函数,并把它们放入类中作为公共方法。

现在我们可以写成