TypeScript

  • VS code 自动编译 TS 文件
  1. 第一步 tsc --init生成 tsconfig.json"outdir": "./js"
  2. 第二步 任务 - 运行任务 监视 tsconfig.json

数据类型

  • TS 中为了使编写的代码更加规范,更有利于维护,增加了类型校验,在 TS 中主要提供了以下数据类型
  • 布尔类型 boolean
  • 数字类型 number
  • 字符串类型 string
  • 数组类型 array
  • 元组类型 tuple
  • 枚举类型 enum
  • 任意类型 any
  • null 和 undefined
  • void 类型
  • nerve 类型
  • 数组类型
  • let arr: number[] = [1, 2, 3]
  • let arr: Array = [1, 2, 3]
  • 元组类型
  • let arr: [number, string] = [1, “hello”]
  • 枚举类型(enum)

随着计算机的不断普及,程序不仅只用于数值计算,还更广泛地用于处理非数值的数据。
例如性别、年龄、月份、颜色等,都不是数值数据。
在其它程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。
也就是说,实现考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值。
这种方法成为枚举方法,用这种方法定义的类型称枚举类型。

  • 定义枚举类型的方法
enum 枚举名 {
		标识符[=整型常数],
		标识符[=整型常数],
		...
		标识符[=整型常数],
	}
  • 使用的方法
enum Color {
		blue,
		red = 30,
		'orange'
	};

	let a: Color = Color.blue
	console.log(a)  // 0
	let b: Color = Color.red
	console.log(b)  // 30
	let c: Color = Color.orange
	console.log(c)  // 31
  • 任意类型
  • 用法
let oBox: any = document.getElementById('box');
	oBox.style.color = 'red';
	// oBox 不指定类型或者指定 Object 类型都会报错,所以需要指定 any 类型。
  • null 和 undefined
  • 用法
let num1: undefined;
	console.log(num1);
	let num2: number | undefined
	num2 = 123
	console.log(num2)
  • void 类型
  • 用法
    ts function run(): void { console.log("ok") } //表示 run 函数没有返回值
  • Never 类型
  • Never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
  • 是其他类型(包括 null 和 undefined)的子类型,代表从不会出现的值
  • 这意味着声明 never 的变量只能被 never 类型所赋值。
  • 用法:
// 返回never的函数必须存在无法达到的终点
	function error(message: string): never {
			throw new Error(message);
	}

	// 推断的返回值类型为never
	function fail() {
			return error("Something failed");
	}

	// 返回never的函数必须存在无法达到的终点
	function infiniteLoop(): never {
			while (true) {
			}
	}

函数

  • TS 中函数的定义
  • 函数声明式
function run(): string {
		return 'abc'
	}
  • 匿名函数
  • 方法可选参数
  • ES5 里面方法的实参和形参可以不一样,但是 TS 中必须一样,如果不一样就需要配置可选参数。
  • 注意:可选参数必须配置到参数的最后面。
function getInfo(name: string, age?: number) {
		if(age) {
			return `${name} --- ${age}`
		} else {
			return `${name} --- 年龄保密`
		}
	}
	alert(getInfo("张三"))
  • 默认参数
  • ES5 中无法设置默认参数,ES6 和 TS 中都可以设置默认参数
function getInfo(name: string, age: number = 20) {
		if(age) {
			return `${name} --- ${age}`
		} else {
			return `${name} --- 年龄保密`
		}
	}
	alert(getInfo("张三"))
  • 剩余参数
  • 利用 三点运算符 获取剩余参数
function sum(...result: number[]): number {
		let sum = 0
		result.forEach(item => {
			sum += item
		})
		return sum
	}
	alert(sum(1, 2, 3, 4))
  • TS函数重载
  • Java 中方法的重载,重载指的是两个或者两个以上同名的函数,但是他们的参数不一样,这时会出现函数重载的情况。
  • TypeScript 中的重载,通过为同一个函数提供多个函数类型定义来实现多种功能的目的
function getInfo(name: string): string;
	function getInfo(age: number): number;
	function getInfo(str: any): any {
		if(typeof str === 'string') {
			return '我叫:' + str;
		} else {
			return '我的年龄是:' + str
		}
	}
  • 箭头函数

ES5 创建对象和继承

  • 原型链 + 对象冒充的组合继承模式
  • 继承构造函数及其原型链里面的属性和方法
