typescript从入门到现在的总结 1 - 14个要点+案例

141 阅读9分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

前段时间发表了node的学习
现在丸子终于要对ts进行总结跟回顾了
目录

  1. 函数
  2. 类型约束
  3. 函数重载
  4. this问题
  5. .静态成员
  6. 继承
  7. 抽象类
  8. 接口基础
  9. 接口可选结构
  10. 接口检测约束
  11. 接口索引签名
  12. 接口函数类型接口
  13. 类接口
  14. 案例

typescript是javascript的超集,在js的基础上,为你的变量参数等加上类型定义,于是就变成了typescript。

下面都是代码+注释结合的学习总结。不多废话,直接看吧。

1 &2 typscirpt的函数声明和类型约束

/**
 * 函数
 *      函数声明
 *      函数表达式
 * 
 * 类型约束
 *      函数参数
 *      函数返回值
 * 
 * 如果函数没有返回值,使用 void,不是undefined
 * // function fn1(x: number, y: number): number {
//     return x + y;
// }

// fn1(1,2);
// fn1('a', 'b');



// 函数表达式
// let fn2 = function(x: number, y: number): number {
//     return x + y;
// }

// let a: number = 1;

// let fn2: (x: number, y: number) => number = function(x: number, y: number): number {
//     return x + y;
// }

// 根据类型推断可以简写
// let fn2: (x: number, y: number) => number = function(x, y) {
//     return x + y;
// }

// fn2('a', 'b');
// fn2('a', 'b');
// fn2(1, 2);


// 可选参数
// function fn3(x: number, y?: number): void {}
// fn3(1);

// 参数默认值
// function fn3(x: number, y = 1): void {
//     console.log(y);
// }
// fn3(0);


// 剩余参数
// function fn4(...args: any[]) {
//     console.log(args);
// }
 */
 

这里看齐啦typescript跟普通javascript好像差别不大啊。

好,下面继续看。

3 .函数重载

// 如果是在js中
// function fn(x, y) {
//     return x + y;
// }

// fn(1, 2);
// fn('a', 'b');


// 函数重载
// function fn(x: number, y: number): number;
// function fn(x: string, y: string): string;

// function fn(x: any, y: any): any {
//     return x + y;
// }

// fn(1, 2);
// fn('a', 'b');
// fn(1, 'a');

4 ts里面的this

/**
 * ts中默认情况下函数中的this默认指向 : any
 */

let obj = {
    a: 10,
    fn() {
        // 因为默认情况下,this是any类型,any类型ts不能提示有任何属性方法
        // let document:any;
        // any的值,ts不能提示或者进行类型属性检测
        // console.log(this.b);

        // 使用noImplicitThis选项可以取消默认this的any来这个设置
        // this.a
    }
}

// obj.fn();


// ts会自动推导事件函数中的this
// document.onclick = function() {
//     this
// }


let obj1 = {
    a: 1,
    fn(this: Element|Document) {    // 在ts中函数的第一个this参数是用来设置this类型约束的
        // 这个this是一个假参数,运行过程中是不存在,是给ts检测使用的
        // console.log(this);
        // 希望ts是按照事件函数中的this去做检测
        this;   //检测检测检测

        // this
    }
};

document.onclick = obj1.fn;
document.body.onclick = obj1.fn;

// function fn10(x: number) {

// }

// fn10( document.querySelector('input').value );

唯一需要特别注意的是typescript 中默认情况下函数内的this指向是any

5 ts中的类

类是对事物的通用概括,也就是通用结构的概括。

运行设置公开访问的属性和方法,也允许设置保护的属性和保护的方法,甚至是私有的属性和方法(也就是对外部完全不开放访问的代码)。

下面看一下代码:

// class Person {
//     /**
//      * ts中的类,成员属性必须要声明后使用
//      * ts中类的成员属性不是在构造函数中声明的,是在class内,方法外
//      * 
//      * public
//      *      公开的,所有的地方都能访问,属性和方法默认是public
//      * protected
//      *      受保护的,在类的内部和他的子类中才能访问
//      * private
//      *      私有的,只能在该对象(类)的内部才可以访问
//      */

//     public username: string = '';
//     // private username: string = '';
//     // protected username: string = '';

//     // readonly username: string = '';

//     constructor(name: string) {
//         this.username = name;
//     }

// }

// class Student extends Person {

//     say() {
//         this.username
//     }
// }

// let p1: Person = new Person('Kimoo');

// p1.username = 'zmouse';


class Person {

    username: string = 'Kimoo';
    // private age: number = 10;

    private _age: number = 10;

    // getAge(): number {
    //     return this.age;
    // }

    // setAge(age: number): void {
    //     if (age > 0 && age < 150) {
    //         this.age = age;
    //     }
    // }

    // 存取器,这个a并不会作为方法,而是属性去访问
    get age(): number {
        return this._age;
    }

