TypeScript学习(九):Handbook -> Classes

849 阅读7分钟

--strictPropertyInitialization

参数开启时,calss 的属性必须在构造函数中定义初始值。可以在属性后面添加 ! 来关闭提示。

class BadGreeter{
    // Property 'name' has no initializer and is not definitely assigned in the constructor.
    name: string
}

构造函数

重载

class Point {
    constructor(x:number, y:number);
    constructor(s:string)
}

注意:构造函数不能有类型参数;构造函数不能返回类型。

get/set

1. 只有get,没有set ----> readonly

2. set 参数没有类型,会自动从 get 的返回值推导类型

3. get/set 必须有相同的 Member Visibility,即 private、public、protected

注意:TS 4.3 中 set/get 可以有不同的类型的 value。

implement

类必须满足一个特定的 interface,可以实现多个 interface。

class C implements A, B{
    // 
}

注意

1. implement 只检查类是不是可以被作为一个 interface。Mehtod  的类型是不会被 implement 改变的。

下方会报错。直觉上会认为 check 方法的参数 s 必然是一个 string。但实际上 implement 后并不会改变 class 内 mtehod 的类型。s 会被稳定为 any。

interface Checkable{
    check(name: string): boolean;
}

class NameChecker implements Checkable{
    check(s){
        returnt s.toLowercse() === 'ok'
    }
}

 2. 可选属性

interface A{
    x: number;
    y?: number;
}

class C implement A{
    x = 0
}
cosnt c = new C()
// 报错
c.y = 10

extends

衍生类继承基类的所有属性和方法,并且可以扩展。

方法重写 Overriding Methods

衍生类可以重写基类的方法,但是方法必须有相同接口。下方报错。

class Base{
    greet(){
    console.log("Hello, World!")
    }
}

class Drived extends Base{
    // 接口不一致 报错
    greet(name: string){
        console.log(name)
    }
}

类的初始化顺序

1. 基类字段初始化

2. 基类构造函数初始化

3. 衍生类字段初始化

4. 衍生类构造函数初始化

内建类型的继承 ---> 适用于ES5

ES5 中派生类的 this 是由派生类创建,再由基类改造;ES6及以后,派生类的 this 是由基类创建,再由派生类改造 (《深入理解ES6》第204页 )。

因此:(这段因果关系推论纯粹个人理解,欢迎批评指正)

1. ES5 中,派生类的某些方法可能不存在

2. instanceof 操作符应用在派生类及其实例的时候会失效;( new MsgError() ) instanceof MsgError ---> false

class MsgError extends Error{
    constructor(m:string){
        super(m);
    }
    sayHello(){
        return this.message
    }
}

挽救方法 ( IE10 及其之前都无效 )

手动调整原型链

class MsgError extends Error{
    constructor(m:string){
        super(m)
        // 调整一下原型链
        Object.setPrototyeOf(this, MsgError.prorotype)    
    }
}

注意:MsgError 的子类也要手动设置原型链

**个人理解:**ES6 中衍生类修饰 基类创建的 this,然后衍生类构造函数返回修饰后的 this 。TS 中的表现与 ES6 一样,但是 TS ---> ES5 及以下的时候,旧语法不支持此特性,因此要手动调整原型链。

类成员可见度 Member Visiblity

public:在 类 的外部任何地方都可以访问类的属性、方法

protected:只能在  子类 中访问基类的方法、属性,不能在 基类 外部访问

下述情况再不同的 OOP 语言中行为不同。在TS/C#/C++中,下述 Derived2 类的 f2 方法是不合法的。因为直接访问的是 基类的 protected 属性。

class Base {
    protected x: number = 1
}
class Derived1 extends Base{
    protected x: number = 5
}
class Derived2 extends Base{
    f1(other: Derived2){
        other.x = 10
    }

    // Property 'x' is protected and only accessible through an instance of class 'Derived2'. 
    // This is an instance of class 'Base'.
    f2(other: Base){
        other.x = 10
    }
}

private:只能在类内部访问属性、方法

在 TS 中,在某一类的一众实例中,其中一个实例的方法可以访问其余实例的私有成员。貌似和private行为相反?

而 Java,C# 之类的语言并不能。

class A{
    private x = 10
    public sameAs(other: A){
        // work
        return other.x === this.x
    }
}

JavaScript的私有属性:developer.mozilla.org/en-US/docs/…

静态成员

只能被类的构造函数调用,其实就是构造函数上添加了一众属性、方法。

静态成员也可以使用 plublic,protected,private修饰。

保留字:name、length、call 等 Function  的属性是不能被定义成静态成员的。

静态类

TS 中不存在静态类

JAVA,C# 等语言中,并不允许方法、属性脱离 class 单独存在,因此只能通过 静态类的方法实现 TS/JS 中的功能。

// 其他语言的写法
class MyStaticClass{
    static doSomething() {}
}

// TS/JS 的写法一
function doSomething() {}
// TS/JS 的方法二
const MyHelperObject = {
    dosomething() {}
}

泛型类

静态方法的类型:TS 中静态成员是不可以设置泛型的。反向思考一下,如果下方Box 一个实例的 Box.defaultValue 的值被的设为一个 string 类型的。那么另一个实例 Box.defaultValue 的值就成了 string,而不是 number。

class Box<T>{
    // Static members cannot reference class type parameters.
    static defaultValue:T
}

运行时 runtime

