你所不知道的Typescript

712 阅读13分钟

1. 什么是Typescript

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个严格超集,
并添加了可选的静态类型和使用看起来像基于类的面向对象编程语法操作 Prototype。
C#的首席架构师以及 Delphi 和 Turbo Pascal 的创始人安德斯·海尔斯伯格参与了 TypeScript 的开发。

TypeScript 设计目标是开发大型应用,然后转译成 JavaScript。
由于 TypeScript 是 JavaScript 的严格超集,任何现有的 JavaScript 程序都是合法的 TypeScript 程序。  

2. TypeScript的优点

支持ES6规范,学到的typescript语法未来是客户端脚本语言的主流语法。
强大的IDE支持: 1、类型检查。 2、语法提示。3、重构(修改方法名称的时候会自动把引用的地方都重构)
是Angular,React的首选开发语言。

3. TypeScript中的数据类型

1.字符串类型

	let str: string = "typescript";
        const str2: string = "Spring";
	console.log(str);//typescript
	console.log(str2);// Spring

2. 数值

	let num: number = 20;
	console.log(num);

3. 布尔值

	let boo: boolean= true;
	console.log(boo);//true

4. 元组类型 是数组的一种,元组类型就是数组里面可以有其他类型的数据 而且数据的位置要按照顺序进行排列

	let arr2: [number, string, boolean, null, undefined] = [10, '20', true, null, undefined];//必须按照顺序和类型进行赋值
	console.log(arr2);//10, '20', true, null, undefined

5. 枚举类型 用来以数字表示特殊含义

    enum user {
    success,//默认是0
	error,//默认是1
	administrator,//默认是3
	jurisdiction,
    };
    用法
    if (user.success === 0) {//通过判断下标确定是否匹配
        console.log('this is Success');
    }
    if (user.jurisdiction === 3) {
	    console.log('this is jurisdiction');
    }

6. any任意类型

    用any可以表示任意类型,但通常不建议使用,因为使用any类型的话,会丧失typescript所带来的类型检测
    const anyThing: any = 'string';
    const str10: any = undefined;
    const boo1: any = true;
    const nu5: any = null; 

7. 怎么一个变量设置多个类型

	let num5: number | null | undefined = 50;
	num5 = null;
	console.log(num5);//null
	num5 = undefined;
	console.log(num5);//undefined;

8. void 类型一般用于函数,表达这个函数没有返回值

	const miss = (num: number): void => {//:void表示这个方法没有返回值
	    console.log('这个方法没有返回任何类型');
	}
	miss();

9. Never 类型 never 是其它类型

包括 null 和 undefined的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值
	在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环),示例代码如下:
	let x: never;
	let y: number;
	
	// 运行错误,数字类型不能转为 never 类型
	x = 123;
	
	// 运行正确,never 类型可以赋值给 never类型
	x = (()=>{ throw new Error('exception')})();
	
	// 运行正确,never 类型可以赋值给 数字类型
	y = (()=>{ throw new Error('exception')})();
	
	// 返回值为 never 的函数可以是抛出异常的情况
	function error(message: string): never {
	    throw new Error(message);
	}
	
	// 返回值为 never 的函数可以是无法被执行到的终止点的情况
	function loop(): never {
	    while (true) {}
	}

4. TypeScript中函数的定义和使用

1. 声明一个函数约束其传参类型,以及返回值类型

	传入两个参数,没有返回值
	const fun1 = (key: string, value: number): void => {
		console.log(key, value);//"Typescript",100
	};
	fun1("Typescript", 100);

2. TypeScript配置可选参数,在ES5或者ES6中函数中的实参可以不传递进去,但是在TS中必须传递进去,如果需要设置非必传参数,就必须设置可选参数具体如下

	const fun2 = (a: string, b?: number) => {//形参后面加个?代表可以传递参数也可以不传递参数
		    console.log(a);//typescript
	}
	fun2('typescript');
	注意:配置可选参数必须配置到最后一个参数,否则ts会有报错提示(虽然编译可以通过但不建议这么使用)