    set age(age: number) {
        if (age > 0 && age < 150) {
            this._age = age;
        }
    }

}

let p1: Person = new Person();

/**
 * 允许在外部获取和修改age的值,但是不希望该修改成非法的,比如1000岁
 */
// p1.age = 1100;

// console.log(p1);

// p1.setAge(20);
// p1.setAge(200);
// console.log(p1.getAge());

// console.log( p1.a );

上面的类和方法调用比较像Java中的的风格。

6 类的静态成员

单例是一种设计模式,它的作用是让程序进程中只有一份对象实例。

下面,我们看看单例的实现代码。


/**
 * 单例
 */

// class Mysql {

//     host: string;
//     port: number;
//     username: string;
//     password: string;
//     dbname: string;

//     constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
//         this.host = host;
//         this.port = port;
//         this.username = username;
//         this.password = password;
//         this.dbname = dbname;
//     }

//     query() {}
//     insert() {}
//     update() {}

// }

/**
 * 创建一个Mysql对象,通过这个对象来操作数据库
 * 如果我们不加以限制的话,这个Mysql是可以new出来多个对象的
 * 每一个Mysql都会占用资源(内存)
 */
// let db = new Mysql();
// db.query();
// db.insert();

// let db1 = new Mysql();
// db1.query();
// db1.insert();




/**
 * 通过某种方法控制系统同时只有一个Mysql的对象在工作
 * 通过口头去约定是不靠谱的
 */

class Mysql {

    // 静态属性,不需要通过new出来的对象方面,直接是通过Mysql类来访问
    public static instance;

    host: string;
    port: number;
    username: string;
    password: string;
    dbname: string;

    private constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.dbname = dbname;
    }

    public static getInstance() {
        if (!Mysql.instance) {
            Mysql.instance = new Mysql();
        }
        return Mysql.instance;
    }

    query() {}
    insert() {}
    update() {}

}


// let db = new Mysql();

// console.log(Mysql.instance);

let db = Mysql.getInstance();
db.query();
db.insert();

这里展示的单例是一个操作Mysql数据库的一个对象实例。

这样保证了在内存中只有一个Mysql实例对象,不管是什么时候调用getInstance方法,我们获得一个唯一的Mysql操作的对象。

这样的好处可以保证进程中只有这个操作对象,统一管理的入口。

7.继承

继承是面向对象的特征之一。

这个在提高代码的重用性有很大作用。

先看代码:

class Person {

    private _a = 1;

    // 在构造函数的参数中如果直接使用public等修饰符,则等同于同时创建了该属性
    constructor(public username: string, public age: number) {
        this.username = username;
        this.age = age;
    }

}

class Student extends Person {

    // 如果子类没有重写构造函数,则直接父类的
    // 如果子类重写了构造函数
    // 注意:需要手动调用父类构造函数
    // super:关键字,表示父类
    constructor(username: string, age: number, public type: string) {
        super(username, age);    //执行父类构造函数

        this.type = type;
    }

}

let s1 = new Student('Kimoo', 30, 'javascript');

从代码我们可看到,继承一下子省略了很多代码。

直接把一个类型的属性,方法复用了。

这样我们想要对一个类型演化的多个类型就很好编程实现,也提高了一个追溯的脉络。

通过继承,我们可以发现整个类型结构的树,从子类型追溯到祖宗类型。

8.抽象类

抽象类,就是类中允许定义抽象的方法。

代码如下:

abstract class Person { //抽象类不能实例化的
    username: string;

    constructor(username: string) {
        this.username = username;
    }

    say() {
        console.log('哈哈哈哈哈');
    }

    // 虽然子类都会有这样的特性,学习,但是子类学习具体过程不一 样,所在在父类确定
    // 不了study方法的具体实现,父类只能有抽象约定,接收什么参数,返回什么内容
    // 如果一个类中有抽象的方法了,那么这个类也必须是抽象的
    abstract study(): void   //抽象方面是没有具体代码的
}


class Student extends Person {

    study() {
        console.log('学生有学生的学习方法 - 需要老师教授');
    }

}

class Teacher extends Person {

    study() {
        console.log('自学');
    }

}

// 如果一个类继承了抽象的父类,就必须实现所有抽象方面,否则这个子类还是必须得为抽象的
// abstract class P extends Person {

// }


// new Person();

有什么用呢?

抽象类允许局部不实现,这样实现了整体约束的情况下,支持了差异化衍生的特性。

这样我们可以通过抽象类实现统一调用一个先不实现的方法(抽象函数)。

然后不同的子类实现该抽象方法,达到统一的调用,但是执行的细节不一样。

9 接口 interface

接口是区别于类的数据结构声明方式。

跟类区别的是,接口中不允许实现,一切只能是声明。

这样有啥好处呢?

