TypeScript:更好的JavaScript

作为编程语言的TypeScript

关于TypeScript,首先要认识的一点就是:它是Anders Hejlsberg的作品。Anders是第一流的编程语言设计师,也是第一流的编译器

实现者。作为Object Pascal和C#之父,Anders这次仍然采用了此前的做法:他设计了一种新的语言,并实现了这种语言的编译器,

来改进一种已有的语言。但这次又和此前有所不同,此前无论是Object Pascal还是C#,编译的目标代码都是机器码,而TypeScript

的目标代码则是JavaScript。

当然,如果把浏览器看作是虚拟机,而JavaScript看作是在这种虚拟机上运行的目标代码也无不可。总而言之,使用TypeScript这种

语言撰写的源代码需要经过TypeScript编译器的编译,而产生的目标代码是标准的JavaScript。但这还不是TypeScript在语言设计层

面上的特别之处,特别之处有两点。

TypeScript支持on-the-fly编译,即写一句TypeScript就可以立即得到对应的JavaScript代码,这个特性和CoffeeScript类似。但它比

CoffeeScript支持更强的上下文推导,不需要完整的语句写完,就可以生成对应的、不完整的JavaScript代码。

TypeScript是JavaScript的超集(superset),“任何合法的JavaScript都是合法的TypeScript。”这种设计很明显是借鉴了C++对于C

做扩充时采用的做法,它兼容已有的JavaScript代码的决定给很多JavaScript程序员向TypeScript转型时铺就坚实的第一步——他们

可以从自己已有的代码出发,通过一点一点的改动来体会到TypeScript带来的好处,同时,时刻保留说“这样就够了”,然后停止的权

利。直到掌握了比较全面的TypeScript技术以后,才从一开始就采用TypeScript来撰写代码,而只取用编译结果。实际上,“任何合

法的JavaScript都是合法的TypeScript”这种说法并不准确,准确的说法是“任何合法的ECMAScript 6都是合法的TypeScript”。当然,

ECMAScript 6还是一个正在修订的语言规范,而TypeScript在现阶段生成的任何目标代码,涉及可能会引起ECMAScript 6的新特性

的,都采用了向下兼容的ECMAScript 5规范作为准则。但对于各个浏览器自行对JavaScript做的那部分扩充,TypeScript不保证予

以支持。

TypeScript特性简介

前面已说过,TypeScript的设计目标是作为JavaScript或者说ECMAScript 6的超集。换句话说,如同C++的初始目标是作为“更好的

C”一样,TypeScript也可以看作是“更好的JavaScript”,那么好在哪里呢?其实用C++和C的关系来做类比,还是很恰当的。

TypeScript充分利用了JavaScript原有的对象模型并在此基础上做了扩充,添加了较为严格的类型检查机制,添加了模块支持和API

导出的能力。比起JavaScript,TypeScript提供了更多在语言层面上的支持,使得程序员能够以更加标准化的语法来表达语义上的约

束,从而降低了程序出错的机率;TypeScript也使得代码组织和复用变得更加有序,使得开发大型Web应用有了一套标准方法。

对象模型的扩展

JavaScript支持极为广泛的对象模型,除了null和undefined以外,几乎所有的其他实体都可以视为对象,即使是数值、字符串和布尔

型亦可以隐式使用其对应的包装器而直接作为对象用于一般场合。函数和数组这样在其他的编程语言中不被视为对象的实体,在

JavaScript亦视为“一等对象”(first-class object),除了利用实体本身的场合,例如使用数值索引或调用函数体以外,还可以作为普

通的对象拿来添加属性和方法。JavaScript对象支持在任意时刻动态添加属性和方法,并且支持修改和扩充内建对象。一句话,

JavaScript提供了大量进行对象操作的基础设施(facilities)和基本工具(utilities),正是这些内容构成了JavaScript丰富而灵活

