TypeScript系列4-手册-类

传统的JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件,但这对于OO编程人员来说显得比较笨拙,因为是在类的基础上来继承。从JavaScript标准ECMAScript 6开始可以采用面向对象基于类来构建应用。在TypeScript中开发人员现在就可以使用这些技术,TypeScript可以将它们编译为目前大多数浏览器和平台能允许的普通Javascript代码,可以不用等待下一版本的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‘属性,一个构造函数,和一个‘greet‘方法。

你也许已经注意到了例子中在引用成员时前面的‘this.‘,表示这是一个成员访问。

在最后一行我们利用‘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‘的特性。

这个例子也展示了子类中的方法可重载(override)基类中的方法,’Snake’和’Horse‘子类都各自创建了一个‘move’方法来重载基类’Animal’的‘move’方法,这样每个子类就可以实现特定的功能。

Private/Public修饰符

缺省为Public

你可能注意到了在上例中我们并没有用‘public‘去描述类的每一个成员使其可见。在类似于C#语言中,必须显式地标注‘public‘关键字才能使得类的成员可见。但是在TypeScript中。每个成员缺省就是public。

有时我们希望控制类成员不能被外部看到,就可以将这些成员标记为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系列2-手册-基础类型, http://my.oschina.net/1pei/blog/493181

[5] TypeScript系列3-手册-接口, http://my.oschina.net/1pei/blog/493388

时间: 2024-11-08 19:55:14

TypeScript系列4-手册-类的相关文章

TypeScript系列6-手册-函数

函数 函数是JavaScript中任意应用程序的基本构件块.可以在函数基础上建立抽象层,模拟类,信息隐藏,模块.在TypeScript中,虽然已经有类和模块,但函数函数仍然扮演着如何做事的关键角色.TypeScript还在标准JavaScript 函数基础上增加了一些新的能力,来使得函数更容易使用. 函数 TypeScript与JavaScript一样,都可以创建命名函数,也可以创建匿名函数.这样允许开发人员根据应用程序选择最合适的方式,不论是在一个API中建立一列函数还是建立一个one-off

TypeScript系列3-手册-接口

接口 TypeScript的一个核心原则是类型检测重点放在值的形状(shape),这有时候被称为鸭子类型化(duck typing)或结构子类型化(structural subtyping).在TypeScript中,用接口(interfaces)来命名这些类型,来定义项目内部代码的合约以及与外部代码的契约. 第一个接口 理解interface如何工作,最容易的方式就是先看一个简单例子: function printLabel(labelledObj: {label: string}) {   

Java JUC之Atomic系列12大类实例讲解和原理分解

Java JUC之Atomic系列12大类实例讲解和原理分解 2013-02-21      0个评论       作者:xieyuooo 收藏    我要投稿 在java6以后我们不但接触到了Lock相关的锁,也接触到了很多更加乐观的原子修改操作,也就是在修改时我们只需要保证它的那个瞬间是安全的即可,经过相应的包装后可以再处理对象的并发修改,以及并发中的ABA问题,本文讲述Atomic系列的类的实现以及使用方法,其中包含: 基本类:AtomicInteger.AtomicLong.Atomic

Cisco UCS C系列服务器配置手册之使用WebBIOS配置RDID

Cisco UCS C系列服务器配置手册 重启后在如下图5所示界面时按Ctrl+H进入图6的Raid配置界面. 图5 图6 进入到raid的配置界面,左面导航选择Configuration Wizard,进入图7. 图7 因为第一次做RAID,所以选择New configuration 创建新的raid配置,点击Next,进入图8. 图8 选择Manual Configuration手动安装,然后点击Next进入图9. 图9 将图9左侧的多个硬盘全部加入右侧到Drive Groups中(每选择一

Java基础复习笔记系列 五 常用类

Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String s2 = “hello”:结论:s1 == s2. 字符串常量放在data区. 3. String s3 = new String("hello"); String s4 = new String("hello");结论:s3 != s4.但s3.equals(s4).

【小白的java成长系列】——顶级类Object源码分析

首先来说一下api文档使用,api这个词对有一定开发经验的java编程人员来说是很喜爱的~ java当然也提供了api开发文档,下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 找到下面的: 下载自己喜爱的版本即可,解压,点击~/jdk-7u60-apidocs/api/index.html就可以查看其api了: 跟上网一样一样的,点击相应链接就可以查看其信息了. 进入正题,说说Object这个类: 先

【小白的java成长系列】——String类的深入分析(基于源码)

接着前面面向对象来说吧~今天来说说String类..其实String类也包含很多面向对象的知识的~ 首先来问一个问题:我们在开发过程中,如果要使用一个类的话,就要创建对象,这句话没什么问题吧~在实际开发的时候确实是这样的,只有创建了对象才能真正的去使用一个普通的类,我们一般创建对象,几乎所有的类创建对象都是要通过new关键字来创建的~ 问题就来了..为什么我们的String可以直接写成String str = "abc";这样子呢? 当然String类也可以通过new来创建对象的...

almost最好的Vue + Typescript系列02 项目结构篇

基于vue-cli 3.x,配合typescript的环境构建的新vue项目,跟以前的结构相比,有了一些变化,下面我们来简单的了解一下 基本结构: node_modules: 项目中安装的依赖模块 public: 主页文件index.html && favicon.icon(将以往单独在外部的index.html移到了public文件夹下),index.html我们可以像平时普通的html文件一样引入文件(css,js)和书写基本信息,添加meta标签等. src: 源码文件夹,基本上我们

TypeScript系列1-1.5版本新特性

1. 简介 随着PC端快速向移动端迁移,移动(体验)优先的概念也越来越响.由于ReactJS目前移动端仅仅支持iOS,因此移动端Web开发框架只能选择: AngularJS/Angula2 + Ionic框架 + Cordova.想要学习好Angula2以及阅读其代码, 就必须了解和学习TypeScript,也因此需要学习好ES6以及Web Component.近期将开始学习TypeScript语言. 下面先看看TypeScript语言的发展: 鉴于JavaScript这种脚本语言很难应用于大规