没有声明具体的方法实现,那不是空白?不是的。这样比类更加弹性。

我们可以用一个接口去承载不同的实现类,然后程序上面达到统一的调用。

这样能够写出更多通用的代码,提高了组件的重用率。

下面是接口的定义

// /**
//  * interface
//  *      为我们提供一种方式来定义某种结构,ts按照这种结构来检测数据
//  * 
//  *      写法
//  *          interface 接口名称 {
//  *              // ... 接口规则
//  *          }
//  * 
//  *  接口中定义的规则只有抽象描述,不能有具体的值与实现的
//  * 
//  *  对象抽象 => 类(对象的抽象描述)
//  *  类抽象 => 抽象类(如果一个类中拥有一个没有具体实现的抽象方法,就是抽象类)
//  *  抽象类 => 接口(如果一个抽象类的成员全部是抽象的,那么可以看做接口)
//  */

// interface Options {
//     width: number,
//     height: number
// }

// function fn(opts: Options) {}

// // 类型检测只检测必须的属性是否存在,不会按照顺序进行,无序的
// fn({
//     height: 200,
//     width: 100
// });

10 接口中的可选结构

有些时候我们需要达到一定的限制的情况下,又要允许某些字段/属性不给值。

这个时候,我们可以使用'?‘ 来修饰属性。这样的话,编译器不会强制检测某个字段一定要给值/赋值。

下面看一下展示代码:

// /**
//  * 如果规则中有些是可选的,那么通过 ? 标识
//  */

// interface Options {
//     width: number,
//     height: number,
//     color?: string
// }

// function fn(opts: Options) {}

// fn({
//     height: 200,
//     width: 100
// });

11 接口中的检测约束

下面展示使用约束检测的场景和绕开约束检测的场景。

这个代码不太多可以快速看一下:

// /**
//  * 如果我们希望检测不必要这么复杂
//  *      - 如果我们希望某些时候,只要包含其中一些规则即可
//  *          - 通过可选 ? 来实现
//  *          - 通过 as 断言
//  *          - 通过变量转换
//  */

// interface Options {
//     width: number,
//     height: number,
//     color: string
// }

// function fn(opts: Options) {}

// // 告知ts检测,我传入的就是一个Options
// // fn({
// //     height: 200,
// //     width: 100
// // } as Options);

// // 先赋值给一个变量,也可以绕开 检测
// // let obj = {
// //     height: 200,
// //     width: 100,
// //     color: 'red',
// //     a: 1
// // }
// // fn( obj );

12 索引签名

这个技术更多是对类型/接口某一个或者多个字端的类型进行限定。

这样的好处很明显,可以直接在编译代码的阶段拒绝不良的数据类型,直接编译失败。

而不是程序在运行的过程才报错,这个时候改bug的代价是很高的!!!

下面是展示代码:

// /**
//  * 希望规则是:一组由数字进行key命名的对象
//  * 我们可以使用索引签名
//  *  为数据定义一组具有某种特性的key的数据
//  *  索引key的类型只能是 number 和 string 两种
//  */

// // interface Options {
// //     // key 是number,value是any类型的数据
// //     [attr: number]: any,
// //     length: number
// // }

// // function fn(opt: Options) {}

// // fn({
// //     0: 100,
// //     1: 100,
// //     2: 2000,
// //     length: 1
// // });



// interface Options {
//     // 索引签名的key课是number,也可以是string
//     [attr: string]: any,
//     length: number
// }

// function fn(opt: Options) {}

// fn({
//     a: 1,
//     b: 2,
//     length: 100
// });

13 函数类型接口

/**
 * 这个接口描述的是一个包含有fn并且值的类型为函数的结构体,并不是描述函数结构
 * 而是一个包含函数的对象结构
 */
// interface Options {
//     fn: Function
// }

// let o: Options = {
//     fn: function() {}
// }
// let fn: (x: number, y: number) => number = function(x: number, y: number): number {return x + y}


/**
 * 定义一个事件函数,那么这个函数必须得有一定的规则了
 * 我们不能随便的把一个函数赋值给事件
 */
// function fn(x: MouseEvent) {
//     console.log(x.clientX);
// }

// document.onkeydown = fn;


// 我们也可以使用 interface 来约定定义函数的结构

// 定义的是函数接口
// interface IFn {
//     (x: number, y: number): number
// }


// let fn: IFn = function(x: number, y: number): number {return x + y}


// 定义了一个接受一个MouseEvent类型参数的函数结构
// interface MouseEventCallBack {
//     (e: MouseEvent): any
// }

// let fn: MouseEventCallBack = function(a: MouseEvent) {

// }

// document.onclick = fn;


// interface ResponseCallBack {
//     (rs: Response): any
// }

// function todo(callback: ResponseCallBack) {
//     callback(new Response);
// }


