TIScript语言,JavaScript 的温和扩展
TIScript 语言与其原型 JavaScript 有何不同。
引言
作为一门语言,TIScript 是 ECMAScript (JavaScript 1.X) 的扩展版本。如果你愿意,可以将其视为 JavaScript++。
TIScript 的设计基于对实际 JavaScript 用例的分析。在某些方面,它简化并统一了 JavaScript 的特性。例如,prototype(原型)机制被简化了。在其他方面,它在保留 JS 原始“观感”的同时,对 JavaScript 进行了扩展。
TIScript 引擎由以下部分组成:
- 编译器(compiler),用于生成字节码
- 虚拟机(virtual machine, VM),用于执行字节码
- 堆管理器(heap manager),使用复制式垃圾回收器(copying garbage collector, GC)
- 运行时(runtime),是原生对象类的实现
TIScript 引擎的源代码可在 TIScript on GoogleCode 仓库中找到。
本文描述了 TIScript 中那些不存在于或不同于 JavaScript 的主要特性。您应该至少熟悉 JavaScript 的基础知识,或者任何其他动态语言,如 Python、Perl、Lua、Ruby 等。
命名空间
命名空间(Namespace)通过使用 namespace
关键字声明。它们可以包含类、函数、变量和常量。
namespace MyNamespace
{
var nsVar = 1;
function Foo() { nsVar = 2; } // sets nsVar to 2
}
MyNamespace.Foo(); // invokes Foo
JavaScript 不支持命名空间。你可以使用对象来模拟它们,但这确实只是一种模拟。
TIScript 中的命名空间只是命名的作用域。例如,在处理对某个看起来像全局变量的赋值时,TIScript 运行时首先会尝试在该函数所属的当前命名空间链中查找该变量。
类、构造函数和属性
TIScript 引入了真正的类。类通过 class
关键字声明,可以包含函数、属性函数、变量、常量以及其他类。
class Bar
{
function this() { // the function named 'this' is
this._one = 1; // a constructor of objects in
} // the class being defined
function foo(p1) { // a method
this._one = p1;
}
property one(v) { // property-function
get { return this._one; } // has getter and
set { this._one = v; } // setter sections
}
}
请注意上面代码中的 property
函数。这是一种特殊类型的函数,用于声明可计算的属性。封装在此类函数中的属性可以正常访问。
var bar = new Bar(); // invokes Bar.this()
bar.one = 2; // invokes Bar.one()::set section
在某些情况下,一组属性在设计时是未知的。TIScript 允许你通过 property undefined()
方法为这类属性实现访问器。
class Recordset
{
function getFieldValue(idx) { ... }
function getFieldIdx(byName) { ... }
property undefined(name, val)
{
get { var fieldIdx = this.getFieldIdx(name);
return this.getFieldValue(fieldIdx); }
}
}
这个属性处理器可以如下使用:
var rs = DB.exec( "SELECT one, two FROM sometable" );
var one = rs.one; // access the column named "one" in the recordset
// via the special handler above
轻量级匿名函数
TIScript 引入了一种用于定义匿名(lambda)函数的轻量级语法。这使得在 TIScript 中共有三种声明匿名函数的方式:
':' [param-list] ':' <statement>;
':' [param-list] '{' <statement-list> '}'
'function(' [param-list] ')' '{' <statement-list> '}'
- 单语句 lambda 函数
- Lambda 函数块
- 经典匿名函数
以下是一个示例,展示如何使用一个内联定义的比较器函数对数组进行降序排序:
var arr = [1,2,3];
arr.sort( :a,b: a < b? 1:-1 );
在这里,:a,b: a < b? 1:-1
是一个 lambda 函数的内联声明,它将被传递给 Array.sort()
方法。
装饰器
装饰器(Decorator)是一种元编程特性,借鉴自 Python 语言。在 TIScript 中,装饰器是一个普通函数。它的名称必须以“@”字符开头,并且必须至少有一个参数。该参数(第一个)是对被装饰的某个其他函数(或类)的引用。以下是一个装饰器函数的声明示例:
function @KEY(func, keyCode)
{
function t(event) // wraps a call to func()
{ // into a filter function
if(event.keyCode == keyCode)
func();
if(t.next)
t.next.call(this,event);
}
t.next = this.onKey; // establishes the chain
this.onKey = t; // of event handlers
}
如果有了这样的装饰器,我们就可以定义在小部件上按下不同按键时激活的代码块:
class MyWidget : Widget
{
@KEY 'A' : { this.doSelectAll(); }
@KEY 'B' : { this.doSomethingWhenB(); }
}
在这里,两个 @KEY
条目装饰了两个匿名函数(参见上一节)。以上代码假定在某处定义了一个Widget
,并且它带有一个回调方法 onKey(event)
。
装饰器是一项高级功能,可能需要一些精力来理解。一旦掌握,装饰器可以显著提高代码的表现力。关于装饰器的更多细节可以在这里和这里找到。
迭代器
JavaScript(以及 TIScript)有一个非常方便的 for-each
语句:for( var item in collection ){..}
,其中 collection
是一个对象或数组的实例。
在 TIScript 中,可枚举对象的列表扩展到了函数实例。因此,语句 for( var item in func)
将调用 func
,并在每次迭代中使用其返回值执行循环体。例如,这个函数:
function range( from, to )
{
var idx = from - 1;
return function() { if( ++idx <= to ) return idx; }
}
将生成 [from..to]
范围内的连续数字。所以,如果你写下这样的代码:
for( var item in range(12,24) )
stdout << item << " ";
那么你将在 stdout
中逐个打印出从 12 到 24 的数字。
这是另一个类集合的例子,它允许以两个方向枚举其成员:
class Fruits
{
function this() {
this._data = ["apple","orange","lime","lemon",
"pear","banan","kiwi","pineapple"]; }
property forward(v)
{
get {
var items = this._data; var idx = -1;
// return function that will supply "next" value
// on each invocation
return function() { if( ++idx < items.length ) return items[idx]; }
}
}
property backward(v)
{
get {
var items = this._data; var idx = items.length;
return function() { if( --idx >= 0 ) return items[idx]; }
}
}
}
如你所见,上面的类有两个属性,它们返回迭代器,允许在两个方向上扫描集合:
var fruits = new Fruits();
stdout << "Fruits:\n";
for( var item in fruits.forward ) stdout << item << "\n";
stdout << "Fruits in opposite direction:\n";
for( var item in fruits.backward ) stdout << item << "\n";
Mozilla 的人介绍了他们版本的Spider Monkey 中的迭代器,我相信这是“原封不动”地从 Python 借鉴过来的。我认为我的迭代器版本更符合 JavaScript 的精神。至少,它没有引入新的实体和类。
原型属性
与 JavaScript 相比,TIScript 中的原型机制得到了简化。
在 TIScript 中,每个对象都有一个名为 prototype
的属性。对象的原型是对其类的引用,而类本身也只是一个对象。类的原型是对其超类的引用。同样,命名空间(它和其他东西一样,也是一个对象)的原型是对其父命名空间的引用。
例如,所有这些语句的求值结果都为 true
:
"somestring".prototype === String; // prototype of the string is String class object.
{ some:"object", literal:true }.prototype === Object;
对于用户定义的类:
class Foo { ... }
var foo = new Foo();
foo.prototype === Foo;
类型系统
JavaScript 的 number
类型将整数和浮点数统一为一个类型。在 TIScript 中,它被分成了两个不同的类:Integer
和 Float
,因为它们确实代表了两个不同的实体。
TIScript 还引入了一些新的类型:
流(Stream)是字符或字节的序列。TIScript 运行时支持三种类型的流:内存流(也称为 String stream
)、套接字流(socket stream
)和文件流(file stream
)。内存流是生成文本的有效方式。引入它们的目的与“大 Java”中的 StringBuffer
/StringBuilder
类相同。
Bytes
对象的实例是一个字节数组。
这两个类构成了 TIScript 内置持久化的核心。通过将 TIScript 对象(以及它引用的所有对象)赋给 storage.root
属性,可以使其持久化。整个对象树会被透明地放置在硬盘上的一个存储文件中。本质上,这是一个面向对象的数据库(OODB)。我称之为 JSON-DB,因为只有 JS 对象的 JSON 子集才能持久化。例如,套接字 stream
本质上是非持久的。
几乎所有的动态语言都以某种形式拥有原子(atom)的概念。TIScript 也有。
符号
对象的名称是一个由允许字符组成的 string
。符号是与该名称关联的一个数字。TIScript
维护着一个全局的 name<->int
对的映射(内部实现为三叉搜索树)。在编译时,每个名称都会被翻译成一个 Int32
数字——即它的符号。
在某些情况下,你可能想显式声明符号。这时,符号字面量就派上用场了。符号字面量是以“#”字符开头的字符序列。它可以包含字母数字字符、下划线('_')和连字符('-')。在符号中使用连字符是为了与 CSS(层叠样式表)兼容,因为它们是名称标记的一部分。
符号使用示例:
function ChangeMode( mode )
{
if( mode == #edit )
this.readOnly = false;
else if( mode == #read-only )
this.readOnly = true;
else
throw mode.toString() + " - bad symbol!";
}
如你所见,符号可以作为具有自描述性、方便且高效的自动枚举
值来使用。
另一个使符号非常有用的特性是按符号访问表示法。
像这样的结构:
var aa = obj#name;
obj#name = val;
会被翻译成:
var aa = obj[#name]; // and
obj[#name] = val
语句。虽然改动不大,但这会使代码更具可读性。
这在 Sciter 中经常使用,它是一个可嵌入的 HTML/CSS/脚本引擎。例如,要访问 DOM 元素的样式(CSS)属性:
var elem = self.select("#some");
elem.style#display = "block";
elem.style#border-left-width = px(1); // or elem.style[#border-left-width] = "1px";
elem.style#border-right-width = px(2);
...
结论
TIScript 的简要概述到此结束。在下一篇文章中,我将解释如何将 TIScript 引擎嵌入到您的应用程序中。
本文是在 Sciter 的所见即所得(WYSIWYG)HTML 编辑器 - Scapp(Sciter application 的缩写)中编写的。因此,TIScript 虚拟机在帮助我写作本文时正在运行。