TypeScript手册翻译系列3-类

传统的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’方法,这样每一个子类就能够实现特定的功能。函数

Private/Public修饰符

缺省为Public

你可能注意到了在上例中咱们并无用'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.");
    }
}

理解私有(private)

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'是不相同的,所以它们是不兼容的类型。

参数属性(Parameter properties)

能够经过关键字public’和’private建立快捷参数属性方式,来建立并初始化类成员字段。参数属性可让咱们仅用一步就能够建立和初始化类成员。下例是上例中咱们去掉了‘theName’,在构造函数中使用‘private name: string’参数,来建立'name'成员的同时初始化这个字段。

class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

这里利用'private'为参数属性类建立了一个私有成员并初始化其值,对于public也相似。

访问器(Accessors)

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。

静态属性(Static Properties)

到这里,咱们只是讨论了实例化类的成员,当实例化时就能够经过对象来访问成员。咱们也能够建立类的静态成员,是经过类来访问而不是经过实例化对象来访问。在下面这个例子中,咱们对原点('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

相关文章
相关标签/搜索