传统的JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件,但这对于OO编程人员来讲显得比较笨拙,由于是在类的基础上来继承。从JavaScript标准ECMAScript 6开始能够采用面向对象基于类来构建应用。在TypeScript中开发人员如今就可使用这些技术,TypeScript能够将它们编译为目前大多数浏览器和平台能容许的普通Javascript代码,能够不用等待下一版本的JavaScript的到来。java
咱们先看一个基于类的简单例子:
typescript
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } var greeter = new Greeter("world");
这种语法和c#或java语言中的语法很类似。这里咱们声明了一个'Greeter'类,这个类有三个成员:一个'greeting'属性,一个构造函数,和一个'greet'方法。
编程
你也许已经注意到了例子中在引用成员时前面的'this.',表示这是一个成员访问。
c#
在最后一行咱们利用‘new’关键字建立了一个'Greeter'类的实例,这会用'Greeter' shape新建一个对象,并调用咱们先前定义的构造函数来初始化此对象。
设计模式
在TypeScript中咱们可使用咱们经常使用的OO设计模式。固然在基于类的编程中,最基本的一个模式是能够经过继承来扩展存在的类,建立出新的类。可看下面的例子:浏览器
class Animal { name:string; constructor(theName: string) { this.name = theName; } move(meters: number = 0) { alert(this.name + " moved " + meters + "m."); } } class Snake extends Animal { constructor(name: string) { super(name); } move(meters = 5) { alert("Slithering..."); super.move(meters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(meters = 45) { alert("Galloping..."); super.move(meters); } } var sam = new Snake("Sammy the Python"); var tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
这个例子展现的TypeScript中一些继承特性在其余语言中也能够看到。这里看到用 'extends' 关键字来建立一个子类。'Horse'和'Snake'子类都继承自基类'Animal', 能够访问'Animal'的特性。 ide
这个例子也展现了子类中的方法可重载(override)基类中的方法,’Snake’和’Horse'子类都各自建立了一个‘move’方法来重载基类’Animal’的‘move’方法,这样每一个子类就能够实现特定的功能。函数
你可能注意到了在上例中咱们并无用'public'去描述类的每个成员使其可见。在相似于C#语言中,必须显式地标注'public'关键字才能使得类的成员可见。可是在TypeScript中。每一个成员缺省就是public。this
有时咱们但愿控制类成员不能被外部看到,就能够将这些成员标记为private。下面代码中咱们但愿隐藏上一章节中'Animal'类的name属性:
编码
class Animal { private name:string; constructor(theName: string) { this.name = theName; } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
TypeScript是一个结构化的类型系统。当比较两个不一样类型,不关心它们来自哪里,若是每一个成员的类型都是兼容的,那么就认为这两个类型也是兼容的。
当比较有'private'成员的类型时,就须要另外处理。当比较两个类型时,若是一个类型拥有私有成员,那么另一个类型必须包含源于同一个声明的私有成员,才认为这两个类型是兼容的。
可参见下面例子来讲明:
class Animal { private name:string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name:string; constructor(theName: string) { this.name = theName; } } var animal = new Animal("Goat"); var rhino = new Rhino(); var employee = new Employee("Bob"); animal = rhino; animal = employee; //error: Animal and Employee are not compatible
上面的例子中有'Animal'和'Rhino'两个类,'Rhino'是'Animal'的一个子类。同时咱们也定义了一个 'Employee'类,它和'Animal'类从形状(shape)上看彻底相同。咱们建立了这三个类的实例,并相互赋值看看会发生什么。由于'Animal'和'Rhino'共享'Animal'中相同的私有访问声明'private name: string',所以它们是兼容的。可是当咱们将'Employee'赋值给'Animal'时,获得类型不兼容错误。虽然'Employee'也有一个私有成员'name',但它与 'Animal'中的私有成员'name'是不相同的,所以它们是不兼容的类型。
能够经过关键字public’和’private建立快捷参数属性方式,来建立并初始化类成员字段。参数属性可让咱们仅用一步就能够建立和初始化类成员。下例是上例中咱们去掉了‘theName’,在构造函数中使用‘private name: string’参数,来建立'name'成员的同时初始化这个字段。
class Animal { constructor(private name: string) { } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
这里利用'private'为参数属性类建立了一个私有成员并初始化其值,对于public也相似。
TypeScript支持利用getters/setters来控制对成员的访问,这样能够更细粒度来控制类的成员访问方式。
下面将一个类转化为使用'get'和'set'方式。先从没有getters/setters的例子开始:
class Employee { fullName: string; } var employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
虽然直接设置'fullName'成员很方便,但若是有人随意改变人名可能会形成麻烦。
在下边,咱们但愿将其转化为必须提供一个secret passcode,才能修改employee。经过'set'关键字来代替直接访问fullName成员,相应地增长一个'get'关键字来访问fullName成员:
var passcode = "secret passcode"; class Employee { private _fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { alert("Error: Unauthorized update of employee!"); } } } var employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
为了证实如今的访问器(Accessors)验证了passcode值,能够尝试修改passcode的值,使其不匹配,就会获得没有权限更新employee的告警信息。
注意:访问器须要设置编译输出为ECMAScript 5。
到这里,咱们只是讨论了实例化类的成员,当实例化时就能够经过对象来访问成员。咱们也能够建立类的静态成员,是经过类来访问而不是经过实例化对象来访问。在下面这个例子中,咱们对原点('origin')成员使用’static’ 关键字,由于origin是全部grid的一个通用值。每一个实例经过类名为前缀来访问这个值。相似于在实例访问成员前面用'this.',对静态访问成员前面用类名Grid。
class Grid { static origin = {x: 0, y: 0}; calculateDistanceFromOrigin(point: {x: number; y: number;}) { var xDist = (point.x - Grid.origin.x); var yDist = (point.y - Grid.origin.y); return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; } constructor (public scale: number) { } } var grid1 = new Grid(1.0); // 1x scale var grid2 = new Grid(5.0); // 5x scale alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
当在TypeScript中声明类的时候,实际上同时建立了多个声明。首先是类实例的类型。
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } var greeter: Greeter; greeter = new Greeter("world"); alert(greeter.greet());
在'var greeter: Greeter'这一行,咱们实际上正在用Greeter做为类Greeter实例的类型。这对于其余面向对象语言的编程人员来讲是很天然的方式。
还建立了一个构造函数,这个函数是在用'new'来建立类的实例时调用的。下面看看上一个例子用JavaScript的编码:
var Greeter = (function () { function Greeter(message) { this.greeting = message; } Greeter.prototype.greet = function () { return "Hello, " + this.greeting; }; return Greeter; })(); var greeter; greeter = new Greeter("world"); alert(greeter.greet());
这里'var Greeter'被赋值为构造函数。当调用'new'时调用这个构造函数,获得类的实例。这个构造函数还包含了类的全部静态成员。咱们能够认为每一个类都有实例部分和静态部分。
咱们对上例稍作修改来展现这个差别:
class Greeter { static standardGreeting = "Hello, there"; greeting: string; greet() { if (this.greeting) { return "Hello, " + this.greeting; } else { return Greeter.standardGreeting; } } } var greeter1: Greeter; greeter1 = new Greeter(); alert(greeter1.greet()); var greeterMaker: typeof Greeter = Greeter; greeterMaker.standardGreeting = "Hey there!"; var greeter2:Greeter = new greeterMaker(); alert(greeter2.greet());
这里'greeter1'和前面的例子相似。咱们实例化'Greeter'类,而后调用此对象。这在前面的例子已经见过。
接下来咱们直接使用类。咱们建立了一个新变量'greeterMaker',这个变量保持了Greeter类的类型信息,换句话说是类的构造函数。这里咱们使用'typeof Greeter',它给出Greeter类的类型,而不是实例类型。或者更准确地说,给出符号Greeter的类型就是构造函数的类型。这个类型包含Greeter全部的静态成员,以及建立Greeter类实例的构造函数。咱们能够用'new greeterMaker'来建立'Greeter'的实例,而后调用其方法。
如上所述,类声明建立了两个东西:一个是类实例的类型,一个是构造函数。由于类建立了类型,因此咱们能够将类用在使用接口的地方。
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } var point3d: Point3d = {x: 1, y: 2, z: 3};
[1] http://www.typescriptlang.org/Handbook#classes
[2] TypeScript - Classes, 破狼blog, http://greengerong.com/blog/2014/11/17/typescript-classes/
[3] TypeScript系列1-简介及版本新特性, http://my.oschina.net/1pei/blog/493012
[4] TypeScript手册翻译系列1-基础类型, http://my.oschina.net/1pei/blog/493181
[5] TypeScript手册翻译系列2-接口, http://my.oschina.net/1pei/blog/493388