TS学习笔记

472 阅读10分钟

实时编译命令:tsc --watch

一、基础类型

数字

let a: number = 10

字符串

let b: string = 'haha'

布尔值

let c: boolean = true

数组

// arr1,arr2 等价的
let arr1: Number[] = [1, 2, 3]
let arr2: Array<number> = [2, 3, 5]

元组类型tuple

数量和类型已知的数组,要一一对应,多和少都不行

let sf: [string, number] = ['sf', 100]

枚举

1. 普通枚举

可以事先考虑到所有变量的可能值,如性别,月份,星期,颜色,日期等值是固定的

enum gender {
    boy,
    girl
}

编译后:

var gender;
(function (gender) {
    gender[gender["boy"] = 0] = "boy";
    gender[gender["girl"] = 1] = "girl";
})(gender || (gender = {}));

可以正反两方向取值

console.log(gender.boy, gender[0]);    // 0  boy
console.log(gender.girl, gender[1]);   // 1  girl

2. 常量枚举

const enum colors {
    red, yellow, green
}
let myColor = [colors.red, colors.yellow, colors.green]

编译后:

var myColor = [0 /* red */, 1 /* yellow */, 2 /* green */];

编译完只留值,原来定义代码就不要了,省略了定义的过程了,节约内存开销

任意类型

any可以赋值给任意类型,如类型定义困难,结构太复杂,没有类型声明的时候都可以使用any。

如果变量定义为any类型,就跟js差不多,不进行类型检测。

let root:any = document.getElementById('roow')
root.style.color = 'red'

非空断言!

开启strictNullChecks: true后,ts担心你的变量为null,空指针异常,会报个错,所以在变量后加个!即可。

let element: (HTMLElement | null) = document.getElementById('roow')
element!.style.color = 'green'

null undefined

是其他类型的子类型

let x:number;
x = 1;
x = undefined;
x = null;

但是 tsconfig.json 中设置了 "strictNullChecks": true,就开启严格空检查,为true不能把null,undefined赋值给其他类型。可以单独设置,或者用'|'分隔

let a1: undefined = undefined;
let a2: null = null;
let a3: any = undefined;
let a4: any = null;
let a5: number | undefined | null;
    a5 = 1;
    a5 = undefined;
    a5 = null;

never

是其他类型,null、undefined的子类型,代表不会出现的值

  • 作为不会返回的函数的返回值类型(报错,死循环等无法继续的)
function error(message: string): never {
    throw Error('报错')
}

function loop(): never {
    while (true) { }
}

void

代表没有任何类型,当一个函数没有返回值的时候,它就是void类型。

function test():void{
    return undefined 
}

return nullstrictNullChecksfalse 时可以,true不行。

void 和 never 的 区别

  • void 可以被赋值为undefined,null。never不能包含任何类型。
  • 返回类型为void的函数能执行,但是返回never的函数无法正常执行。

二、复杂类型

类型断言

// 类型断言
let name1: number | string;
console.log((name1! as number).toFixed(2));
console.log((name1! as string).length);

// 双重断言
console.log(name1! as any as boolean);

字面量类型和类型字面量

字面量类型

const up: 'Up' = 'Up';
const down: 'Down' = 'Down';
const left: 'Left' = 'Left';
const right: 'Right' = 'Right'
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
function move(derection: Direction) { }
move('Right')

类型字面量

type Person = {
    name: string,
    age: number
}
let p1: Person = {
    name: 'hhh',
    age: 100
}

字符串字面量和联合类型

字符串字面量

type T1 = '1' | '2' | '3'

联合类型

type T2 = string | number | boolean

变量只能赋值符合条件的,否则会报错

let t1: T1 = '1'
let t2: T2 = true

三、函数

函数定义

约定参数类型和函数返回值类型

function hello(name: string): void {
    console.log(name);
}
hello('anan')

函数表达式

函数没有返回值,设置为void即可。

type GetName = (fname: string, lname: string) => string   //(void)
let getName: GetName = function (fname: string, lname: string): string {
    return fname + lname
}

可选参数

? 可以传参,也可以不传

function print(name: string, age?: number) {}
print('hello')

默认参数