对象模型。TypeScript主要从两个方面对JavaScript对象模型进行扩展:一是在核心语言方面,二是在类概念的模塑方面。

声明语义学

在TypeScript中书写涉及DOM对象的JavaScript代码,一般来说不会遇到问题。但这并不是因为TypeScript语言中对DOM对象有

所“了解”,而是因为TypeScript默认会加载名为lib.d.ts的声明文件,其中默认已包含了所有的DOM对象的声明。换言之,当你写下这

个语句:

之时,编译器实际上已隐式地在最前面加上了一句:

这种声明称为环境声明(ambient declaration),遇到环境声明以后,TypeScript便会试图从声明的来源(如声明库中)分析和推导

对象的类型信息。如果找不到任何的来源,它便默认该对象的类型为any。但无论如何,环境声明都不会向生成的JavaScript里加任

何语句。事实上,所有的TypeScript声明都不会生成对应的JavaScript语句,因为JavaScript对象模型中的声明是可选的。这里也可

以看出TypeScript遵循“除非必要,不生成多余的语句”的哲学。但声明在TypeScript中除了有着预先提供类型信息的重要作用之外,

编译器还能根据这些信息完成强大的类型推导,以及精准的静态类型检查。

数据语义学

TypeScript中的数据要求带有明确的类型,如果设定为一种类型,却要将该类型内不合法的值赋给它,则静态类型检查机制会将这

样的语句标示为错误。

可以采用interface关键字定义具名结构类型,这个特性类似于在JavaScript中采用字面量来定义JavaScript对象。所不同的是,在

TypeScript中每个组分都必须指定类型,不过组分可以是可选的,在实际提供字面量对象时,可选组分可以不提供。

同样地,interface的数据类型定义,以及在TypeScript中为数据指定的任何静态类型声明,都不会在生成任何的JavaScript目标代码

有任何体现。例如上面这段代码生成的JavaScript目标代码仅仅是:

函数对象的类型主要由它的签名式(signature)决定,包括各个形式参数的名字、类型和返回值类型。

值得注意的是,函数对象的返回值可以是void,这是void类型唯一可以出现的地方,而它的唯一可能取值是undefined。

函数语义学

TypeScript中的函数除了在JavaScript的函数对象模型的基础上添加了静态类型检查,体现了函数的数据方面之外,还在函数本身的

性质上增加了不少新特性,例如函数缺省参数值:


它会对应地生成以下的JavaScript目标代码,从这里可以清晰地看到它生成代码的逻辑是通过判断参数有无定义来进行的:


TypeScript支持有限的函数重载,为何说是有限的呢?因为一般意义上的函数重载是根据函数签名式的不同,在函数被实际调用时

根据实际参数的类型来绑定到特定的重载函数的。其背后的实现机制,大多数是所谓的名称重整(name mangling)。但TypeScript

中的函数重载不能这样做,它只支持能够以共用实现体为基础的重载,无论声明了多少个同名且签名式不同的函数,它都只能有一

个实现体,且这个实现体必须对所有的重载版本都有意义。这样说可能比较令人费解,

看个例子好了:

只要看一下上述TypeScript代码生成的JavaScript目标代码,就明白了大半:

原因就在于TypeScript在实现重载时并没有使用名称重整机制,而JavaScript又不支持重载,所以只能做出这样非常大的折中方案

了。

TypeScript中最引人注目的一个函数对象特性是支持所谓的“箭头记法”(arrow notation),即Lamda表达式。例如下面的三个函数是

等价的:

但箭头记法最重要的用途还是在需要使用回调函数的场合。此时最易犯的一个错误就是this的作用域并非保留在被调用函数所在的局

部作用域,而成了函数调用方所在的作用域。还是通过一个例子来看:

此时,点击页面,弹出的警告框显示的值是“NAN”,显然有问题。而问题就出在这里的this指的是函数调用方作用域,此处成了全局

