传统的JavaScript注重用函数和基于原型的继承来建立可复用的组件,但这可能让用习惯面对对象方式的程序员感到棘手,由于他们的继承和建立对象都是由类而来的。从JavaScript的下一个版本,ECMAScript 6开始,JavaScript程序员就可以用基于这种基于类的面对对象方式来建立编写本身的程序了。在TypeScript中,不须要再等JavaScript的下一个版本就已经支持开发者使用这一技术了。程序员
类
让咱们来看一个简单的基于类的例子:编程
class Greeter { greeting: string; constructor(message: string) {this.greeting = message; } greet() {return "Hello, " + this.greeting; } }var greeter = new Greeter("world");
若是你以前有使用过C#或者Java,会以为语法很是类似。咱们声明一个新的类"Greeter"。这个类里面有三个成员,一个名为"greeting"的属性,一个constructor和一个"greet"方法。
你会注意到,在类里面当某一个成员使用了"this",意味着他访问的是这个类的成员。
在最后一行中,咱们使用"new"来为Greeter类构造一个实例。这将会调用以前定义的构造函数,而且建立一个新的Greeter类型的对象,而且执行构造函数来初始化这个对象。函数
继承
在TypeScript中,咱们可使用常见的面向对象模式。固然,在基于类的编程中最基本的模式之一就是可以建立一个新的类,这个新的类继承已有的类,并对已有的类作扩展。
来看一个例子:this
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"这个父类,而且对其特性进行了扩展。在这里,咱们使用"extends"关键字来建立一个子类。你能够看到,这里"Horse"和"Snake"两个子类都基于"Animal"这个基类而且获取其特性。
例子也提现出在子类中能够重写基类中的方法以达到重写后的方法是在这个子类中专用。这里的"Horse"和"Snake"都建立了"move"这个方法,这样就重写了从基类继承过来的move方法,而且在不一样类中给"move"不一样的方法。spa
公有和私有的修饰符
默认是public(公有)
你可能已经注意到了,在上面的例子中,咱们并未对类的任何可见成员使用"public"关键字进行修饰。相似C#语言,须要给成员使用"public"修饰符用来明确它是可见。在TypeScript中,每一个成员默认是"public"的。
你还能够给成员标记上"private",这样你就能够控制在你的类以外哪些成员是可见。咱们能够像这样重写上一节的"Animal"类:prototype
class Animal { private name:string; constructor(theName: string) { this.name = theName; } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
理解Private(私有)
TypeScript是个构造类型的系统。当咱们对两个类型进行比较的时候,不管它们是从哪里来,若是全部成员的类型都是兼容的,那么咱们能够认为他们的类型也是兼容的。
当咱们比较的类型中含有"private"(私有)成员,则咱们就须要不一样的对待了。两个类型(假如是A和B)被认为是兼容的,若是A类型含有一个私有成员,那么B类型就必须也有一个私有成员而且与A类型的私有成员源自同一处声明。
让咱们用一个例子来更好的看看私有成员在实践中如何运用:3d
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; // 错误: Animal 和 Employee 不兼容
在这个例子中,咱们有一个"Animal"和一个"Rhino","Rhino"是"Animal"的一个子类。咱们还有一个新的类"Employee",它看上去跟"Animal"类是彻底相同的。咱们给这些类分别建立实例,而且对他们进行相互赋值,看下将会发生什么。由于"animal"和"rhino"的私有成员都是从"Animal"类定义的"private name: string"共享而来的,因此他们是兼容的。然而,"employee"的状况却不是这样的。当咱们试图将"employee"赋值给"animal",咱们获得了一个错误,他们的类型是不兼容的。尽管"Employee"也有一个名称是"name"的私有成员,但它和在"Animal"中的私有成员"name"仍是不相同的。code
参数属性
关键字"public"和"private"经过建立参数属性的方式给咱们提供了建立和初始化类的成员的便捷方式。这个特性让你能够一个步骤就建立和初始化成员。这里有一个以前例子的进一步修改。注意咱们是如何在constructor中将"name"使用"private name: string"的便捷方式完整的建立并初始化成这个类的私有成员"name"的。对象
class Animal { constructor(private name: string) { } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }var goat = new Animal("Goat"); goat.move(25); // Goat moved 25 m.
经过这种方式使用"private"来建立和初始化私有成员,"public"也同样。继承
访问器
TypeScript提供 getters/setters 的方式来拦截对于对象成员的访问。它让咱们能够更精确的控制如何对对象成员的进行访问。
让咱们来将一个类改写成用"get"和"set"。首先,咱们从一个没有"get"和"set"的例子开始:
class Employee { fullName: string; }var employee = new Employee(); employee.fullName = "Bob Smith";if (employee.fullName) { alert(employee.fullName); }
以上代码容许咱们随意设置fullName,可能咱们会以为这样比较直接和方便,但这么为所欲为的改变名字也可能会致使问题。
在这个版本中,咱们将给被容许修改员工信息的用户一个可用的密码。在对fullName进行"set"访问的以前,咱们会以检查密码来代替容许直接修改。咱们添加一个相应的"get"让以前的例子依然能实现。
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); }
为了证实如今访问须要密码,咱们能够修改密码,而后咱们会发现,当密码不符合的时候会弹出提示"Error: Unauthorized update of employee!"(错误:没有修改employee的权限)。
注意:访问器须要咱们将文件以ECMAScript5编程输出。
tsc --target ES5 your.ts
静态属性
到此为止,咱们值谈到类的实例成员,那些只有实例化后才初始化而且显示的成员。咱们还能够为类的建立静态成员,那些在类自己可见而非实在实例上可见。在这个例子中,咱们使用"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 规模var grid2 = new Grid(5.0); // 5x 规模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"的新变量。这个变量保存了这个类,换种说法即保存了这个构造函数。这里咱们使用"typeof Greeter",这么作的话"greeterMaker"的类型就成了"Greeter"类的类型,而非"Greeter"的实例的类型("Greeter"类的实例类型为"Greeter")。更准确的说,"给我Greeter类的类型",也就是构造函数的类型。这个类包含"Greeter"类的全部静态成员和建立"Greeter"类的实例的构造函数。同以前的例子同样,咱们对"greeterMaker"使用"new",用来建立"Greeter"的实例而且触发。
将类看成接口同样使用
正如咱们在上一节所说的,声明一个类的同时会建立其余两个东西:这个类的实例类型和一个构造函数。由于类可以建立类型,因此在使用interface(接口)的地方均可以使用class(类)。
class Point { x: number; y: number; } interface Point3d extends Point { z: number; }var point3d: Point3d = {x: 1, y: 2, z: 3};