function Person(name, age) {
		this.name = name;
		this.age = age;
		this.run = function () {
			alert(this.name + "在运动")
		}
	}
	Person.prototype.sex = "男"
	Person.prototype.work = function() {
		alert(this.name + "在工作")
	}

	function Web(name, age) {
		// 对象冒充实现继承构造函数的属性和方法
		Person.call(this, name, age)
	}

	// 实现继承其原型链上的属性和方法	- 下面两种方式都可以实现
	// Web.prototype = new Person()
	Web.prototype = Person.prototype

	var w = new Web("张三");
	w.run()
	w.work()

TS 中的类

  • TS 中类的定义
class Person {
   	name: string;
   	constructor(name: string) {
   		this.name = name
   	}
   	run():void {
   		alert(this.name)
   	}
   }
  • TS 中实现继承 - extends、super
class Person {
  	name: string;
  	constructor(name: string) {
  		this.name = name
  	}
  	run():void {
  		alert(`${this.name}在运动`)
  	}
  }
  class Web extends Person {
  	constructor(name: string) {
  		// 初始化父类的构造函数
  		super(name)
  	}
  	work() {
  		alert(`${this.name}在工作`)
  	}
  }
  • 类里面的修饰符
  • TS 里面定义属性的时候给我们提供了三种修饰符
  • public:共有 在类里面、子类、类外面都可以访问
  • protected:保护类型 在类里面、子类里面可以访问,在类外面无法访问
  • private:私有 在类里面可以访问,子类、类外部都无法访问

静态属性、静态方法

  • static 关键字声明属性或者方法
class Person {
		public name: string;
		public age: number = 20;
		static sex = "男"
		constructor(name: string) {
			this.name = name
		}
		run() {
			alert(`${this.name}在运动`)
		}
		work() {
			alert(`${this.name}在工作`)
		}
		// 静态方法	里面无法直接调用类里面的属性
		static print() {
			// alert("print方法" + this.age)
			alert("print方法" + Person.sex)
		}
	}
	Person.print()

多态

  • 父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现
  • 多态也是继承的一种表现,多态属于继承
class Animal {
		name: string;
		constructor(name: string) {
			this.name = name
		}
		eat() {	// 具体吃什么,不知道,继承它的子类去实现,每一个子类的表现不一样
			console.log("吃的方法")
		}
	}
	class Dog extends Animal {
		constructor(name: string) {
			super(name)
		}
		eat() {
			return this.name + '吃粮食'
		}
	}
	class Cat extends Animal {
		constructor(name: string) {
			super(name)
		}
		eat() {
			return this.name + '吃老鼠'
		}
	}

抽象类

  • TS 中的抽象类:它是提供其他类继承的基类,不能直接被实例化
  • abstract 关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
  • abstract 抽象方法只能放在抽象类里面
  • 抽象类和抽象方法用来定义标准 – Animal 这个类要求它的子类必须包含 eat 方法
abstract class Animal {
		public name: string;
		constructor(name: string) {
			this.name = name
		}
		abstract eat(): any;	// 抽象方法不包含具体实现并且必须在派生类中实现
	}
	class Dog extends Animal {
		constructor(name: string) {
			super(name)
		}
		// 抽象类的子类必须实现抽象类里面的抽象方法
		eat() {
			return this.name + '吃粮食'
		}
	}
	class Cat extends Animal {
		constructor(name: string) {
			super(name)
		}
		// 抽象类的子类必须实现抽象类里面的抽象方法
		// 不包含 eat 抽象方法,所以报错了
		// 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“eat”。
	}

	// let a = new Animal()		// 不能这样用

	let dog = new Dog("大黄")
	alert(dog.eat())

接口

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

  • 属性接口
  • TS 中定义方法传入参数
function printLabel(label: string): void {}
	printLabel("哈哈")
  • TS 中定义方法传入参数对 json 进行约束
function printLabel(labelInfo: {label: string}): void {}
	printLabel("哈哈")	// 错误写法
	printLabel({label: "哈哈"})		// 正确写法
  • 对批量方法传入参数进行约束
  • 接口:行为和动作的规范,对批量方法进行约束
interface FullName {
		firstName: string;
		secondName: string;
	}
	function printName(name: FullName) {
		// 必须传入对象 firstName 和 secondName
		console.log(name.firstName + '---' + name.secondName)
	}
  • 可选属性
interface FullName {
		firstName: string;
		secondName?: string;	
	}
  • 函数类型接口:对方法传入的参数以及返回值进行约束
  • 加密的函数类型接口
