一.类成员
TypeScript里的类的定义与ES6 Class规范一致,静态属性,实例属性,访问器等都支持:
class Grid {
    static origin = {x: 0, y: 0};
}
class Employee {
    private _fullName: string;
    get fullName(): string {
        return this._fullName;
    }
    set fullName(newName: string) {
        this._fullName = newName;
    }
}
但需要注意2点:
- ES3不支持getter/setter,因此要求编译配置为ES5+ 
- 只有getter没有setter的属性会被自动推断为 - readonly(成员修饰符之一)
二.成员修饰符
访问控制修饰符
支持3个访问控制修饰符:
- public:类的成员属性/方法默认都是 - public,没有访问限制
- private:无法在该类声明的外部访问其成员(如无法通过 - this.xxx访问私有成员)
- protected:与 - private类似,但在派生类中也可以访问受保护成员
例如:
class Animal {
    // 私有成员属性
    private name: string;
    // 受保护成员属性
    protected ancestor: string;
    // 不写的话,默认public
    constructor(theName: string) { this.name = theName; }
    // public成员方法
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}
注意,这些访问控制都只是编译时的限制,运行时并不做强检查。符合TypeScript的设计原则:
不给编译产物增加运行时开销
另外,类成员可访问性也是类型检查的一部分,private/protected修饰符会打破鸭子类型,例如:
class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
}
class Person {
    name: string;
    constructor(theName: string) { this.name = theName; }
}
// 正确:鸭子类型(长得像就是)
let person: Animal = new Person('Sam');
class Plant {
    // 私有成员name
    private name: string;
    constructor(theName: string) { this.name = theName; }
}
// 错误:name属性可访问性不匹配(长得像也不行,可访问性不一样)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');
P.S.特殊的,protected constructor表示该类不允许直接实例化,但支持继承
readonly修饰符
可以通过readonly修饰符声明属性只读,只能在声明时或构造函数里赋值,例如:
class Octopus {
    readonly name: string;
    public readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.
P.S.当然,readonly与访问控制修饰符并不冲突,可以作用于同一个属性
参数属性
对于在构造函数里初始化的属性:
class Octopus {
    readonly name: string;
    constructor (theName: string) {
        this.name = theName;
    }
}
有一种简写方式:
class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {
    }
}
其中,name叫参数属性,通过给构造函数的形参名前添上private/protected/public/readonly修饰符来声明
三.继承
class A extends B {
    //...
}
类似于Babel转换(清晰起见,仅摘录关键部分):
function _inherits(subClass, superClass) {
  // 继承父类原型属性
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  // 继承父类静态属性
  if (superClass)
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subClass, superClass)
      : (subClass.__proto__ = superClass);
}
// 子类构造函数中继承父类实例属性
function A() {
    // 通过父类构造函数给子类实例this添上父类实例属性
    return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}
TypeScript里的Class继承也会被编译替换成基于原型的继承,如下:
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        // 继承父类静态属性
        extendStatics(d, b);
        function __() { this.constructor = d; }
        // 继承父类原型属性,及实例属性
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
// __extends(A, B);
二者大同小异,从实现上看,TypeScript编译产物更健壮,因为其目标是:
在任何支持 ES3+的宿主环境中运行
P.S.比较有意思的是静态属性的继承,具体见一.如何继承静态属性?
四.抽象类
TypeScript里也有抽象类的概念:
abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}
抽象类里可以有带实现的具体方法(如move),也可以有只声明不实现的抽象方法(如makeSound),但要求子类必须实现这些方法:
class Cat extends Animal {
    makeSound() {
        console.log('meow meow meow');
    }
}
另一个相似的概念是接口,二者区别在于接口中只能定义“抽象方法”(没有abstract关键字,确切地说是方法签名),例如:
interface Animal {
  // 对比 abstract makeSound(): void;
  makeSound(): void;
}
// 对比 class Cat extends Animal
class Cat implements Animal {
    makeSound() {
        console.log('meow meow meow');
    }
}
五.类与类型
声明一个类的同时,也声明了该类实例的类型,例如:
class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
let greeter: Greeter;
greeter = new Greeter("world");
其中,实例greeter是Greetr类型的,也就是说,Class声明具有类型含义:
- 该类实例的类型: - Greeter
- 类自身的类型: - typeof Greeter
实际上,类自身的类型约束了静态属性、实例属性、构造函数、原型方法等特征,例如:
class GreeterDuck {
    // 类自身的类型约束
    static standardGreeting = 'Hi';
    greeting: string;
    constructor(message: string) { }
    greet() { return 'there'; }
    // 允许多不允许少(鸭子类型)
    sayHello() { /*...*/ }
}
let greeterType: typeof Greeter = GreeterDuck;
更进一步的:
// 从Class类型扩展出Interface
interface HelloGreeter extends Greeter {
    sayHello(): void;
}
let hGreeter: HelloGreeter = new GreeterDuck('world');
没错,因为类具有类型含义,所以接口能够继承自类