Ts中的Class
TypeScript offers full support for the class
keyword introduced in ES2015.
定义类
class Point {
// 属性默认是公共的,可以在类外部改变
// 应该显式指定类型或可以推断的类型
x:number = 10;
// 如果开启了--strictPropertyInitialization,如果没有初始化,必须在constructor中初始化
y:number;
z = 10
//name: string;
//Property 'name' has no initializer and is not definitely assigned in the constructor.
constructor(){
console.log('Point constructor')
this.y = 10
}
}
const p = new Point();
p.x = 20
console.log(p.x,p.y,p.z)
//输出结果如下:
[LOG]: "Point constructor"
[LOG]: 20, 10, 10
//上述typescript编译为ES6后,如下:
class Point {
constructor() {
this.x = 10;
this.y = 10;
this.z = 10;
console.log('Point constructor');
}
}
const p = new Point();
console.log(p.x, p.y, p.z);
定义类表达式
和ES6中一样,typescript中也支持类表达式:
const Point = class {
x:number = 10;
y:number = 10;
z = 10
constructor(){
console.log('Point constructor')
}
}
const p = new Point();
console.log(p.x,p.y,p.z)
// 编译为ES6如下:
const Point = class {
constructor() {
this.x = 10;
this.y = 10;
this.z = 10;
console.log('Point constructor');
}
};
const p = new Point();
console.log(p.x, p.y, p.z);
断言操作符!
如果一个属性,不希望通过构造函数初始化,可以使用!操作符
class OKGreeter {
// Not initialized, but no error
name!: string;
}
readonly Fields
通过readonly修饰的字段属性,只能在constructor中进行初始化。
class Greeter {
readonly name: string = "world";
constructor() {
this.name = "hello"
}
err() {
// 无法赋值
//this.name = "not ok";
}
}
const g = new Greeter();
// g.name = "also not ok"; //无法赋值
Constructors
类的构造函数和函数很类似,可以添加参数,添加类型,给默认值,或进行重载(overloads)
class Point {
x: number;
y: number;
// Normal signature with defaults
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Point {
// Overloads
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}
:zap:构造函数与函数的区别:
- Constructors can’t have type parameters - these belong on the outer class declaration, which we’ll learn about later
- Constructors can’t have return type annotations - the class instance type is always what’s returned
:100:Super Calls
class Base {
k = 4;
}
class Derived extends Base {
constructor() {
super()
// 如果需要访问this. 必须先调用super()
console.log(this.k);
}
}
new Derived() // 4
methods
类中的函数属性称为方法(method)。
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
Getters / Setters
class C {
private _length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}
const c = new C()
// c._length = 10 // private 修饰的只能在类内部访问
c.length = 100
console.log(c.length) //100
:zap: 注意:
- 只要有get属性,属性就是只读属性(readonly)
- set的参数类型可以省略,根据get的返回类型进行推断
- setter/getter属性的可见性应该一样(public、private、protected)
Index Signatures
:zap: 不是很懂
class MyClass {
[s: string]: boolean | ((s: string) => boolean);
check(s: string) {
return this[s] as boolean;
}
}
implements 与 extends
implements
类可以实现接口,以检查类是否满足特定要求:
interface Pingable {
name: string;
ping(): void;
}
class Sonar implements Pingable {
//必须实现接口中的属性
name: string = 'PingPang';
// constructor(public name:string){}
//必须实现接口中的方法
ping(){
}
}
当然,一个类也可以同时实现多个接口:class C implements A, B {}
需要注意的是:
implementing an interface with an optional property doesn’t create that property:
interface A {
x: number;
y?: number;
}
class C implements A {
x = 0;
}
const c = new C();
//c.y = 10; //error:Property 'y' does not exist on type 'C'.
extends
类(子类/派生类)可以通过extends关键字继承另一个类(基类)。
这样子类就有了基类中所有的属性和方法,同时子类还可以定义自己额外的属性和方法。
class Animal {
move() {
console.log("Moving along!");
}
}
class Dog extends Animal {
woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("woof!");
}
}
}
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);
派生类可以覆写基类的方法和属性,也可以通过super.语法调用基类的==方法==。
class Base {
name = 'BaseName'
greet() {
console.log(`Hello,Base !`);
}
}
class Derived extends Base {
name = 'DerivedName'
greet(name?: string) {
if (name === undefined) {
//可以访问基类的方法
super.greet();
} else {
// 无法访问基类的属性
console.log(`super name -${super.name}`)
console.log(`Hello, ${name}`);
}
}
}
const d = new Derived();
d.greet();
d.greet(d.name);
//输出:
[LOG]: "Hello,Base !"
[LOG]: "super name -undefined"
[LOG]: "Hello, DerivedName"
:zap: It’s important that a derived class follow its base class contract.
即,虽然派生类可以覆写基类的方法,但是应用遵守基类的契约。
class Base {
greet() {
console.log("Hello, base!");
}
}
class Derived extends Base {
// 错误,参数类型错误,Type '(name: string) => void' is not assignable to type '() => void'.
greet(name:string){
console.log("Hello, derived!");
}
}
Initialization Order
The order of class initialization, as defined by JavaScript, is:
- The base class fields are initialized
- The base class constructor runs
- The derived class fields are initialized
- The derived class constructor runs
继承内置类
可以字定义类继承自Error、Array 、Map等内置类。
但是需要手动改下原型链。
成员可见性
public
类的成员默认就是public的,可以在类的外部访问。
class Greeter {
name = "jack"
greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();
console.log(g.name)
protected
仅在类内部、以及派生类中可见。
class Greeter {
protected sayHi() {
return "hi";
}
}
class SpecialGreeter extends Greeter {
say() {
// 子类可以访问sayHi
console.log("Howdy, " + this.sayHi());
}
}
const greater = new Greeter()
const g = new SpecialGreeter();
g.say()
// greater.sayHi() //Error, 内的外部不可见
// g.sayHi() //Error, 内的外部不可见
:zap:注意:
前面,我们提到:派生类应该遵守基类的契约。
但是,可见性不受契约限制,派生类可以改变基类成员的可见性。
class Base {
protected m = 10;
}
class Derived extends Base {
// No modifier, so default is 'public'
m = 15;
}
const d = new Derived();
console.log(d.m); // OK
private
仅在类内部可见。
class Base {
private x = 0;
getX(){
return this.x
}
}
const b = new Base();
console.log(b.getX()) //OK
// console.log(b.x); //Property 'x' is private and only accessible within class 'Base'.
:zap:注意:对于private修改的成员,子类不会继承
静态成员Static Members
静态成员包括静态属性和静态方法。
:zap: ES6中,Class 内部只有静态方法,没有静态属性(有个提案,还在stage3阶段)。
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
// 编译为ES6为:
class MyClass {
static printX() {
console.log(MyClass.x);
}
}
// ES6的静态属性
MyClass.x = 0;
console.log(MyClass.x);
MyClass.printX();
静态成员也可以同时有可见性修饰符,也可以被继承。
class MyClass {
private static x = 0;
static printX() {
console.log(MyClass.x);
}
}
// MyClass.x = 100 // 内外部无法访问私有的静态属性
//静态成员被继承
class Base {
static getGreeting() {
return "Hello world";
}
}
class Derived extends Base {
myGreeting = Derived.getGreeting();
}
const d = new Derived()
console.log(d.myGreeting) // [LOG]: "Hello world"
:zap: 注意,由于ES6中,静态属性实际上是挂载在类上的,而类本身就是可以用new调用的函数,所以静态成员的名称不能和函数原型上已有的属性同名,如:name/length/call
等
没有静态类
在Java中有静态内部类,但是Typescript不需要,因为普通函数和对象可以完成相同的工作。
// Unnecessary "static" class
class MyStaticClass {
static doSomething() {}
}
// Preferred (alternative 1)
function doSomething() {}
// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};
泛型类Generic Classes
ts中引入了泛型,这是ES6没有的。
泛型类看上去与泛型接口差不多。 泛型类使用( <>
)括起泛型类型,跟在类名后面。
与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
const b = new Box("hello!");
const c = new Box(666)
泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
class Box<Type> {
static defaultValue: Type; //Error
//Static members cannot reference class type parameters.
}
类中的this
:100: TypeScript 不会改变 JavaScript 的运行时行为。
由于javascript中this的处理非同寻常:
class MyClass {
name = "MyClass";
getName() {
//this : 调用时的对象
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
// Prints "obj", not "MyClass"
// 因为getName是通过obj调用的,因为this指向obj,而不是指向MyClass的实例
console.log(obj.getName());
// 如果期望固定this的指向,建议使用箭头函数
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());