Learining TypeScript (一) TypeScript 简介
一、TypeScript出现的背景
在过去的几年里,基于JavaScript的Web应用的数量呈几何级数增长,虽然目前ES6(即ECMAScript2015,从ES6开始采用年份命名)的标准在2015年6月份已经发布了,增添了许多新的特性,例如:模块和类,以及一些实用的特性,例如Map,Sets,Generates,Promises等等,支持向后兼容,所有的老代码也可以正常运行,但目前为止还没有一个完全支持ES6标准的JavaScript代理,不论是浏览器还是服务器环境,所以开发者不得不将ES6代码向ES5转义,所以ES6标准的广泛应用,还需要一个漫长的过程。
为了解决JavaScript的维护和扩展问题,微软花了两年的时间开发出TypeScript,并在2012年10月公布了第一个公开的版本。它是JavaScript的一个超集,它与现存的JavaScript代码有非常高的兼容性,任何合法的JavaScript程序都是合法的TypeScript程序,另外它还给大型项目提供一个构建机制,TypeScript(以下简称ts)中加入了基于类(Class)的对象、接口和模块,采用面向对象编程的方式。至此ts就诞生了。
二、TypeScript的架构
学习的过程就是这样,当你学习一门新的东西的时候,总是要从基础知识开始学起,这样有利于你以后的学习,还有个作用是,给你以后的面试提供很大的帮助。
1、设计目标
我们可以从以下几点中了解到,决定TypeScript作为程序语言发展到今天这个形态的设计目标和架构:
- 对JavaScript结构的静态分析很有可能死错误的。微软(以下简称MS)的工程师们认为为了防止并排查一些运行时错误的最佳方式是,创造一种在编译期间进行静态类型分析的强类型语言,因此他们设计了一个语言服务层给开发者提供一些更好的工具。
- 与现存的JavaScript代码有非常高的兼容性。TypeScript作为JavaScript的超集,所有合法的JavaScript程序代码都是合法的TypeScript代码。
- 给大型项目提供一个构建机制。引入了类的对象、接口和模块,结合最佳的面向对象的编程,使代码更具可维护性和扩展性。
- 对于发行版本的代码,没有任何运行时开销。在使用TypeScript时,通常是将程序设计阶段和运行阶段区分开来的,通俗的讲就是说我们在设计程序时使用TypeScript代码,而运行时呢编译成JavaScript代码。为何要这样呢?原因很简单,比如在ts中有接口的概念,在js中是不存在也不支持接口的,那么ts编译器在编译出的js代码中也就不会声明也不会模拟这个特性。MS的Engineers提供了一些类似于代码转换(将TypeScript特性转变为JavaScript代码实现)和类型擦除(移除静态类型标记)的组件给ts的编译器,来产生纯净的JavaScript代码,类型擦除组件不仅移除了类型的标注,还移除了所有ts代码的所有的高级语言特性,比如接口等。
ts在编译时默认使用ES3标准进行编译,极大的提高了浏览器的兼容性,当然它也支持ES5和ES6的标准。一般情况下使用任意受支持的编译目标,我们都可以使用ts特性,但部分特性需要ES5或更高的版本版本作为编译目标的先决条件。
- 遵循当前以及未来出现的ECMAScript规范。ts不仅支持现有的js代码,而且也拥有兼容未来版本的能力,大多数的ts的新增特性都是基于未来的JavaScript提案,这样也意味着许多ts代码在将来甚至会变成合法的JavaScript代码。
- 成为跨平台的开发工具。MS使用Apache协议开源了TypeScript,并且它可以在所有的主流操作系统上安装和执行。
2、TypeScript组件
TypeScript语言内部主要被分为三层,每一层又被依次分为子层或者组件,如下图所示:
每一层都有不同的用途:
- 语言层:实现所有TypeScript的语言特性。
- 编译层:执行编译、类型检查,然后将TypeScript代码转换成JavaScript代码。
- 语言服务层:生成信息以帮助编辑器和其他工具来提供更好的辅助特性,比如IntelliSense(智能感知),这是微软提供的一种代码检查与提示服务么可以在编码时提供相关信息。
- IDE整合:为了利用TypeScript的特性,IDE开发者需要完成一些集成工作。
三、TypeScript语言特性
在介绍TypeScript语言特性之前,首要任务是要搭建开发环境,官方提供了一个在线编辑器http://www.typescriptlang.org/play/ 该编辑器如下图所示:
编辑器左侧编写TypeScript代码,右侧会自动编译成JavaScript代码,当然作为我们专业的programmer来说,很少会采用这种在线编辑的方式进行开发了,可以下载安装TypeScript的编译器,作为一个.Net Developer来说,我们可以时候用Visual Studio,从VS2015开始,自动集成了TypeScript的开发环境,如果是以下版本,需要去官方下载TypeScript插件,当然我们现在推荐你使用VS2017了。除了这些之外,如果想在其他操作系统中开发,OSX和Linux,那我们可以选择一些比较流行的编辑器,他们都会有TypeScript插件,比如Visual Studio Code(以下成为VS Code),Sublime等,这里我推荐VS Code,它较Visual Studio不同在于前者是IDE集成开发环境,而后者定义为Eidtor(编辑器),我们不需要去安装Visual Studio几十个G的安装包,而且VS Code已经提供了成千上万的插件供你使用,你自己也可以去开发自己的插件,如图所示:
微软大大一直以来都是那么的贴心。VS Code提供了各操作系统的版本,可以直接下载使用。
选择好我们的开发编辑器后,就要安装TypeScript,需要用到npm命令,但是在使用npm命令前,需要在开发环境中安装node.js,地址https://nodejs.org,找到对应的版本下载即可。验证是否安装成功,可使用
npm –version 查看npm版本
下面我们可以使用命令安装TypeScript了
npm install -g typescript
(-g)为全局安装,但是如果你是OS X用户,需要使用sudo命令,需要获取管理员权限,
sudo npm install -g typescript
可以使用 tsc -v 命令验证是否安装成功
Ok,至此我们的开发环境就搭建完成了。
现在我们创建一个test.ts文件,保存一个文件夹内,里面输入以下代码:
var t : number = 1;
然后切换到此文件夹内,执行
tsc test.ts
此时可以发现,在同目录下,会编译生成一个test.js文件。如图:
1、类型
我们已经知道,ts是js的超集,ts通过向js增加可选的静态类型声明来把js变成强类型语言,可选的静态类型声明可约束函数,变量,属性等程序实体,这样编译器和相应的开发工具就可以在开发过程中提供更好的正确性验证和辅助功能(比如IntellSense)。还可以让程序员对自己的和其他开发人员的代码表达他的意图,另外ts的类型检测在编译器运行并且没有运行时开销。
比如:
var counter; //未知(any)类型
var counter=0; //number类型(推断出的)
var counter:number; // number类型
var counter:number=0; // number类型
2、变量、基本类型和运算符
这部分简要说明,对ts的基本类型包括boolean、number、string、array、void和所有用户自定义的enum类型。所有的这些类型都是any类型的子类型。来看几种特殊的类型的用法:
- Array 和js一样,ts允许使用数组,array类型的声明有两种写法,
第一种:
var list:number[]=[1,2,3];
第二种是使用范型数组类型Array:
var list:Array<number>=[1,2,3];
- enum 枚举类型,为了更好的给一个数字集合命名,默认从0开始,当然你也可以手动设置成员的值,如:
enum Color{Red,Green,Blue};
var c:Color=Color.Red;
- any类型可以表示任意的JavaScript的值。
- void类型在某种程度上是any类型的对立面,即所有类型不存在的时候,你会在一个没有返回值得函数中看到他:
function warnUser:void{
alert("This is my warning message!");
}
- null 和undefined 。在JavaScript中undefined是全局作用域的一个属性,它会赋值给那些已被声明但是未被初始化的变量;null是一个字面量(不是全局对象的一个属性),他可以被赋值给那些表示没有值的变量。例如:
var TestStr; //变量声明但未初始化
alert(TestStr); //显示undefined
alert(typeof TestStr); //显示undefined
var TestStr=null; //变量声明,并且被赋值为null
alert(TestStr); //显示null
alert(typeof TestStr); //显示object
然而在TypeScript中,null和undefined不能当作类型来使用:
var TestStr:null; //错误,类型错误
var TestStr:undefined; //错误,找不到undefined
- var、let和const
在TypeScript中,当声明一个变量时,可以使用var 、let和const关键字:
var myNum:number=-1;
let isValid:Boolean=true;
const appid:string="0FDFD-8989-4CC3-9080-CSDJJFID7HJFD9";
var 声明的变量保存在最近的函数作用域中,如果不存在任何函数中则在全局作用域中。
let声明的变量保存在最近的比函数作用域小的块作用域中,如果不存在则在全局作用域中。
const关键字会创建一个保存在创建位置作用域中的常量,可以是全局作用域也可以是块作用域,这表明const是块作用的,关于作用域的知识会在后面进一步了解。
- 联合类型
TypeScript允许联合声明:
var path:string[]|sring;
path='/temp/log.xml';
path=['/temp/log.xml', '/temp/info.xml'];
path=1;
此时path=1;会报类型错误,因为在声明时并未对path进行number的合法声明。
- 类型守护
可以在运行时使用typeof或者instanceof运算符对类型进行验证。TypeScript语言服务会在if区域寻找这些运算符,然后对应地更改类型:
var x: any = { /* ... */ };
if(typeof x === 'string') {
console.log(x.splice(3, 1)); // 错误,'string'上不存在'splice'方法
}
// x 依然是 any 类型
x.foo(); // 合法
在这段代码中,我们首先声明了一个any类型的变量x,随后在运行时通过typeof运算符对x进行了类型检查。如果x的类型为string时,我们就会尝试调用被认为是x的一个成员的splice方法。TypeScript语言服务可以读懂在条件语句中使用typeof的用法。TypeScript会自动推断出x一定是string类型,然后告诉我们splice方法不存于string类型上。这种特性被称为类型守护。
- 类型别名
TypeScript允许使用type关键字声明类型别名:
type PrimitiveArray = Array<string|number|boolean>;
type MyNumber = number;
type NgScope = ng.IScope;
type Callback = () => void;
但是不建议在一个大的项目团队中使用这种别名,可维护性会降低。
- 环境声明
环境声明允许在TypeScript 代码中创建一个不会被编译到 JavaScript中的变量。这个特性是用来促进与现有 JavaScript 代码、DOM(文档对象模型),还有BOM(浏览器对象模型)结合而设计的。让我们看一个例子:
customConsole.log("A log entry!"); // 错误
如果你尝试调用customConsole对象上的log方法,TypeScript会告诉我们customConsole对象未被声明:
// Cannot find name 'customConsole'
出现这种情况并不令人意外。但是,有时候我们希望调用一个未被定义的对象上的方法,比如window对象上的console方法。
console.log("Log Entry!");
var host = window.location.hostname;
当访问 DOM 或 BOM 对象时,我们没有遇到错误,是因为这些对象已经在一个特殊的 TypeScript 文件(被称为声明文件)中被声明了。可以使用declare操作符创建一个环境声明。
在下面这段代码中,我们会声明一个被customConsole对象实现的接口。然后使用declare操作符在作用域中增加一个customConsole对象:
interface ICustomConsole {
log(arg : string) : void;
}
declare var customConsole : ICustomConsole;
然后就可以在没有编译错误的情况下使用customConsole:
customConsole.log("A log entry!"); // 成功
TypeScript 默认包含一个名为lib.d.ts的文件,它提供了像 DOM 这种 JavaScript 内置库的接口声明。
使用.d.ts结尾的声明文件,是用来提高 TypeScript 对第三方库和像 Node.js 或浏览器这种运行时环境的兼容性的。
- 运算符
运算符包括:算术运算符、比较运算符、逻辑运算符、位运算符、以及赋值操作符。
这些运算符在这里不再赘述,如果有开发的基础的话,这些应该都会清楚。
3、流程控制语句
包含选择语句、循环语句和分支语句
if 、if else 、?、switch case 、while 、do while 、for in(类似freach)、for循环,这些语句是我们再熟悉不过的东东了,也不再赘述了吧。
4、函数
关于函数,就像JavaScript一样,TypeScript可以通过具名或匿名方式创建,根据具体情况,选择合适的方式声明即可。如:
function greet(name?:string):string{
return "Hi"+name;
}//这是一个带有返回值的具名函数;
//匿名函数
var greet=function(name?:string):string{
return "Hi"+name;
}
如果不想使用函数的语法,还可以有另一种选择,使用箭头(=>)操作符并不适用function关键字,如:
var greet=(name?:string):string=>{
return "Hi"+name;
}
在这个例子中,我们还可以给greet变量添加上匹配匿名函数的类型:
var greet=(name?:string)=>string=function(name:string):string{
return "Hi"+name;
}
*注意:当处于类的内部时,如果使用箭头函数(=>)语法将会改变this操作符的工作机制,会在后面的学习中详细讨论。
已经学习了如何将一个变量强制描述为指定形式的函数。这在我们使用回调函数时是十分有用的。
function sume(a:number,b:number,callback(result:number)=>void){
callback(a+b);
}
5、类
在ES6中添加了基于类的面向对象编程语法,由于TypeScript是基于ES6的,所以我们可以开始使用基于类的面向对象的语法了。TypeScript的编译器会负责将TypeScript代码编译为兼容主流浏览器和平台的JavaScript代码。
class Person {
fullname: string;
constructor(firstname: string, lastname: string) {
this.fullname = firstname + " " + lastname;
}
hello(name?: string) {
if (name) {
return "Hi! " + name + "! My Name is " + this.fullname;
} else {
return "Hi! My Name is" + this.fullname;
}
}
}
var person = new Person("Allen", "Choi");
var msg = person.hello("Allen");
alert(msg);// "Hi !Allen! My Name is Allen Choi";
var msgnull = person.hello();
alert(msgnull);//"Hi! My Name is Allen Choi"
在上面的例子中,我们定义了一个Person类,有三个成员:一个fullname属性,一个构造函数constructor和一个greet方法。当在TypeScript中声明类时,所有的属性和方法都是公共的。
当在对象内部访问对象成员时,我们都加上了this操作符,这表明这是一个成员访问操作,我们使用new操作符构造了一个Person实例,这会调用类的构造函数,按照定义对实例进行初始化。
为了兼容ES3和ES5,TypeScript中的类会编译为JavaScript中函数
var Person = /** @class */ (function () {
function Person(firstname, lastname) {
this.fullname = firstname + " " + lastname;
}
Person.prototype.hello = function (name) {
if (name) {
return "Hi! " + name + "! My Name is " + this.fullname;
}
else {
return "Hi! My Name is" + this.fullname;
}
};
return Person;
}());
var person = new Person("Allen", "Choi");
var msg = person.hello("Allen");
alert(msg);
var msgnull = person.hello();
alert(msgnull);
6、接口
在TypeScript中,可以使用接口来确保类拥有指定的结构。
interface LoggerInterface {
log(arg: any): void;
}
class Logger implements LoggerInterface {
log(arg) {
if (typeof console.log === "function") {
console.log(arg);
} else {
alert(arg);
}
}
}
在这个例子中,我们定义了一个LoggerInterface接口,和实现了一个Logger的类,Typescript也允许使用接口来约束对象,这样可以帮助我们避免很多小错误。例如下面的例子中:
interface UserInterface {
name: string;
password: string;
}
var user: UserInterface = {
name:"",
pasword:"" //password 遗漏错误属性
}
7、命名空间
命名空间,又称内部模块,被用于组织一些具有某些内在联系的特性和对象。命名空间能够使代码结构更清晰,可以使用namespace和export关键字,在TypeScript中声明命名空间。
namespace Geometry {
interface VertorInterface {
/*...*/
}
export interface Vertor2dInterface {
/*...*/
}
export interface Vertor3dInterface {
/*...*/
}
export class Vertor2d implements VertorInterface, Vertor2dInterface {
/*...*/
}
export class Vertor3d implements VertorInterface, Vertor3dInterface {
/*...*/
}
}
var vector2dInstance: Geometry.Vertor2dInterface = new Geometry.Vertor2d();
var vector3dInstance: Geometry.Vertor3dInterface = new Geometry.Vertor3d();
在上面例子中,我们声明了一个包含了Vector2d、Vector3d类和VectorInterface、Vector2dInterface、Vector3dInterface接口的命名空间。
*注意:命名空间内的VectorInterface并没有export关键字,所以在命名空间外部,我们访问不到它。
8、综合运用
下面是一个使用了模块、类、函数和类型注解的案例:
module Geometry{
export interface Vector2dInterface{
toArray(callback:(x:number[])=>void):void;
length():number;
normalize();
}
export class Vector2d implements Vector2dInterface{
private _x:number;
private _y:number;
constructor(x:number,y:number){
this._x=x;
this._y=y;
}
toArray(callback:(x:number[])=>void):void{
callback([this._x,this._y]);
}
length():number{
return Math.sqrt(this._x*this._x+this._y*this._y);
}
normalize(){
var len=1/this.length();
this._x*=len;
this._y*=len;
}
}
}
var vector:Geometry.Vector2dInterface=new Geometry.Vector2d(2,3);
vector.normalize();
vector.toArray(function(vectorasArray:number[]){
alert('x:'+vectorasArray[0]+'y:'+vectorasArray[1]);
})
四、小结
这一部分的内容主要是介绍了一些关于TypeScript的基础知识,相信有开发经验的人对这些并不感冒,不管你是用了两分钟看完了,还是用了20分钟看完了,希望你能有一点点的收获,内容有书本上的内容,也有网上摘的,如有不对的地方,请多包涵指正。从头看到尾,或许你越来越感觉它的语法以及一些特性,像极了C#,对于.Net 开发人员来说应该是再熟悉不过了。后面的内容会持续更新,也希望自己能坚持下来吧。