interface encrypt {
		(key: string, value: string): string
	}
	let run: encrypt = function(key: string, value: string): string {
		return key + value
	}
  • 可索引接口:数组、对象的约束 (不常用)
  • 可索引接口对数组的约束
interface UserArr {
		[index: number]: string;
	}
	let arr: UserArr = ['123', 'bbb']
	console.log(arr[0])
  • 可索引接口对对象的约束
interface UserObj {
		[index: string]: string;
	}
	let obj: UserObj = {name: '张三'}
	console.log(obj.name)
  1. 类类型接口:对类的约束 和 抽象类有点类似
interface Animal {
  		name: string;
  		eat(str: string): void
  	}
  	class Dog implements Animal {
  		name: string;
  		constructor(name: string) {
  			this.name = name
  		}
  		eat() {
  			console.log(this.name + '吃肉')
  		}
  	}
  1. 接口扩展:接口可以继承接口
interface Animal {
  		eat(): void;
  	}
  	interface Person extends Animal {
  		work(): void;
  	}
  
  	class Programmer {
  		public name: string;
  		constructor(name: string) {
  			this.name = name
  		}
  		coding(code: string) {
  			console.log(this.name + code)
  		}
  	}
  
  	class Web extends Programmer implements Person {
  		constructor(name: string) {
  			super(name)
  			this.name = name
  		}
  		eat() {
  			console.log(this.name + '吃馒头')
  		}
  		work() {
  			console.log(this.name + '写代码')
  		}
  	}
  
  	let w = new Web("小黑")
  	w.eat()
  	w.work()
  	w.coding("写 TS 代码")

泛型

泛型:软件工程中,我们不仅要创建一直的定义良好的 API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同事也能支持未来的数据类型,着在创建大型系统时为你提供十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决类 接口 方法的复用性、以及对不特定类型的支持。

泛型的定义

  • T 表示泛型,具体什么类型是调用这个方法的时候决定的

泛型函数

function getData<T>(value: T):T {
		return value
	}
	getData<number>(123)

泛型类:

class MinClass<T> {
		public list: T[] = []
		add(value: T):void {
			this.list.push(value)
		}
		min():T {
			let minNum = this.list[0];
			for(let i = 0; i < this.list.length; i++) {
				if (minNum > this.list[i]) {
					minNum = this.list[i]
				}
			}
			return minNum
		}
	}
	let m1 = new MinClass<number>();
	m1.add(1) 
	m1.add(14) 
	m1.add(5) 
	alert(m1.min())

泛型接口

  • 基本使用
interface ConfigFn<T> {
		(value: T): T
	}
	function getData<T>(value: T):T {
		return value
	}
	var myGetData:ConfigFn<string> = getData;
	myGetData('20')
	// myGetData(20)		// 错误
	// getData<string>('1234')		// 错误·ts
  • 数据库操作
class ArticleCate {
		title: string | undefined;
		desc: string | undefined;
		status: number | undefined;
		constructor(params: {
			title: string | undefined,
			desc: string | undefined,
			status?: number |undefined,
		}) {
			this.title = params.title
			this.desc = params.desc
			this.status = params.status
		}
	}
	class MysqlDb<T> {
		add(info: T):boolean {
			console.log(info)
			return true
		}
		updated(id: number, info: T): boolean {
			console.log(info)
			return true
		}
	}
	// 添加操作
	let a = new ArticleCate({
		title: "分类",
		desc: '123',
	})
	let db = new MysqlDb<ArticleCate>()
	db.add(a)
	// 修改操作
	let b = new ArticleCate({
		title: "分类2",
		desc: '456',
		status: 1,
	})
	db.updated(123, b)

综合使用

功能:定义一个操作数据库的库,支持 Mysql、Mssql、MongDB
要求1:Mysql、Mssql、MongDB 功能一样,都有 add、update、delete、get 方法
注意:约束统一的规范,以及代码重用
解决方案:需要约束规范所以要定义接口,需要代码重用所以用到泛型
1、接口:在面向对象的编程中,接口是一种规范
2、泛型 通俗理解:泛型就是解决类 接口 方法的复用性

