传统的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中,我们可以使用常见的面向对象模式。当然,在基于类的编程中最基本的模式之一就是能够创建一个新的类,这个新的类继承已有的类,并对已有的类做扩展。
来看一个例子:

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"不同的方法。

公有和私有的修饰符

默认是public(公有)

你可能已经注意到了,在上面的例子中,我们并未对类的任何可见成员使用"public"关键字进行修饰。类似C#语言,需要给成员使用"public"修饰符用来明确它是可见。在TypeScript中,每个成员默认是"public"的。

你还可以给成员标记上"private",这样你就可以控制在你的类之外哪些成员是可见。我们可以像这样重写上一节的"Animal"类:

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类型的私有成员源自同一处声明。

让我们用一个例子来更好的看看私有成员在实践中如何运用:

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"还是不相同的。

参数属性

关键字"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};

TypeScript Class(类)的更多相关文章

  1. TypeScript 素描 - 类

    本文虽然是学自官方教程而来,但是也融入了自己的理解,而且对官方的例子做了一些修改 /* 类 面向对象编程的一大核心 使用C#.Java进行编程的朋友肯定已经是不能够再熟悉了 TypeScript的类与 ...

  2. TypeScript入门四:TypeScript的类(class)

    TypeScript类的基本使用(修饰符) TypeScript类的抽象类(abstract) TypeScript类的高级技巧 一.TypeScript类的基本使用(修饰符) TypeScript的 ...

  3. TypeScript入门-类

    ▓▓▓▓▓▓ 大致介绍 在ECMASript6中引入了类这一概念,通过class声明一个类.对于学习过C和C++的人应该不会陌生 ▓▓▓▓▓▓ 类 看一个简单的类: class Greeter { g ...

  4. Typescript 学习 - 类

    class class 并不是一种新的数据结构,只是在函数原型基础上的语法糖 class People { hand: number; constructor(hand: number) { this ...

  5. typescript - 4.es5与typescript的类与继承

    ES5中的类与类的继承 (1)简单的类 function Person() { this.name = '张三'; this.age = 20; } var p = new Person(); ale ...

  6. Angular2入门:TypeScript的类 - 参数属性:定义和初始化类成员

  7. Angular2入门:TypeScript的类 - 定义、继承和作用域

    一.定义和继承 二.public.private和protected

  8. typescript实现类规则

    备注: 单独的 index.d.ts对于代码实现没有约束性,将约束和实现写在一个页面里有约束性,或者使用如下: // clock.interface.ts export interface Clock ...

  9. 从C#到TypeScript - 类

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

随机推荐

  1. lecture11-hopfiled网络与玻尔兹曼机

    Hinton课程第11课 这部分的课程算是个知识背景,讲述RBM的来源吧,毕竟是按照hopfield--BM-RBM的路线过来的. 因为水平有限,都是直译,如果纠结某句话,肯定看不懂,所以这些课程只需 ...

  2. ASP.NET mvc异常处理的方法

    第一种:全局异常处理 1.首先常见保存异常的类(就是将异常信息写入到文件中去) public class LogManager { private string logFilePath = strin ...

  3. 数据库系统原理——ER模型与关系模型

    原文链接: http://blog.csdn.net/haovip123/article/details/21614887 犹记得第一次看<数据库系统原理>时看天书的感觉,云里雾里:现在已 ...

  4. 弱占优策略--Weakly Dominant Strategy

    Weakly Dominant Strategy Equilibrium(均衡). 先说弱占优.一个策略弱占优就是说,无论其他人采取什么样的策略,这个策略的回报都大于等于其他策略的回报.如果所有人都使 ...

  5. Service之来电监听(失败的案例)

    Service:服务,可以理解成一个运行再后台没有界面的Activity,集成于Seriver,是四大组件之一 Service的继承关系:Service-->ContextWrapper--&g ...

  6. MVC 中的 ispostback

    总之呢就是在MVC中试下 ispostback那种效果, 环境就是:登录验证loinger, if (Request.HttpMethod == "POST"){} 没理解透彻 源 ...

  7. 63-w 简明笔记

    显示关于系统用户的信息 w [options] [username] w用于显示当前登录系统的用户的名字以及他们的终端设备编号.登录时间.正在运行的命令和其他一些信息 参数 username 限定仅显 ...

  8. C#开发命名规范

    学习C#之初,始终不知道怎么命名比较好,很多时候无从命名,终于有一天我整理了一份命名规范文档,自此我就是按照这个命名规范书写代码,整洁度无可言表,拙劣之处请大家斧正,愚某虚心接受,如有雷同,不胜荣幸 ...

  9. linux 常用命令总结

    PS命令: 1.命令格式: ps[参数] 2.命令功能: 用来显示当前进程的状态 3.命令参数: a  显示所有进程 -a 显示同一终端下的所有程序 -A 显示所有进程 c  显示进程的真实名称 -N ...

  10. 十天冲刺---Day7

    站立式会议 站立式会议内容总结: 燃尽图 照片 两个人编码其实效率挺高的.但是在一些方面,比如说页面UI的编写,会非常吃力,很难达到自己的效果. 由于埋头在编码,所以issues的增加随之停止. 有点 ...