Typescript中文教程-类

传统JavaScript关注功能和基于原型的继承为基本手段,建立可重用的组件,但这可能会觉得有点尴尬,对程序员来说当熟练使用面向对象的方法来开发时,类所继承的功能和对象是由其创建的。从ECMAScript 6,JavaScript程序员可以使用这种面向对象的基于类的方法构建应用程序。在typescript里,我们现在允许开发人员使用这些技术,无需等待下一个版本的JavaScript在所有主要的浏览器和平台JavaScript和编译它们。

1、类

让我们看看一个简单的基于类的例子:

1
2
3
4
5
6
7
8
9
10
11
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’,这表示,这是一个成员的访问。
在最后一行我们构造一个Greeter的实例使用“new”。这要求我们前面定义的构造函数,创建一个新对象的Greeter类型,并运行构造函数来初始化它。

2、继承

在 TypeScript,我们可以使用常见的面向对象模式。当然,基于类的编程是最基本的。模式之一是能够使用继承扩展现有的类来创建新的

让我们看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);

这个例子中有很多其他语言所包含的继承特性。在这里,我们看到使用‘extends’的关键字来创建一个子类。你可以看到这个“Horse”和“Snake”子类基类‘Animal’,获得其功能。

示例还展示了能够覆盖基类中的方法与专门子类的方法。这里‘Snake’和‘Horse’都创建了一个‘move’方法覆盖了基类‘Animal’的‘move’,赋给每个类特定的功能。

3、私有/公有修改器

默认的公有

你可能注意到上面的例子我们没有使用‘public’关键字使类的每一个成员都可见。类似C#的语言需要明确的关键字‘public’来时每个成员可见。在TypeScript,默认每个成员都是公有的。

你还可以马克私有成员,所以你控制什么是公开可见的类。我们可以写前一节的“Animal”类如下:

1
2
3
4
5
6
7
class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        alert(this.name + ” moved ” + meters + “m.”);
    }
}

 

4、智能化私有

TypeScript是一种结构化类型系统。当我们比较两种不同类型时,无需关注他们从哪里来,如果每个成员的类型相兼容,那么就说他们兼容。

当我们比较私有类型的成员时却有所不同。对于两种兼容的类型来说,如果其中一个是私有成员,那另一个也一定得是起源于相同的声明的私有成员。

让我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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’完全相同。我们创建这些类的一些实例然后把他们互相赋值来看看会发生什么。由于‘Animal’和‘Rhino’都有来自‘Animal’的同一声明的私有属性名字(name):字符串(string),他们是兼容的。然而,对于‘Employee’是不同的。当我们试着将一个‘Employee’赋值给‘Animal’发生了一个类型不兼容的错误。甚至‘Employee’有同样的叫‘name’的私有成员,但这个跟‘Animal’里创建的并不是同一个。

5、参数属性

关键字“public”和“private”也给你一个通过创建参数属性为创建和初始化类的成员的简写。属性让您可以创建并初始化一个成员在同一步骤里。这里对前面的例子进一步修订。注意我们完全放弃“theName”,构造器里创建和初始化’name‘成员时只使用缩短的‘private name:string‘参数。

1
2
3
4
5
6
class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + ” moved ” + meters + “m.”);
    }
}

以这种方式使用“private”创建并初始化一个私有成员,公有成员也同理。

 6、存取标记

TypeScript支持getter / setter的拦截访问一个对象的成员。这将给你一种更精细的成员访问对象的方式。

让我们转换一个简单的类使用’get‘和’set‘的类。首先,我们从一个不用获取和设置的例子开始。

1
2
3
4
5
6
7
8
9
class Employee {
    fullName: string;
}
var employee = new Employee();
employee.fullName = “Bob Smith”;
if (employee.fullName) {
    alert(employee.fullName);
}

虽然允许人们直接改变fullName十分方便,但是如果人们可以随意改变名字这可能给我们带来麻烦。

在这种情况下,我们在修改Employee之前确保有个可用的密码。将直接进入fullName替换成检查密码的’set‘。我们添加一个相应的“get”让前面的示例无缝地继续工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}

现在我们的访问器检查密码来验证我们自己,我们可以修改密码来看看当密码不匹配时警告框将告诉我们没有更新Employee的权限。

7、静态属性

到目前为止,我们只讨论了类的实例成员。当对象实例化的时候将会出现。我们也能建立类的静态static成员。将在类本身出现而不是类的实例里。在这个例子里,我们在’origin‘上使用’static‘,作为girds的通用值。每个实例通过类里预先设置好的名字来访问。跟’this‘类似,在静态实例访问处我们使用’Grid‘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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}));

 

8、进阶技术

8.1构造函数

当你在TypeScript声明一个类,你实际上是同时创建多个声明。第一个是类的实例的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的类型实例的类。这几乎是其他面向对象语言的程序员的第二天性。

我们还创建另一个值,我们称之为constructor function当我们’new‘类的实例时的函数调用。在实践中看看这是什么样子,让我们看一下上面的例子创建的JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
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’操作符并运行该函数时,我们得到一个类的实例。构造函数也包含类的所有静态成员。另一种认识每个类的方法是其由实例instance和静态static两方面组成。

下面我们修改一下这个例子来看看这有什么不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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());

这个例子里,‘greeter’的工作方式和前面相同。我们实例化‘Greeter’类,然后使用这个对象。这在前面我们见过。

下一步,我们直接使用类。这里我们建一个变量叫做‘greeterMaker’。这个变量将会保存这个类自己,或者说这是另一种构造函数。这里我们使用‘typeof Greeter’,意思是‘把Greeter类他自己的类型给我’而不是实例化这个类型。或者更精确的说是“把符号Greeter的类型给我”,即构造函数的类型。这将包含创建Greeter类实例的构造函数的所有静态成员.。跟前面调用一样,我们通过使用‘new’操作符在‘greeterMaker’前面来创建新的‘Greeter’实例。

8.2使用一个类作为一个接口

正如我们在前一节中说,类声明创建两件事:一种代表类的实例和构造函数。因为类创建类型,你可以在使用接口的地方使用它:

1
2
3
4
5
6
7
8
9
10
class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
var point3d: Point3d = {x: 1, y: 2, z: 3};

发表评论