interface DBI<T> {
		add(info: T): boolean;
		update(info: T, id: number): boolean;
		delete(id: number): boolean;
		get(id: number): any[];
	}
	// 定义一个操作 mysql 数据库的类		注意:要实现泛型接口,这个类也应该是一个泛型
	class MysqlDb<T> implements DBI<T> {
		constructor() {
			console.log("数据库建立连接")
		}
		add(info: T): boolean {
			console.log(info)
			return true
		}	
		update(info: T, id: number): boolean {
			throw new Error("Method not implemented.");
		}
		delete(id: number): boolean {
			throw new Error("Method not implemented.");
		}
		get(id: number): any[] {
			let list = [
				{
					title: 'xxxx',
					desc: 'xxxxx',
				},
				{
					title: 'xxxx',
					desc: 'xxxxx',
				},
			]
			return list
		}
	}
	// 定义已个操作 mssql 数据库的类
	class MssqlDb<T> implements DBI<T> {
		add(info: T): boolean {
			console.log(info)
			return true
		}	
		update(info: T, id: number): boolean {
			throw new Error("Method not implemented.");
		}
		delete(id: number): boolean {
			throw new Error("Method not implemented.");
		}
		get(id: number): any[] {
			throw new Error("Method not implemented.");
		}
	}
	// 操作用户表		定义一个 User 类和数据表做映射
	class User {
		username: string | undefined;
		password: string | undefined;
	}
	let u = new User()
	u.username = "张三"
	u.password = "123456"
	let oMysql = new MysqlDb<User>()
	oMysql.add(u)
	
	let oMssql = new MssqlDb<User>();
	oMssql.add(u)
	
	// 获取 User 表 id 为4的数据
	let data = oMysql.get(4)
	console.log(data)

模块

模块的概念

  • 官方:
  • 关于术语的一点说明:请务必注意一点,Typescript 1.5里面语名已经发生变化。“内部模块"现在称作"命名空间”
  • “外部模块"现在则简称为"模块”,模块在其自身的作用域里执行,而不是在全局作用域里
  • 这意味着定义在一个模块里的变量、函数、类等等在模块外部都是不可见的,除非你明确地使用 export 形式之一导出它们。
  • 相反,如果想使用其他模块导出的变量、函数、类、接口等的时候,你必须要导入它们,可以使用 import 形式之一。
  • 理解:
  • 我们可以把一些公共的功能单独抽离成一个文件作为一个模块
  • 模块里的变量、函数、类等默认是私有的,如果我们要在外部访问模块里面的数据(变量、函数、类),
  • 我们需要通过 export 暴露模块里面的数据(变量、函数、类)
  • 暴露后我们通过 import 引入模块就可以使用模块里面暴露的数据(变量、函数、类)

命名空间

  • 在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
  • 同 Java 的包、.net 的命名空间一样,TypeScript 的命名空间可以将代码包裹起来,只对外暴露需要外部访问的对象,命名空间内的对象通过 export 暴露出去

命名空间和模块的区别

  • 命名空间:内部模块,主要用于组织代码,避免命名冲突
  • 模块:ts的外部模块的简称,侧重代码的重用,一个模块里可能会有多个命名空间
namespace A {
  		interface Animal {  }
  		export class Cat { 
  			eat() {}
  		}
  	}
  	let a = new A.Cat()

装饰器

  • 装饰器:装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。
  • 通俗的讲,装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能
  • 常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
  • 装饰器的写法:普通装饰器(无法传参),装饰器工厂(可传参)
  • 装饰器是过去几年中 Js 最大的成就之一,已是 ES7 的标准特性之一

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。传入一个参数

  • 普通装饰器(无法传参)
function logClass(params: any) {
  		console.log(params)
  		// params 就是当前类
  		params.prototype.apiUrl = 'xxx'
  		params.prototype.run = function() {
  			console.log("我是一个 run 方法")
  		}
  	}
  	@logClass
  	class HttpClient {
  		constructor() {}
  		getData() {}
  	}
  	let http = new HttpClient()
  	console.log(http.apiUrl)
  	http.run()
  • 装饰器工厂(可传参)
function logClass(params: string) {
  		return function(target: any) {
  			console.log(target)
  			console.log(params)
  
  			target.prototype.apiUrl = params
  		}
  	}
  	@logClass('hello')
  	class HttpClient {
  		constructor() {}
  		getData() {}
  	}
  	let http = new HttpClient()
  	console.log(http.apiUrl)
  • 类装饰器:下面是一个重载构造函数的例子
  • 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
  • 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