3. TypeScript 设置默认值

//设置了默认值,并传入实参,默认实参会代替默认值,这一点和ES6一致
const fun3 = (a: number, b: string = 'ECMAScript'): void => {
	console.log(a);//20
	console.log(b);//typescript
};
fun3(20, 'typescript');
/设置了默认值,没有传递实参,默认B的值就是true
	const fun4 = (a: number, b: boolean = true): void => {
	    console.log(a);//60
	    console.log(b);//true
};
fun4(60);

4. TypeScript函数剩余参数

	//接收多个参数,并放到一个容器里面,与ES6中的rest...三点运算符一样
	const fun5 = (...result: number[]): void => {//用变量result接收实参,并指明数据类型
	let sum: number = 0;
		for (let index = 0; index < result.length; index++) {
		     sum += result[index];
	};
	    console.log(sum);//150
	};
	fun5(10, 20, 30, 40, 50);
	//注意接收多个实参的变量必须放在最后一个,否则会报错
		
	//接收参数,与变量名一一对应
	const fun6 = (first: string, ...result: string[]): void => {
		console.log(first);//string
		console.log(result);//[ 'number', 'boolean', 'function', 'true' ]
	}
	fun6('string', 'number', 'boolean', 'function', 'true');

5. TypeScript函数重载

// java中方法的重载:重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
// typescript中的重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。
//TS中函数重载  TS为了兼容ES5和ES6 不能写大括号
//对实参类型进行约束
	function dataFn(a: string, b: number): void
	function dataFn(a: number, b: string): void
	function dataFn(a: number, b: number): void
	function dataFn(a: any, b: any): void {
//对传入的实参类型进行判断 如果符合某个函数就执行其函数体
	if(typeof (a) === "string") {
		    console.log('This is 字符串')
	};
	if (typeof (a) === "number" && typeof (b) === "number") {
		     console.log('this is 数字');
	};
	if (typeof (a) === 'number') {
		     console.log(a, b);//20,typescript
		 } else {
		    }
	}
	dataFn(10, 20);
	dataFn(20, 'typescript');

6. TypeScript中的箭头函数

基本形式:
    let func = num:number => num; //只有一个形参可以‘=’后面写形参名,并约束其类型
	let func = () => num;//如果有多个形参,在‘=’后面写‘()’把形参写在()里面并约束其类型
	let sum = (num1, num2) :number=> num1 + num2;//如果只有1条执行语句,直接在‘=>’后面写执行语句即可,还要指定其返回类型
	如果有多条语句必须写{},将代码写在{}里面,重新指定返回值,以及类型
注意事项:
	函数体内的this对象,就是定义时所在的上下文,如果箭头函数是全局里面的话,还是指向window,建议在箭头函数外部再嵌套一层函数以便于控制里面的this
	不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
	不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
	const fun8 = (a: number, b: number): void => {
		console.log(a, b)
	}
	fun8(10, 20);

7. TypeScript中的类与ES6一致

class Person {
	name: string;//声明类型 前面省略了关键字 public
	age: number;
	constructor(name: string, age: number) {//构造函数 实例化类的时候触发的方法
	        this.age = age;
	        this.name = name;
	}
	getName(name: string): void {
	        this.name = name;
	        console.log(this.name);
	 }
	 setName(name: string): void {
	        this.name = name;
	        console.log(this.name);
	 }
}
let person = new Person('Kissing', 20);
	
person.getName("spring");//spring
	
person.setName("faker");//faker

8. TypeScript实现继承

class Web extends Person {
	constructor(name: string, age: number) {
	     super(name, age);//super表示调用父类的方法
	 }
	 work(): void {
	     console.log(`${this.name}在运动`)
	 }
	 showAge(): void {
	     console.log(this.age);
	 }
}
	
let web = new Web('handsome', 40);
web.work();//handsome在运动
web.showAge();//40

9. TypeScript类中的修饰符