function ajax(url: string, method: string = 'GET') {}
ajax('/')

剩余参数

function sum(...numbers: number[]) {
    return numbers.reduce((val, item) => val + item, 0)
}
console.log(sum(1, 2, 3));

函数的重载

  • 函数根据传入不同的参数而返回不同类型的数据
  • 为同一个函数提供多个函数类型定义来进行函数重载 例如:参数为字符串,给name赋值,为number给age赋值
let obj: any = {}
function attr(val: string): void
function attr(val: number): void
function attr(val: any): void {
    if (typeof val === 'string') {
        obj.name = val
    } else if (typeof val === 'number') {
        obj.age = val
    }
}
attr('aa')
attr(10)

四、类

多个ts文件中,如果有重复名称的变量或类,都会报错,因为把它们当做全局的文件,解决这个问题,可以在文件中用export {} ,ts会认为这是一个模块,里面的变量都是私有的

export { }
class Person {
    name: string
    getNage(): void {
        console.log(this.name);
    }
}
let p1 = new Person();
p1.name = 'zt'
p1.getNage()

定义存取器

通过存取器改变一个类中属性的读取和赋值操作

class User {
    myName: string
    constructor(myName: string) {
        this.myName = myName
    }
    get name() {
        return this.myName
    }
    set name(value) {
        this.myName = value
    }
}
let user = new User('name1')
user.name = 'haha'
console.log(user.name); //haha

编译后:

var User = /** @class */ (function () {
    function User(myName) {
        this.myName = myName;
    }
    Object.defineProperty(User.prototype, "name", {
        get: function () {
            return this.myName;
        },
        set: function (value) {
            this.myName = value;
        },
        enumerable: false, // 是否可枚举:能否用for in迭代出来
        configurable: true // 是否可配置:delete,属性可删?
    });
    return User;
}());
var user = new User('name1');
user.name = 'haha';
console.log(user.name);

参数属性

readonly

如果是公开只读的属性,它只能在构造函数中赋值

class Animal {
    public readonly name: string; 
    constructor(name: string) {
        this.name = name
    }
    changeName(val: string) {
        this.name = val
    }
}

如果修改只读属性会报错

image.png

继承

class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    getName(): string {
        return this.name
    }
    setName(name: string): void {
        this.name = name
    }
}
class Student extends Person {
    stuNo: number
    constructor(name: string, age: number, stuNo: number) {
        super(name, age)
        this.stuNo = stuNo
    }
    getStuNo(): number {
        return this.stuNo
    }
}
let s1 = new Student('zt', 20, 2014)

public,private,protected

将代码简化写法 constructor(public myName: string)

* : 自动把myName变量赋值给当前实例。

class User {
   // myName: string  (省略啦!)
    constructor(public myName: string) {   // *
      // this.myName = myName (省略啦!)
    }
    get name() {
        return this.myName
    }
    set name(value) {
        this.myName = value
    }
}
  • public:自己,自己的子类,其他类都能访问
  • protected:自己和自己的子类可访问,其他类不可访问
  • private:自己能访问,子类和其他类都不可访问
// 父类
class Father {
    public name: string
    protected age: number  
    private money: number
    constructor(name: string, age: number, money: number) {
        this.name = name
        this.age = age
        this.money = money
    }
    getName(): string {
        return this.name      // 自己可访问
    }
    getAge(): number {
        return this.age       // 自己可访问
    }
    getMoney(): number {
        return this.money       // 自己可访问
    }
}

// 子类
class Child extends Father {
    constructor(name: string, age: number, money: number) {
        super(name, age, money)
    }
    desc() {
        console.log(this.name, this.age);  // 子类可访问
        console.log(this.money); // 报错

    }
}

// 外部
let child = new Child('ztt', 11, 22)
console.log(child.name); // 外部可访问
console.log(child.age); // 报错: 属性“age”受保护,只能在类“Father”及其子类中访问。
console.log(child.money); // 报错:属性“money”为私有属性,只能在类“Father”中访问。

子类,父类方法重名,子类会覆盖父类方法

class Father1 {
    toString() {
        console.log('Father1');
    }
}
class Child1 extends Father1 {
    toString() {
        console.log('Child1');
    }
}
let father1 = new Father1()
father1.toString()  // Father1

