typescript从入门到现在的总结2

115 阅读6分钟

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

总结一已经总结了差不多很多ts的东西啦
这个总结2主要讲泛型,虽然丸子对这一块还是有点模糊,但说一下自己的理解了
希望可以帮到大家

泛型介绍

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

题外话,补充一下范型的历史

早在2001年8月,有一门基于Java,能运行在JVM上的编程语言,就实现过运行时泛型,它叫做Pizza。不过很可惜,Pizza在一年后就消亡了,主要的开发人员转入了Generic Java(简称GJ)项目中,而GJ这门语言的泛型整合了通配符之后,就构成了如今Java泛型的原型。

J2SE的开发者当然是明白Java泛型的问题的,在JVM LS 2015上,就有介绍几种泛型实现的比较:

C++使用编译时模板填充来实现泛型,对于每一种参数类型,都会产生一份代码,优势是能很好的指定各种类型,劣势是没有代码复用,会占用大量的空间。

C#则是把类型变量存入了二进制文件(以参数化二进制码的形式),这对于使用各种类型是有优势的,唯一存在的问题只是虚拟机的实现会复杂一些。

Java的泛型实现则是依赖擦除,这对于代码复用有好处,但是对于原始数据类型支持不足。

了解完泛型的历史,就不得不提一个人,Martin Odersky。此人是Pizza的作者,也是GJ的设计者之一,他在一次访谈中爆料:Java要支持泛型,遇到的最大的问题是向上兼容。

TypeScript 在设计的的时候就充分吸收了Java的范型设计思想。 就像C#和Java一样,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

下面时候非范型的代码


/**
 * x 只能是数字类型,如果需要接收任意类型的参数,那么需要使用any,或者函数重载
 * @param x 
 */
// function fn(x: number): number {
//     return x * 10;
// }

// fn(10);
// fn('10');

下面我们展示一下范型的代码怎么写:

/**
 * 泛型
 *  很多时候,类型是写死的,这样就不利于复用
 * 
 *  这样,我们就需要给类型这种值也可以设置变量
 *  <类型变量名>,一般系统使用单字母大写,比如 T,E...
 *  写在函数名,参数列表之间,这是函数的
 */
// function fn<T>(x: T): number {
//     return Number(x) * 10;
// }

//fn(1);  //在调用fn函数的时候,同时给T赋值number
// fn<number>(1);

// fn<string>(1);  //有问题的

// function fn(x) {
//     return x * 10;
// }

// let a = fn(1);
// let b = fn(2);


// function fn(...args) {
//     let arr: number[] = [];
//     for (let i=0; i<args.length; i++) {
//         arr[i] = args[i];
//     }
//     return arr;
// }

// let arr2 = fn('a', 'b', 'c');

// function fn<T, S>(arg1: T, arg2: S): [T,S]{
//     return [arg1, arg2];
// }

// let a = fn<string, number>('a', 1);

// function fn<T>(arg: T[]): T[]{
//     return arg;
// }

// fn<string>( ['1','2'] );
// fn<string>( [1,2] );

// let arr: number[] = [];
// let arr2: Array<number> = [];

对比后的代码解析:

我们给x添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。

我们把这个版本的x函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。

泛型的类

TypeScript泛型类 - 把类作为参数类型的泛型类.

泛类:泛型可以帮助我们避免重复的代码以及对不特定数据类型的支持(类型校验),下面我们看看把类当做参数的泛型类: 先定义个类然后把类作为参数来约束数据传入的类型

class MyArray <T> {

    private _data: T[] = [];

    constructor() {

    }

    public mypush(v: T): number {
        this._data.push(v);
        return this._data.length;
    }

}

// 对于arr对象这个实例来讲,里面的T就是string
let arr: MyArray<string> = new MyArray();
arr.mypush('1');

// 对于arr对象这个实例来讲,里面的T就是number
let arr2: MyArray<number> = new MyArray();
arr2.mypush(1);


// let a: Array<number> = [1,2,'1'];

上面的代码是使用MyArray类的一个范型展示。 这样的数组对象能够支持不同类型的的对象不用重新定义类。

只需要使用的时候,声明上类型,比如Array。

上面说了很多范型的内容,下面展示一下接口。

这个是接口和类的代码展示。

// // interface Person {
// //     [attr: string]: any
// // }
// class Person {
//     constructor(public username: string, public age: number) {
//     }
// }

// // // // Person => 一个拥有Person类型对应特征的对象
// // let p1: Person = new Person('Kimoo', 30);

// // function SuperMan(obj: Person) {
// //     obj.fly = function() {
// //         console.log('fly');
// //     }
// // }


// // function getPersonObj(constructor: Person) {// 我想约束传入的必须是一个构造函数
// //     // Person 表示这个类型对应的对象,我们这里要的Person的构造函数,不是他的对象
// //     // return new constructor();
// //     return new constructor();
// // }

// // getPersonObj( Person  );

// // function getArray(constructor: {new(): Array<string>}) {   // {new()} 接收一个可以产生对象的构造函数
// //     return new constructor();
// // }

// // getArray( Array  );


// // Person类的对象
// // let a: Person;
// // let fn1: {new (): Person};

这里我们可以看到范型更像数据类型一样,接口跟类跟函数这些更多是一种结构的抽象。

范型就是一个*类型,可以随便传入一个类型。比如在使用一个接口的时候,加上类型。

但是跟any不同的是,范型接口/类一旦明确了类型之后,我们只能往里面继续操作/放置同样的类型数据。否则错误!!!

另一个接口展示

// // interface Window { 
// //     [attr: string]: any
// // }


// // window.miaov = 1;

再展示一下范型带来的限制

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

为此,我们定义一个接口Len来描述约束条件。

泛型约束:

// // function fn<T extends HTMLElement>(a: T) {
// //     a.querySelector('div');
// // }

// interface Len {
//     length: number
// }

// function fn<T extends Len>(a: T) {
//     // 不是所有类型都有length
//     a.length
// }

// fn('1');

总结一下

为什么使用泛型

许多时候,标注的具体类型并不能确定,比如一个函数的参数类型。TypeScript 中不建议使用 any 类型,不能保证类型安全,调试时缺乏完整的信息。

TypeScript可以使用泛型来创建可重用的组件。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活。可以在编译时发现你的类型错误,从而保证了类型安全。

泛型的使用

使用泛型可以创建泛型函数、泛型接口,泛型类,支持很广泛,这样让程序更加智能兼容多样化的组件。也提高了组件的重用性。

另外也涉及到了接口,类等,展示了范型的约束,挺多代码的,读者记得多学一下。

就写到这里

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