class Person {
	public name: string;//public公有属性可以当前类以及子类,和外面访问
	protected age: number;//保护属性,可以当前类以及子类访问,但不能被外部访问
	private sex: string;//私有属性,仅限当前类访问,子类和外部均不能访问
	constructor(name: string, age: number, sex: string) {
	    this.name = name;
	    this.age = age;
	    this.sex = sex;
	};
	getName() {
	     console.log(this.name);//Loki
	};
	getAge() {
	    console.log(this.age); 26
	};
	getSex() {
	  console.log(this.sex);//male
	}
};
let person = new Person('Loki', 26, "male");
person.getName();
person.getAge();
person.getSex();
alert(person.name);//Loki
alert(person.age);//报错 属性“age”受保护,只能在类“Person”及其子类中访问
alert(person.sex);//报错属性“sex”为私有属性,只能在类“Person”中访问

10. TypeScript中的类其中的静态属性和静态方法以及调用(一般用的不是很多)

class Season {
	static third: string = 'autumn';//使用关键字static
	first: string;
	public second: string;
	constructor(first: string, second: string) {
	    this.first = first;
	    this.second = second;
	};
	getFirst() {
	    console.log(this.first);
	};
	getSecond() {
	    console.log(this.second + "-" + Season.third);
	}
	static four() {
	    console.log('this is four');
	}
}
let season = new Season('spring', 'summer');
console.log(Season.third);//查看属性
Season.four();//调用方法

11. TypeScript中的多态

//多态:父类定义一个方法不去实现,让继承它的子类去实现  每一个子类有不同的表现 
//多态属于继承
class Animal {
	name: string = 'm';
	constructor(name: string) {
	}
	eat() {
	    console.log(`这是吃的方法`);
	}
}
		
class Dog extends Animal {
	constructor(name: string) {
		   super(name);
	}
	eat() {
		        console.log(`${this.name}喜欢吃肉`)
	}
}
		
class Cat extends Animal {
		constructor(name: string) {
		    super(name);
		}
		eat() {
		        console.log(`${this.name}喜欢吃老鼠`)
	        }
}

12. TypeScript中的抽象类

//typescript中的抽象类:它是提供其他类继承的基类,不能直接被实例化。
//用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
//abstract抽象方法只能放在抽象类里面,抽象类是一种用来约束子类的标准而已,也就是规定
	abstract class Animal {//定义抽象接口
	    abstract run(): void;//定义抽象方法,并约束没返回值
	    public dog: string;
	    constructor(dog: string) {
		        this.dog = dog;
	    }
	}
		
		class Dog extends Animal {//继承
		    constructor(dog: string) {
		        super(dog);
		    }
		    run(): void {//子类实现抽象方法
		        console.log(`${this.dog}喜欢运动`);
		    }
		};
		const dog = new Dog('小狗');
                dog.run();

6.TypeScript中的接口

接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。定义标准。

1. 属性接口

        定义接口
	interface fullName {//关键字是interface
	    first: string;//定义属性名以及类型必须以;结尾
	    second: string;//定义属性名以及类型必须以;结尾
	    third: number;//定义属性名以及类型必须以;结尾
	}
	
	function printName(name: fullName) {//规定传入的值必须符合要求
	    console.log(name.first + "--" + name.second);
	    console.log(name.third);
	}
	// printName({//这种写法错误,直接传对象只能写第一和第二个参数
	// //不能写入其他参数
	//     first : "spring",
	//     second : "summer",
	//     age : 20,
	//     sex : 'male',
	// })
	
	let obj = {//写法正确
	    first: "Missing",//属性名与属性值必须符合要求
	    second: "Kissing",//属性名与属性值必须符合要求
	    third: 20,//属性名与属性值必须符合要求
	}
	printName(obj);//传值
	对传入的值可填可不填
	interface season {
	    one: string;
	    two: string;
	    three: string;
	    four?: string;//加上“?”可以填入值也可以不填入值
	}
	
	function oder(oder: season) {
	    console.log(oder.one);
	    console.log(oder.two);
	    console.log(oder.three);
	    //console.log(oder.four);
	}
	let oderSeason = {
	    one: 'spring',
	    two: "summer",
	    three: "autumn",
	    //four : "winter",//在抽象中已经加入“?”这个值可以不写
	}
	oder(oderSeason);

