65.9K
CodeProject 正在变化。 阅读更多。
Home

TIScript语言,JavaScript 的温和扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (21投票s)

2009年2月25日

BSD

6分钟阅读

viewsIcon

131214

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 中,它被分成了两个不同的类:IntegerFloat,因为它们确实代表了两个不同的实体。

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 虚拟机在帮助我写作本文时正在运行。

© . All rights reserved.