前言
本人是一个刚入行的菜鸡前端程序员,写这个文章的目的只是为了记录自己学习的笔记与成果,如有不足和错误之处还请大家多多指点。
TypeScript 介绍
- TypeScript 是由微软开发的一款开源的编程语言。
- TypeScript 是 JavaScript 的超级,遵循最新的 ES6/ES5 规范。TypeScript 扩展了 JavaScript 的语法。
- TypeScript 更像后端 Java、C# 这样的面向对象语言可以让 js 开发大型企业项目。
- 谷歌也在大力支持 TypeScript 的推广,谷歌的 angular2.x+ 就是基于 TypeScript 语法。
- 最新的 vue 、 react 也可以集成 TypeScript
TypeScript 安装 编译
- 安装
npm install -g typescript
- 编译
tsc helloworld.ts
TypeScript 开发工具 VsCode 自动编译 .ts 文件
- 创建 tsconfig.json 文件 tsc --init 生成配置文件
tsc --init
- 修改 tsconfig.json 里面的 "outDir" 配置
- 点击菜单 任务 -> 运行任务 点击 tsc:监视-tsconfig.json 然后就可以自动生成代码
TypeScript 中的数据类型
TypeScript 中为了使编写代码更规范,更利于维护,增加了类型校验,在TypeScript中主要给我们提供了以下数据类型。
TypeScript 中定义变量必须指定类型
布尔类型 (Boolean)
var flag:boolean = true
数字类型 (Number)
var num:number = 123
字符串类型 (String)
var str:string = 'abc'
数组类型 (Array)
// 第一种
var arr:number[] = [1,2,4] //定义一个数组,指定数组中所有的值都是number类型
// 第二种
var arr:Array<number> = [1,3,4] //定义一个数组,指定数组中所有的值都是number类型
// 第三种
var arr:any[] = [1, 'aa', 4] //定义一个数组,数组中的所有值可以是任意类型
元组类型 (Tuple) - 属于数组的一种
//可以给数组中的每一个位置指定类型
var arr:[number,string] = [1, 'this is ts']
枚举类型 (Enum)
随着计算机的不断普及,程序不仅只用于数值计算,还更广泛的用于处理非数值的数据。例如: 性别、月份、星期几、颜色、单位名、学历、职业等,都不是数值类型。在其它的程序设计语言中,一般用一个数值来代表某一状态,则程序就很统一阅读和理解。也就是说,事先考虑到某一变量可能取得值,尽量用自然语言中含义清楚得单词来表示它得每一个值,这种方法称为枚举方法,用这种方法定义得类型称为枚举类型。
/*
enum 枚举名 {
标识符[=整型常数]
标识符[=整型常数]
...
标识符[=整型常数]
}
*/
enum flag {
success=1,error=0
}
let s:flag=flag.success
console.log(s) // 1
enum Color {
blue,red,'orange'
}
let c:Color=Color.red
console.log(c) // 1 如果标识符没有赋值,那么它得值就是下标
任意类型 (Any)
var num:any = 123 // num可以是任意类型
Null / Undefined - 其它类型的子类型
var num:number;
console.log(num) //输出:undefined ts报错
var num:undefined;
console.log(num) //输出:undefined 正确
// 定义没有赋值就是 undefined
var num:number | undefined // num可以为number类型或 undefined
Void 类型
TypeScript 中的 void 表示没有任何类型,一般用于定义方法的时候方法没有返回值
//表示方法没有返回任何类型
function run():void {
console.log('run)
}
Never 类型
never类型 - 其他类型 (包括 null、undefined) 的子类型,代表从不会出现的值。这意味着声明never的变量只能被never类型所赋值。
var a:never;
a = 123 //报错
a=(() => {
throw new Error('错误')
})()
TypeScript中的函数
TypeScript 函数的定义
- 函数声明
// 指定函数返回一个 string
function run():string {
return 'aaa'
}
// 没有返回值的方法
function run():void {
console.log('run')
}
- 匿名函数
var fun2 = function():number {
return 124
}
- ts中定义方法传参
/* 参数name为string,age为number。返回一个string */
function getInfo(name:string,age:number):string {
return `${name} ---- ${age}`
}
- 方法的可选参数
// age 可传可不传
function getInfo(name:string,age?:number):string {
if(age) {
return `${name} ---- ${age}`
} else {
return `${name}`
}
}
// 注意:可选参数必须配置到参数的最后面
- 默认参数 es5 中不能设置默认参数, es6和ts中都可以设置默认参数
// name的默认值是 aaa
function getInfo(name:string='aaa',age?:number):string {
if(age) {
return `${name} ---- ${age}`
} else {
return `${name}`
}
}
- 剩余参数 - 三点运算符
function sum(...result:number[]):number {
let sum = 0
for(let i = 0; i < result.length; i++) {
sum += result[i]
}
return sum
}
- 函数重载 java 中方法的重载:重载指的是两个或两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
TypeScript 中重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。
function getInfo(name:string):string;
function getInfo(age:number):string;
function getInfo(str:any):any {
if(typeof str === 'string') {
return `我叫${str}`
} else {
return `我的年龄是${str}`
}
}
TypeScript 中的类
class Person {
name:string; //属性 前面省略了public 关键词
constructor (name:string) { //构造函数 - 实例化类的时候触发的方法
this.name = name
}
run():void {
console.log(name)
}
}
- TypeScript 中实现继承 通过 extends/super 关键字
class Web extends Person {
constructor(name:string) {
super(name)
}
}
当父类和子类有同样的方法时,优先调用子类中的方法
- 类里面的修饰符 TypeScript里面定义属性的时候给我们提供了三种修饰符
- public : 公有 在类里面、子类、类外面都可以访问
- protected : 保护类型 类、子类里面可以访问,类外部没法访问
- private : 私有 类里面可以访问,子类和类外部都不能访问 属性如果不加修饰符,默认是 public
- TypeScript 中的静态属性 静态方法
class Person {
public name:string; //属性 前面省略了public 关键词
// static 关键字设置静态属性
static age = 21
constructor (name:string) { //构造函数 - 实例化类的时候触发的方法
this.name = name
}
run():void { //实例方法 需要实例化才能调用的方法
console.log(name)
}
// static 关键字声明静态方法
static print():void { // 静态方法
// 静态方法中没办法直接调用类里面的属性
console.log(Person.age)
}
}
- TypeScript 多态
多态 - 父类定义一个方法不去实现,让继承它的子类取实现,每一个子类有不同的表现。多态属于继承class Animal{ name:string; constructor(name:string) { this.name = name } eat() { console.log('eat') } } 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 + '吃鱼' } } - TypeScript 抽象
抽象类- 它是提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
抽象方法只能放在抽象类中
// 抽象类和抽象方法用来定义标准, 标准:Animal这个类要求它的子类必须包含 eat 方法
abstract class Animal {
public name:string;
constructor(name:string) {
this.name = name
}
abstract eat():any; // 抽象方法不包含具体实现并且必须在派生类中实现
}
// var a = new Animal() /* 错误的写法,抽象类不能直接被实例化 */
class Dog extends Animal {
constructor(name:string) {
super(name)
}
// 抽象类的子类必须实现抽象类里面的抽象方法
eat() {
console.log(this.name + '吃骨头')
}
}
TypeScript 中的接口
接口的作用:在面向对象的变成中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到了一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规范这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescript中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
定义标准。
属性类接口
属性接口- 对json的约束
// 就是对传入对象的约束 - 属性接口
// 通过 interface 关键字定义接口
interface FullName {
firstName:string; // 注意:分号结束
secondName:string;
}
// 接口的可选属性
interface FullName {
firstName:string; // 注意:分号结束
secondName?:string; // secondName 可传可不传
}
function printName(name:FullName) {
// 必须传入对象 firstName secondName
console.log(name.firstName + '----' + name.secondName)
}
// printName({age:20,firstName: '张', secondName: '三'}) // 会出现编译报错
let obj = {age:20,firstName: '张', secondName: '三'}
printName(obj) //不会报错
函数类型接口
函数类型接口 - 对方法传入的参数以及返回值进行约束
// 加密的函数类型接口
interface encrypt {
(key:string, value:string):string
}
var md5:encrypt = function(key:string,value:string):string {
// 模拟操作
return key+value;
}
md5('name','张三')
可索引接口
可索引接口 - 数组、对象的约束 (不常用)
// 对数组的约束
interface UserArr {
[index:number]:string
}
let arr:UserArr = ['aaa', 'bbb']
// 对对象的约束
interface UserObj {
[index:string]:string
}
let obj:UserObj = {name: '张三'}
类类型接口
对类的约束 - 和抽象类相似
interface Animal {
name: string;
eat(str:string):viod;
}
// implements 关键字,实现一个类
class Dog implements Animal {
name:string;
constructor(name:string) {
this.name = name
eat() {
console.log(this.name + '吃骨头')
}
}
}
接口扩展 - 接口可以继承接口
interface Animal {
eat():void;
}
interface Person extends Animal {
work():void;
}
class web implements Person {
public name:string
constructor (name:string) {
this.name = name
}
eat() {
console.log(this.name + '喜欢吃馒头')
}
work() {
console.log(this.name + '写代码')
}
}
泛型
泛型 - 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件。
通俗理解 - 泛型就是解决 类、接口、方法的复用性,以及对不特定数据类型的支持。
泛型函数
// 只能返回string类型的数据
function getData(value:string):string {
return value
}
//同时返回 string 和 number 类型
// any 可以解决这个问题,但是放弃了类型检查
function getData(value:any):any {
return value
}
//传入number类型必须返回number,传入string必须返回string
// 泛型 - 可以支持不特定的数据类型
// T 可以写成任意其他字母,但是三个必须保持一致,且必须大写
function getData<T>(value:T):T {
return value
}
// 使用 - 指定传入的number,返回的时候也是number
getData<number>(123);
泛型类
比如有个最小堆算法,需要同时支持返回数字和字符串两种类型。 通过类的泛型来实现
// 只支持数字类型 - 如果传入字符串 那么就会编译不通过
class MinClass {
public list:number[] = [];
add (num:number) {
this.list.push(num)
}
min():number {
let minNum = this.lis[0]
if(let i = 0; i < this.list.length; i++) {
if(minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
// 类的泛型 -
class MinClass<T> {
publick list:T[]:[];
add(value:T):void {
this.list.push(value)
}
min():T {
let minNum = this.lis[0]
if(let i = 0; i < this.list.length; i++) {
if(minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
// 实例化类 并且制定了类的T代表的类型是 number
let m1 = new MinClass<number>()
// 实例化类 并且制定了类的T代表的类型是 string
let m2 = new MinClass<string>()
泛型接口
// 函数类型接口
interface ConfigFn {
(value1:string,value2:string):string
}
let setData:ConfigFn = function(value1:string,value2:string):string {
return value1 + value2
}
// 泛型接口 - 第一种写法
interface ConfigFn {
<T>(value:T):<T>
}
let getData:ConfigFn = function<T>(value:T):T{
return value
}
// 泛型接口 - 第二种写法
interface ConfigFn<T> {
(value:<T>):<T>
}
function getData<T>(value:<T>):<T> {
return value
}
let myGetData:ConfigFn<string> = getData
练习
功能:定义一个操作数据库的库 支持 Mysql Mssql MongoDb
要求1: Mysql MsSql MongoDb 功能一样 都有 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>{
add(info: any): boolean {
throw new Error("Method not implemented.");
}
update(info: any, 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.");
}
}
//定义一个操作mssql数据库的类
class MsSqlDb<T> implements DBI<T>{
add(info: any): boolean {
throw new Error("Method not implemented.");
}
update(info: any, 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)
TypeScript 模块
模块
模块 - 关于术语的一点说明:请务必注意一点,TypeScript 1.5 里术语名已经发生了变化。“内部模块” 现在称作“命名空间”。 “外部模块 ”现在简称为“模块”模块在其自身的作用域里执行,而不是在全局作用域里。
这意味着定义在一个模块里的变量、函数、类等等再模块外部是不可见得,除非你明确的使用 exports 形式之一导出他们。相反,如果想使用其他模块导出的变量、函数、类、接口等的时候,你必须要导入它们,可以使用 import 形式之一。
- export 暴露
let obj = {}
let arr = []
export {obj, arr }
export function getData():void {
}
// export default 默认导出,只能在模块中使用一次
export deault getData
// 直接引入
import getData from './index.ts'
- import 导入
import{ getdata, obj , arr } from './index.ts'
// 将 getData 重命名为 get
import{ getData as get } from './index.ts'
TypeScript 命名空间
-
命名空间 - 在代码量较大的情况下,为了避免各种变量名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。命名空间内的对象通过export导出 -
命名空间 - 模块 的区别 命名空间:内部模块,主要用于组织代码,避免命名冲突
模块:ts的外部模块的简称,侧重代码的附庸,一个模块里可能会有多个命名空间
// 命名空间 namespace
namespace A {
interface Animal {
name:string;
eat():void;
}
// 需要用 export 将命名空间内部的导出才能使用
export class Dog implements Animal {
name: string;
constructor(theName:string) {
this.name = theName
}
eat() {
console.log(this.name + '吃骨头')
}
}
export class Cat implements Animal {
name: string;
constructor(theName:string) {
this.name = theName
}
eat() {
console.log(this.name + '吃鱼')
}
}
}
namespace B {
interface Animal {
name:string;
eat():void;
}
export class Dog implements Animal {
name: string;
constructor(theName:string) {
this.name = theName
}
eat() {
console.log(this.name + '吃骨头')
}
}
export class Cat implements Animal {
name: string;
constructor(theName:string) {
this.name = theName
}
eat() {
console.log(this.name + '吃鱼')
}
}
}
// A\B 两个空间里面的内容 名字一样 但不冲突
let d = A.Dog('狗')
TypeScript 装饰器
装饰器 - 装饰器是一种特殊的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、方法、属性、参数的功能。
- 常见的装饰器有
- 类装饰器
- 属性装饰器
- 方法装饰器
- 参数装饰器
- 装饰器的写法
- 普通装饰器(无法传参)
- 装饰器工厂(可传参) 装饰器是过去几年中js最大的成就之一,已是 ES7 的标准特性之一
类装饰器
类装饰器 - 类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。传入一个参数
// 装饰器
function logClass (params:any) {
console.log(params)
// params 就是当前类
// 为当前类 扩展属性
params.prototype.apiUrl = '动态扩展属性';
params.prototype.run = function() {
console.log('我是一个run方法')
}
}
//使用装饰器
@logClass
class HttpClient {
constructor() {
}
getData() {
}
}
// 实例化
let http:any = new HttpClient()
console.log(http.apiUrl)
hettp.run()
类装饰器 - 装饰器工厂
可以传参数的类装饰器
function logClass(params:string) {
// params 就是传入的参数
return function(target:any) {
// target 就是当前使用装饰器的类
target.prototype.apiurl=params
}
}
@logClass('hello')
class HttpClient {
constructor() {
}
getData() {
}
}
let http = new HttpClient()
console.log(http.apiUrl) // hello
类装饰器 - 重载构造函数
function logClass (target:any) {
return class extends target {
apiUrl:any = '我是修改后的apiUrl';
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() // 我是修改后的apiUrl ----
属性装饰器
属性装饰器 - 属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数。
- 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 2.属性的名字
// 类装饰器
function logClass(params:string) {
return function(target:any) {
}
}
// 属性装饰器
function logProperty(params:any) {
return function(target:any,attr:any) {
// target 类的原型对象
// attr 属性的名字 - 本例中的url属性
// 将类中的 url属性值改为 传进来的 params
target[attr] = params
}
}
@logClass('xxxx')
class HttpClient {
@logProperty('https:www.baidu.com') // url的装饰器
public url:string | undefined;
constructor() {
this.api = '我是构造函数里面的apiUrl
}
getData() {
console.log(this.apiUrl)
}
}
方法装饰器
方法装饰器 - 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:
- 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 2.方法的名称
- 3.成员的属性描述符
// 装饰器
function logMethod(params:any) {
//params 参数的名字
return function(target:any, methodName:any, desc:any) {
// target 类的构造函数
// methodName 方法的名称
// desc 方法的描述
// 可以拓展属性和方法
target.apiUrl = 'xxxxx'
target.run = function() {
console.log('run')
}
// 修改装饰器的方法 把装饰器方法里面的传入的所有参数改为string类型
// 1.保存当前的方法
let oMethod = desc.value
desc.value = function(...args:any[]) {
args = args.map((value) => {
return String(value)
})
oMethod.apply(this,args)
}
}
}
class HttpClient {
public url:any | undefined
constructor() {
this.url = 'https://www.baidu.com'
}
@logMethod('http://www.google.com')
getData(...args:any[]) {
console.log(args)
console.log('我是getData里面的方法')
}
}
let http = new HttpClient()
http.getData(123, 'xxx') // ['123', 'xxx'] 我是getData里面的方法
方法参数装饰器
方法参数装饰器 - 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:
- 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 2.方法的名称
- 3.参数在函数参数列表中的索引
function logParams(params:any) {
return function(target:any,methodName:any,paramsIndex:any) {
// target 构造函数
// methodName 方法的名称
// paramsIndex 参数的索引
target.apiUrl = params
}
}
class HttpClient {
public url:any | undefined;
constructor() {
}
getData(@logParams('xxxx') uuid:any) {
console.log('我是构造函数中的getData')
}
}
let http:any = new HttpClient()
http.getData(11111)
装饰器执行的顺序
属性装饰器 -> 方法装饰器 -> 参数装饰器 -> 类装饰器
有多个同类装饰器的执行顺序是先2后1