作用域,结果自然不对。此时只要改用箭头记法,问题就迎刃而解:

这个记法是ECMAScript 6引入的,查看一下TypeScript生成的目标代码,就可以了解它是采用了迂回的办法在实现达到效果的同时

又保持向下的兼容性。

class和继承语义学

TypeScript对JavaScript对象模型最重要的扩充,自然在于它补充了JavaScript中所没有引入的“类”的概念。是的,在JavaScript中没

有类,只有对象,要实现所谓的“类式操作”(classical operations),如封装、多态等,要通过若干基础设施,如原型、构造函数等

来完成。这些对于非常熟悉JavaScript的程序员来说,也许都是可以完成的任务,但对于新手来说就困难重重了。并且,即使是高

手,一段时间不写相关的代码也很容易遗忘和出错。但TypeScript却提供了标准的机制,将普通程序员熟悉的、C++和C#中常用的

类概念映射到JavaScript中去,这样就大大降低了在JavaScript进行类式操作的难度。由于相关的概念理解起来并不困难,但技术内

容却非常多,所以这里只介绍几点较关键的。

首先,用一句话来概括在TypeScript中class的核心语义:所有的class都是一个立即函数,所有的数据成员都是这个函数实例的属

性,所有的方法都是这个函数原型的属性,所

有的静态成员都是这个函数的构造函数的属性。各就各位,不会出错。只要看一下class生成的JavaScript目标代码就很明了,假设

有个根据工龄计算工资的Human类定义如下:

它生成的JavaScript目标代码如下:

值得说明的是,TypeScript支持所谓的“存取器”。采用存取器,可以将函数封装,并且以数据属性的形式暴露出来。例如可以为上述

类增加一个获取工资数额的存取器:

对应的JavaScript代码比较复杂,浏览器需要支持ECMAScript 5才能运行:


代码组织和重构

TypeScript中引入了模块的概念,这类似于C++中的名字空间。它可以把声明、数据、函数和类封装在模块中,并采用export关键字

导出,供模块外部的代码取用。之所以说它和命名空间比较相似,一是因为同名的模块可以自动合并,甚至可以分别存储在多个文

件中;二是因为模块的名字可以分成不同层次,在层次较多时还可以命名简化的别名。但无论模块怎么组织,最终生成的还是标准

的、可直接取用的JavaScript代码。正是靠着模块化、可插拔的结构,TypeScript才得以在维护一个较小的语言核心的前提下,对广

泛使用的库如jQuery、CommonJS和Node.js等提供了完整的支持。由于TypeScript并不是采用字符串匹配的粗糙方式来推导变量和

函数的名字,对TypeScript代码进行命名的重构就如同微软的其他编程语言一样容易。只需要选中要重新命名的实体,并键入新的

名字,而不需要担心名字相同而意义不同的其他实体也被同时重命名了。

小结

TypeScript是现今所有对JavaScript的改进中,唯一完全兼容并作为它的超集存在的解决方案。并且,TypeScript几乎是改进了

JavaScript对象模型的方方面面,本文介绍的只是其中比较重要的一部分技术,还有很多细节还需要读者自己去探索。现在,

TypeScript的最新版本是0.8.1,并且开放了全部的源代码。很有意思的是,TypeScript本身就是用TypeScript实现的,这种递归式的

结构也是编译器大牛们很钟爱的方式之一,因为当年Bjarne Stroustrup也用C++本身来写C++编译器。熟悉TypeScript源码和规范不

仅让我们可以更快地掌握这门新语言,也能够更深入地了解如何利用它来解决一些更复杂的问题,例如如何扩充它来支持一些特定

的浏览器才提供的JavaScript特性等。总而言之,TypeScript可以说是最有前途的JavaScript扩展甚至替代的解决方案之一,有志于

前端技术的朋友们应该尽快地熟悉起来。

猜你喜欢

转载自blog.csdn.net/xiaozhi_2016/article/details/64444590