let child1 = new Child1()
child1.toString() // Child1

子类调用父类方法

class Father1 {
    toString() {
        console.log('Father1');
    }
}
class Child1 extends Father1 {
    toString() {
        super.toString() // Father1
    }
}
let child1 = new Child1()
child1.toString()

装饰器

装饰器是一种特殊的声明,可以被附加到类声明,方法,属性和参数上,它可以修改类的行为。

有类装饰器,方法装饰器,参数装饰器。

类装饰器

在类声明之前声明,用来监视修改和替换类的定义「装饰器是个函数」

用@xxx一个方法放在类前,装饰这个类

function addNameEat(constructor: Function) {
    constructor.prototype.name = 'hello'
    constructor.prototype.eat = function () { }
}

@addNameEat
class Person {
    name: string
    eat: Function
    constructor() { }
}
let p: Person = new Person()
console.log(p.name);
p.eat()

替换类

namespace c {
    function replaceClass(constructor: Function) {
        return class {
            name: string
            eat: Function
            age: number   // 可以多一个属性,但不能少
            constructor() { }
        }
    }

    @replaceClass
    class Person {
        name: string
        eat: Function
        constructor() { }
    }
    let p: Person = new Person()
    console.log(p.name);
    p.eat()
}

返回一个新的类,把老类替换掉,多一个属性可以,但不能少。

装饰器工厂

上段代码中希望@addNameEat可以传参,目前看来传不了,所以就给addNameEat方法再套一层addNameEatFactory,并返回addNameEat

function addNameEatFactory(name:string) {
    return function addNameEat(constructor: Function) {
        constructor.prototype.name = name
        constructor.prototype.eat = function () { }
    }
}

@addNameEatFactory('hello')
class Person {
    name: string
    eat: Function
    constructor() {}
}
class Person {
    name: string
    eat: Function
    constructor() { }
}

先让@addNameEatFactory('hello')执行,返回一个addNameEat函数,它再装饰类

命名空间

如果想在一个文件里或一个模块里声明2个一样的类型,可以用命名空间分隔,就不冲突了。

命名空间本质是一个闭包

namespace a {
    function addNameEat(constructor: Function) {
        constructor.prototype.name = 'hello'
        constructor.prototype.eat = function () { }
    }
    @addNameEat 
    class Person {
        name: string
        eat: Function
        constructor() { }
    }
    let p: Person = new Person()
    console.log(p.name);
    p.eat()
}

namespace b {
    function addNameEatFactory(name: string) {
        return function addNameEat(constructor: Function) {
            constructor.prototype.name = name
            constructor.prototype.eat = function () { }
        }
    }
    @addNameEatFactory('world')
    class Person {
        name: string
        eat: Function
        constructor() { }
    }
    let p: Person = new Person()
    console.log(p.name);
    p.eat()
}

属性装饰器

属性装饰器会在运行时当做函数被调用,可以装饰属性和方法。

namespace d {
    /**
     * @param target 如果装饰的是实例属性,target是构造函数的原型
     * @param propertyKey 
     */
    function upperCase(target: any, propertyKey: string) {
        let value = target[propertyKey]
        const getter = () => value
        const setter = (newValue: string) => { value = newValue.toUpperCase() }
        if (delete target[propertyKey]) {
            Object.defineProperty(target, propertyKey, {
                get: getter,
                set: setter,
                enumerable: true,
                configurable: true
            })
        }
    }

    /**
     * @param target 如果装饰的是静态属性,target是构造函数本身
     * @param propertyKey 属性名
     */
    function staticPropertyDecorator(target: any, propertyKey: string) {
        console.log(target);
        console.log(propertyKey);
    }