2. 函数类型的接口

	//函数类型接口 对方法传入的参数,以及返回值的约束
	//不能直接传入变量名 必须以实参传递进去
	interface encryption {//定义一个函数类型的接口
	    (key: string, value: string): void;
	}
	
	let admin: encryption = (key: string, value: string): void => {//实现这个接口
	    console.log(key + "---" + value);//name --- Odin
	}
	admin('name', "Odin");

3. 可索引接口也就是数组和对象的约束(一般不常用)

	//对数组的约束 可索引接口
	interface userArr {
	    [index: number]: string;//下标的类型必须是number 而数组里面的类型可以任意指定
	}
	let arrayThird: userArr = ["20", "40,"];
	//对 对象的约束
	interface userObj {
	    [index: string]: string;
	}
	let userObject: userObj = {
	    name: "string",
	    age: '20',
	}

4. “类”类型接口:对类的约束和抽象类相似

	interface Rainbow {
	    name: string;
	    color(str: string): void;
	}
	class sevenRainbow implements Rainbow { //使用关键字implements 继承接口
	    name: string;
	    constructor(name: string) {
	        this.name = name;
	    }
	    color(mean: string) {
	        console.log(this.name + ',' + mean);
	    }
	}
	let SevenRainbow = new sevenRainbow("什么是幸福?");
	SevenRainbow.color('幸福就是猫吃鱼,狗吃肉,奥特曼打小怪兽');

6. TypeScript中的泛型

1. 什么是泛型

	泛型:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
	通俗理解:泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)
	//<任意字母即可> 一般使用T  形参的类型和返回值一般也是T 
	function getDate<T>(name: T, value: T): T {
	    console.log(name);
	    console.log(value);
	    return value
	}
	getDate<number>(123, 456);//调用后面加<类型> 后面调用的时候
	getDate<string>("missing", "kissing")

2. 泛型类的实现

	函数最小堆功能,要求同时支持返回number和string
	class MyComputed {
	    public list: number[] = [];
	    add(num: number) {
	        this.list.push(num);
	    };
	    min() {
	        let minNum = this.list[0];
	        let len = this.list.length
	        for (let index = 0; index < len; index++) {
	            if (minNum > this.list[index]) {
	                minNum = this.list[index]
	            }
	        };
	        return minNum;
	    }
	}
	let myComputed = new MyComputed();
	myComputed.add(20);
	myComputed.add(10);
	myComputed.add(30);
	myComputed.add(40);
	let minMax = myComputed.min();
	console.log(minMax)//10
	以上代码有局限性例如只能传入number类型的值 不便于使用
	以下使用泛型进行改造
	class minNumber<T> {
	    public list: T[] = []
	    add(value: T): void {
	        this.list.push(value)
	};
	minMyNumber(): T {
	        let minNum = this.list[0];
	        let len = this.list.length
	        for (let index = 0; index < len; index++) {
	            if (minNum > this.list[index]) {
	                minNum = this.list[index]
	            }
	 };
	    return minNum;
	 }
	}
	let minNumber1 = new minNumber<number>();
	minNumber1.add(20);
	minNumber1.add(10);
	minNumber1.add(30);
	let min = minNumber1.minMyNumber();
	console.log(min);//10
	let minNumber2 = new minNumber<string>();
	minNumber2.add('a');
	minNumber2.add('b');
	minNumber2.add('c');
	let strNumber = minNumber2.minMyNumber();
	console.log(strNumber);//a通过对比ascll码表

3. 泛型接口