TS 并不改变 JS 的运行时逻辑,因此,与 JS 一样会有诡异的行为。

what‘s this in JS?

箭头函数

箭头函数的 this 是词法作用域。

1. 可以保证 this 的值永远正确,避免一些诡异行为

2. 占用内存更多,每个使用箭头函数写法的类实例,都会保存一遍 this 的值

3. 在派生类中并不能使用 super.getName。因此箭头函数的写法并不会保存在原型链上

class MyClass{
    name = "MyClass"
    getName = () =>{
        return this.name    
    }
}
const c = new MyClass()
const g = c.getName

// Prints "MyClass" instead of crashing
console.log(g())

this parameters

this 作为函数的参数时,TS 会抹去 this。

// TS 中的写法
function fn(this: SomeType, x:number){}

// 转化为 JS 后,等价于下方
function fn(x) {}

在method 中,通过传入 MyClass 类型的 this 参数以保证 方法的 上下文是正确的。同样地,在运行时中,this 参数会被抹去。同样地,在global context 执行 getName 会报错。

class MyClass{
    name = "MyClass"
    getName(this: MyClass){
        return this.name
    }
}

const c = new MyClass()
c.getName()

// crash
const g = c.getName
g()

this 类型 ---> this Types

TS 中, class 可以使用 this 类型

class Box{
    content = string = ""
    sameAs(other:this){
        return other.content === this.content
    }
}

class DerivedBox extends Box {
    otherContent: string = "?"
}
const base  = new Box
const derived = nwe DerivedBox
derived.sameAs(base)

this 作为类型守卫

使用 this 结合 其他类型守卫 手段可以将目标对象收缩到特定的 类型。

下方案例中 this is  Networked & this 理解是 this is (Networked & this)。isNetwork() 函数执行后,更改了 this 的类型,因此 FileRep 类有了 Networked 的类型,即,将对象收缩到特定类型。

class FileSystemObejct{
    isFile(): this is FileRep{
        return this instanceof FileRep
    }
    isDirectory():this is Directory {
        reutn this instanceof Directory
    }
    isNetworked(): this is Networked & this{
        return this.networked
    }
    constructor(public path: string, private networked: bolean){}
}

class FileRep extends FileSystemObject{
    constructor(path:string, public content:string){
        super(path, false)
    }
}

class Directory extends FileSystemObject{
    children: FileSystemObejct[]
}

interface Networked{
    host: string
}

const fso: FileSystemObject = new FileRep("foo/bar.text", "foo")

if(fso.isFile()){
    // FileRep
    fso.content;
}else if(fso.isDirectory()){
    // Directory
    fso.children
}else if (fso.isNetworked()){
    // Networked & FileSystemObject
    fso.host
}

普遍用法

这个案例中,利用 this is 去移除 value 的 undefined 类型。

class Box<T>{
    value?: T
    
    hasValue(): this is {value: T}{
    return this.value !== undefined
    }
}

const box = new Box()
// (property) Box<unknow>.value?: unknown 
box.value = "Gameboy"

if(box.hasValue()){
    // value: unknown
    box.value
}

Parameter Properties

TS 中类的构造函数可以不用给构造函数的变量赋值,TS 会创建与参数类型相同的 类属性,并且在构造函数执行的时候 赋值。

class Params{
    cosntructor(
        public readonly x: number,
        protected y: number,
        private z: number
    ){
        // no body necessary
    }
}

const a = new Params(1, 2, 3)
// work
console.log(a.x)

// crash
console.log(a.z)

类表达式 Class Expressions

下方的类表达式写法与直接声明一个类的效果相同,但是不同的是在表达式中可以省略类的名称。

const someClass = class<T> {
    content:T;
    constructor(value:T){
        this.content = value;
    }
}

抽象类与类成员 abstract Classes and Members

抽象方法或属性并不需要提供其实现,且只能存在抽象类中。抽象类并不能直接被实例化,通常用作基类。其派生类必须实现其类的抽象方法,否则会报错。

abstract class Base{
    abstract getName():String;
    printName(){
         console.log(`hello, ${this.getName()}`)
    }
}

// crash
const b = new Base()

class Derived extends Base{
    getName(){
        return "world";
    }
}

const d = new Derived();
// work
d.printName()

在某些情况下,可能想接收类的构造函数,并且创建类的实例,但是碰到抽象类会报错。

下方,街上上述抽象类 Base

function greet(ctor: typeof Base){
    // crash 
    const instance = new ctor()
    instance.printName()
}

function greet1(ctor: new () => Base){
    const instance = new Ctor()
    instance.printName()
}

// work
greet(Derived)
// crash
// Cannot assign an abstract constructor type to a non-abstract constructor type.
greet(Base)

类之间的关系

大多数情况下,TS 只会比较类的结构。

class Point1{
    x = 0;
    y = 0;
}
class Point2{
    x = 0;
    y = 0;
}
// work
const p: Point1 = new Point2()

class Person{
    name: string;
    age: number;
}
class Employee{
    name: string;
    age: number;
    salary: number;
}
// work
const p1: Person = new Employee()
// crash
const p2: Employee = new Person()

Empty Class:空类没有任何成员,在结构化的类型系统中,空类是任何对象的超类。

class Empty{}
function fn(x: Empty){
    // ...
}

// All OK!
fn(window)
fun({})
fn(fn)

参考

www.typescriptlang.org/docs/handbo…