function logClass(target: any) {
		console.log(target)

		return class extends target {
			apiUrl: any = '我是修改后的数据'
			getData() {
				this.apiUrl = this.apiUrl + '----'
				console.log(this.apiUrl)
			}
		}
	}

	@logClass
	class HttpClient {
		public apiUrl: string | undefined;
		constructor() {
			this.apiUrl = "我是构造函数中的 apiUrl"
		}
		getData() {
			console.log(this.apiUrl);
		}
	}
	let http = new HttpClient() 
	http.getData()
  • 属性装饰器
  • 属性装饰器表达式会在运行时当作函数被调用,传入下列两个参数:
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字
// 类装饰器
	function logClass(params: string) {
		return function(target: any) {
			// console.log(params)
			// console.log(target)
		}
	}

	// 属性装饰器
	function logProperty(params: any) {
		return function(target: any, attr: any) {
			console.log(params)
			console.log(target)
			console.log(attr)
			target[attr] = params
		}
	}
	@logClass('xxx')
	class HttpClient {
		@logProperty('http://itying.com')
		public url: any | undefined;
		constructor() {
			
		}
		getData() {
			console.log(this.url)
		}
	}
	let http = new HttpClient() 
	http.getData()
  • 方法装饰器
  • 它会被应用到方法的属性描述符上,用来监视,修改或者替换方法定义。
  • 方法装饰器会在运行时传入下列3个参数:
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字
  • 成员的属性描述符
  • 方法装饰器的使用
  • 方法装饰器一
function get(params: any) {
		return function(target: any,methodName: any, desc: any) {
			console.log(target)
			console.log(methodName)
			console.log(desc)
			target.apiUrl = 'xxx'
			target.run = function () {
				console.log('run')
			}
		}
	}
	class HttpClient {
		public url: any | undefined;
		constructor() {}
		@get('http://itying.com')
		getData() {
			console.log(this.url)
		}
	}
	let http = new HttpClient() 
	console.log(http.url)
	http.run()
  • 方法装饰器二
function get(params: any) {
		return function(target: any,methodName: any, desc: any) {
			console.log(target)
			console.log(methodName)
			console.log(desc.value)
			// 修改装饰器的方法   把装饰器方法里面传入的所有参数改为 string 类型
			// 1. 保存当前方法
			let oMethod = desc.value
			desc.value = function(...args:any[]) {
				args = args.map((value) => {
					return String(value)
				})
				console.log(args)
				oMethod.apply(this, args)
			}
		}
	}
	class HttpClient {
		public url: any | undefined;
		constructor() {}
		@get('http://itying.com')
		getData(...args:any[]) {
			console.log(args)
			console.log('getData里面的代码')
		}
	}
	let http = new HttpClient()
	http.getData(123, '12333')
	```
  • 方法参数装饰器
  • 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 方法的名称名字
  • 参数在函数参数列表中的索引
function logParams(params: any) {
		return function(target: any, methodName: any, paramsIndex: any) {
			console.log(params)
			console.log(target)
			console.log(methodName)
			console.log(paramsIndex)
			target.apiUrl = params
		}
	}
	class HttpClient {
		public url: any | undefined;
		constructor() {}
		getData(@logParams('uuid') uuid: any) {
			console.log(uuid)
		}
	}
	let http = new HttpClient()
	http.getData(123)
	console.log(http.apiUrl)
	```
  • 装饰器执行顺序
  • 属性 > 方法 > 方法参数 > 类
  • 如果有多个同样的修饰器,它会先执行后面的
function logClass1(params: string) {
		return function(target: any) {
			console.log("类装饰器1")
		}
	}
	function logClass2(params: string) {
		return function(target: any) {
			console.log("类装饰器2")
		}
	}
	function logAttribute(params?: string) {
		return function(target: any, attrName: any) {
			console.log("属性装饰器")
		}
	}
	function logMethod(params?: string) {
		return function(target: any, attrName: any, desc: any) {
			console.log("方法装饰器")
		}
	}
	function logParams1(params?: string) {
		return function(target: any, attrName: any, desc: any) {
			console.log("方法参数装饰器1")
		}
	}
	function logParams2(params?: string) {
		return function(target: any, attrName: any, desc: any) {
			console.log("方法参数装饰器2")
		}
	}
	@logClass1("http://www.itying.com/api")
	@logClass2("xxxx")
	class HttpClient {
		@logAttribute()
		public apiUrl: string | undefined
		constructor() {
		}
		
		@logMethod()
		getData() {
			return true
		}
		setData(@logParams1() attr1:any, @logParams2() attr2:any) {}
	}
	let http = new HttpClient()
	// 输出顺序:属性装饰器 方法装饰器 方法参数装饰器2 方法参数装饰器2 类装饰器2 类装饰器1