// todo(function(x: string) {

// });

// fetch('url').then( (a: Response) => {
//     a.json();
// } );


// interface AjaxData {
//     code: number;
//     data: any
// }
// interface AjaxCallback {
//     (rs: AjaxData): any
// }


// function ajax(callback: AjaxCallback) {
//     callback({
//         code: 1,
//         data: []
//     });
// }


// ajax( function(x: AjaxData) {
//     x.code
//     x.data

//     x.message
// } );

函数类型的接口,这个可以实现动态传入参数的类型约束。

就像上面的代码一样,我们定义了x类型为AjaxData类型。

这样就保证了参数的结构,必须符合指定的接口,否则报错。

而且接收的函数参数也是接口类型。

这样就实现不同动作(函数)执行结构的约束。

14 类接口

有时候,我们需要使用接口让某个类去符合某种契约,达到强约束的情况下弹性的输入。

这个怎么做呢?

先看代码:

/**
 * 类接口
 *      使用接口让某个类去符合某种契约
 * 
 * 类可以通过 implements 关键字去实现某个接口
 *      - implements 某个接口的类必须实现这个接口中确定所有的内容
 *      - 一个类只能有一个父类,但是可以implements多个接口,多个接口使用逗号分隔
 */
 /**
 * 类接口
 *      使用接口让某个类去符合某种契约
 * 
 * 类可以通过 implements 关键字去实现某个接口
 *      - implements 某个接口的类必须实现这个接口中确定所有的内容
 *      - 一个类只能有一个父类,但是可以implements多个接口,多个接口使用逗号分隔
 */

interface ISuper {
    fly(): void;
}

class Man {

    constructor(public name: string) {
    }

}

class SuperMan extends Man implements ISuper {

    fly() {
        console.log('起飞');
    }

}

class Cat {

}

class SuperCat extends Cat implements ISuper {
    fly() {
        console.log('起飞');
    }
}

let kimoo = new SuperMan('Kimoo');

15 实战案例展示

下面是使用纯js来实现的请求处理。

js的案例如下
function http(options) {

    let options = Object.assign({
        method: 'get',
        url: '',
        isAsync: true
    }, options);

    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(options.method, options.url, options.isAsync);

        xhr.onload = function() {
            resolve( JSON.parse(xhr.responseText) );
        }

        xhr.onerror = function() {
            reject({
                code: xhr.response.code,
                message: '出错了'
            });
        }

        xhr.send();
    })
}

http()

http({
    methods: 'get',
    url: '....',
    isAsync: true
});

http({
    methods: 'post',
    url: '....',
    isAsync: true
});

上面我们封装了http请求,然后调用了get和post。通过分别传递不同的http options来实现get,post请求的调用。

ts实现展示

换成ts的写法

interface HttpOptions {
    method: string,
    url: string,
    isAsync: true
}
interface HttpResponseData {
    code: number,
    data: any
}

function http(options: HttpOptions) {

    let opt:HttpOptions = Object.assign({
        method: 'get',
        url: '',
        isAsync: true
    }, options);

    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(opt.method, opt.url, opt.isAsync);

        xhr.onload = function() {
            let data: HttpResponseData = JSON.parse(xhr.responseText);
            resolve( data );
        }

        xhr.onerror = function() {
            reject({
                code: xhr.response.code,
                message: '出错了'
            });
        }

        xhr.send();
    })
}


http({
    method: 'get',
    url: '....',
    isAsync: true
}).then( (rs: HttpResponseData) => {
    rs.code
} );

typescript我们可以使用接口来限定参数和响应结果的结构。

这样带来了更加严格的检查,不好的地方就说没有像原声的js那样弹性,多几个字段属性也不会报错。

好处的地方恰恰就是对输入的严格限制,这样避免了无用数据的污染。

也更好的把输入和输出进行了限制。而不是给一个option,很多时候这个在项目后期的维护上是很复杂的。

看吧,这就是typescript的魅力了。它的诞生还是很有必要的。

注解

注解(装饰器)是一类特殊类型的声明,可以用在类、方法、构造器、属性和参数上。

其实本质上,定义一个注解,就是定义一个TypeScript方法,只是该方法必须符合官方的规范。

方法分别返回符合规范的函数闭包,参数target、propertyKey、descriptor。

经过实践,这三个参数中target和propertyKey是必须的,没有的话编译过不去,descriptor可以省略。

下面简单展示一下@Controller这个注解。

// @Controller
// class IndexController {

    
//     index() {

//     }

//     @router('/')
//     @method('get')
//     main() {

//     }

// }

看到了把,使用注解节省了很多代码,而且看起来简化了很多。

总结

今天展示的内容很多,也分享了实战的代码。需要多多阅读。

就写到这里

我是丸子,每天学会一个小知识。
一个前端开发
希望多多支持鼓励,感谢