定义泛型接口有两种方式
		第一种
		interface myConfig {
		    <T>(value: T): T
		};
		const getData1: myConfig = function <T>(value: T): T {
		    return value;
		}
		const data1 = getData1<string>('miss');
		console.log(data1);//miss
		
		第二种写法
		interface Config<T> {
		    (value: T): T;
		}
		function getData<T>(value: T): T {
		    console.log(value);
		    return value;
		}
		let myData: Config<string> = getData;
		myData("Missing");
		
		把类当做参数来约束传入的类型
		class MuSqlDB <T> {
		    add(info:T):boolean{
		        console.log(info);
		        return true;
		    }
		};
		
		
		class ArticleCate {
		    title : string | undefined;
		    name : string | undefined;
		    age :number | undefined;
		    constructor(params : {
		        title : string | undefined;
		        name : string | undefined;
		        age : number | undefined;
		    }) {
		        this.title = params.title;
		        this.name = params.name;
		        this.age = params.age;
		    }
		}
		let articleCate = new ArticleCate({
		    title : 'Title',
		    name : "Loki",
		    age : 200,
		});
		
		let muSqlDB = new MuSqlDB <ArticleCate> ();
		muSqlDB.add(articleCate);

7. TypeScript中模块化

1. 模块的的概念(官方):

    关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。
    “外部模块”现在则简称为“模块” 模块在其自身的作用域里执行,而不是在全局作用域里;
    这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export形式之一导出它们。 
    相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用 import形式之一。

2. 模块的概念(自己理解):

我们可以把一些公共的功能单独抽离成一个文件作为一个模块。
模块里面的变量 函数 类等默认是私有的,如果我们要在外部访问模块里面的数据(变量、函数、类),
我们需要通过export暴露模块里面的数据(变量、函数、类...)。
暴露后我们通过 import 引入模块就可以使用模块里面暴露的数据(变量、函数、类...)。

3. 模块的暴露和引入(与ES6一致)

1.逐个暴露

	export const getData = (value1: string): void => {
		    console.log(value1);
	}
	export let first: string = "First";
	export let second: string = 'Summer';
	export let third: string = 'Autumn';
        export let four: string = "Winter";
	逐个引入
	import { getData, first, second, third, four } from './index5';

2. 统一暴露

	let first: string = "First";
	let second: string = 'Summer';
	let third: string = 'Autumn';
	let four: string = "Winter";
	export {
		  first,
		  second,
		  third,
		  four
	}
	统一引入
	import { getData, first, second, third, four } from './index5';

3。 默认暴露(这种方法常用)

	export default 默认暴露 (每个模块都可以有一个default导出。 默认导出使用 default关键字标记;并且一个模块只能够有一个default导出。
	需要使用一种特殊的导入形式来导入 default导出。)
	export default {
		name: 'Loki',
		age: 20,
		sex: 'female',
		get(params: string): void {
		     console.log(params);
		},
	};
	默认引入
		import data from './index5';
		console.log(data.name, data.sex, data.get('spring'), data.age);

8. TypeScript中的命名空间

命名空间:
    在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
    同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包裹起来,
	只对外暴露需要在外部访问的对象。命名空间内的对象通过export关键字对外暴露。
	命名空间和模块的区别:
    命名空间:内部模块,主要用于组织代码,避免命名冲突。
    模    块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。
	使用namespace作为关键字 具体用法如下
	//把命名空间导出
	export namespace A {
	    interface Animal {
	        name: string;
	        eat(key: string, value: string): void;
	    }
	    export class Dog implements Animal {
	        name: string = 'name';
	        eat(value: string): void {
	            console.log(value + '喜欢吃骨头');
	        }
	    }
	    export class Cat implements Animal {
	        name: string = 'name';
	        eat(key: string): void {
	            console.log(key + '喜欢吃鱼');
	        }
	    }
	}
	//把命名空间引入,以及使用
	import { A, } from './index5';
	
	const dog = new A.Dog();
	dog.eat("小狗");
	
	const cat = new A.Cat();
	cat.eat("小猫");