    /**
     * @param target 原型
     * @param propertyKey 方法名
     * @param descriptor 属性描述器
     */
    function noEnumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = false // 设置为不可枚举
    }

    /**
     * 这个方法主要用来求和,但参数是any类型,有可能传入字符串等类型导致结果不正确,所以需要处理成数字
     * @param target 
     * @param propertyKey 
     * @param descriptor 
     */
    function toNumber(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        let oldMethod = descriptor.value // 老方法
        descriptor.value = function (...args: any[]) { // 重写value,整个新函数
            args = args.map(item => parseFloat(item))
            return oldMethod.apply(this, args)
        }
    }

    class Person {
        @upperCase
        name: string = 'zt' // 实例属性
        @staticPropertyDecorator
        public static age: number = 10; //静态属性
        @noEnumerable
        getName() { console.log(this.name) } // 实例方法
        @toNumber
        sum(...args: any[]) { // 实例方法
            return args.reduce((accu: number, item: number) => accu + item, 0)
        }
    }
    let p = new Person()
    console.log(p.name); //
    console.log(p.sum('1', '2', '3'));
}

参数装饰器

( Nest.js大量用到了参数装饰器 )

namespace e {
    /**
     * 
     * @param target 静态成员就是构造函数,非静态成员就是构造函数原型
     * @param methodName 方法名称(login)
     * @param paramIndex 参数在函数参数列表中的索引
     */
    function addAge(target: any, methodName, paramIndex: number) {
        console.log(target, methodName, paramIndex);
        target.age = 10

    }
    class Person {
        age: number
        login(username: string, @addAge password: string) {
            console.log(this.age, username, password); // 10, 1, 2
        }
    }
    let p = new Person()
    p.login('1', '2')
}

装饰器的执行顺序

如果有类装饰器、属性装饰器、参数装饰器,他们之间的顺序如下:

  1. 类装饰器是最后执行,后写的类装饰器先执行
  2. 方法和方法参数中的装饰器,先执行参数,后执行方法
  3. 方法和属性装饰器,谁在前面先执行谁 一般由内往外执行,先内后外,先上后下
namespace f {
    function ClassDecorator1() {
        return function (target) {
            console.log('ClassDecorator1');
        }
    }
    function ClassDecorator2() {
        return function (target) {
            console.log('ClassDecorator2');
        }
    }
    function PropertyDecoractor(name: string) {
        return function (target, propertyName) {
            console.log('PropertyDecoractor', propertyName, name);
        }
    }
    function methodDecoractor() {
        return function (target, propertyName) {
            console.log('methodDecoractor', propertyName);
        }
    }
    function parameterDecoractor() {
        return function (target, methodName, index) {
            console.log('parameterDecoractor', methodName, index);
        }
    }

    @ClassDecorator1()
    @ClassDecorator2()
    class Person {
        @PropertyDecoractor('name')
        name: string = ''
        @PropertyDecoractor('age')
        age: number = 10
        @methodDecoractor()
        hello(@parameterDecoractor() p1: string, @parameterDecoractor() p2: string) { }
    }
}

抽象类

  • 抽象描述一种抽象的概念,无法被实例化,只能被继承。
  • 无法创建抽象类的实例。
  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现。

抽象方法

  • 抽象类和方法不包含具体实现,必须在子类中实现
  • 抽象方法只能出现在抽象类中
  • 子类可以对抽象类进行不同的实现

多态:同一个方法不同的子类有不同的实现

重写(override):子类重写继承自父类的方法

重载(overload):函数的重载,一个函数有多个定义

abstract class Animal {
    name: string
    abstract speak(): void // 抽象方法的关键字abstract
}

class Cat extends Animal {
    speak(): void {
        console.log('miao miao miao ');
    }
}

class Dog extends Animal {
    speak(): void {
        throw new Error("wang wang wang");
    }
}

五、接口

  • 接口一方面可以在面向对象编程中表示为行为的抽象,另外可以用来描述对象的形状。
  • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
  • 一个类可以继承另一个类并实现多个接口
  • 接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念
  • 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类可以有多个子类,但只能有一个父类。

同名的接口可以写多个,类型会自动合并

interface Speakable {
    name: string,
    speak(): void
}
interface Speakable {
    speak(): void
}

interface Eatable {
    eat(): void
}

// Person 实现接口Speakable,Eatable
class Person implements Speakable, Eatable {
    name: string
    speak(): void {
        throw new Error("Method not implemented.")
    }
    eat(): void {
        throw new Error("Method not implemented.")
    }
}

------------------还没完-------------------