记录从零开始的TypeScript基础
简要概括在学习过程中的重点难点
作为今后复习的文章 ✨
引入
typeScript 是 javaScript 的超集:TypeScript = JavaScript + type + (some other stuff) 其他特性
特性
- 面向对象(prototype、Function、Object)
- Class interface
- 类型检查(静态类型、强类型)
- typeScript 编译成 javaScript时便检查(提前避免bug)
- 参数自带文档内容
- IDE或者是编译工具的良好支持(自动完成提示)
安装
-
创建typeScript-demo文件 -> index.ts文件
-
全局安装typeScript
npm install typescript -g
- 编译typeScript代码文件
tsc index.ts -> 产生编译后的index.js文件
类型定义
类型定义
定义变量: [数据类型]
自动编译模式
tsc -w index.ts -> 会自动编译代码
array 数组
定义数组
let myArr: string[]
表示数组中每个元素均为字符串
let myArr: Array<number>
表示该变量为数组且每个元素均为数值型
继承变量类型
- 变量赋于过类型后
- 另一变量再赋于后
- 自动获取该变量类型
let s: String = '123'
let s1 = s
// s1变量类型 -> String
tuple 元组
定义
该集合中有多种数据类型
let my_tuple: [string, number]
第一位为字符串、第二位为数值型
也不能为空、顺序不能颠倒、仅能为两个
编译出来的javaScript为数组
function 函数
常规
// 定义add函数
function add(a: number, b: boolean)
{
return a + b
}
// 所传值为相对应的数据类型、所传的个数为两个
箭头函数
const add = (a: number, b: boolean) => {
return a + b
}
function return value
函数指定return返回值的数据类型
const add = (a: number, b: boolean): number => { return a + b }
返回内容的数据类型为number,否则报错
const add = (a: number, b: boolean):void => { return a + b }
不返回任何值(否则报错)
优势: 明返回值类型可以方便人员处理操作
function 可选参数
一般默认值、可选参数不放在第一位参数
默认值(param = value)
const add = (a: number, b: number = 10): number => { return a + b }
-
设置b默认值为10
-
可只传一个参数赋予a
-
传两个参数赋予a,覆盖b
-
少传、多传报错
可选参数(?)
const add = (a: number, b?: number = 10): number => { return a }
- b参数可选传或不传
- 函数内部需要判断b传或不传的情况
function Rest Rarameters
剩余的参数(…) -> 一般为数组
const add = (a: number, ...num: number[]): number => {
return num.reduce(function(total, num) {
return total + num
}, a) // a为初始值
}
let sum = add(20, 30, 40, 50)
// 返回第一个参数与之后参数的总和
// 该…Rest Rarameters数据类似要一致
any 数据类型
let a: any
a变量为任何类型的值
可以利用typeof 来判断数据类型来处理操作
let b: any[]
b数组内元素均为any
null、undefined 数据类型 | 连环类型
TypeScript自带的判断数据类型
const type = (value: any): string => {
return `your value is number`
}
const type1 = (value: any): string => {
return `your value is number`
}
// 可传入value数据类型为string或者number
const typ2 = (value: string | number) => {
return `your value is string or number`
}
null、undefined为一种数据类型
null、undefined -> 为所有数据类型的子集 -> 就是所有数据类型都可以传入null、undefined
执行命令
- tsc - -strictNullChecks 文件
- null、undefined不为子集
class 类
class(模板)new 对象
数据(data)行为(action)
//定义class
class Person {
firstName: string
lastName: string
}
//生成对象
let aPerson = new Person()
//赋予对象属性
aPerson.firstName = "xxxx"
methods 方法 constructor 构造器
class Movie {
name: string;
private play_count:any;
constructor(name: string = 'xxx') { // 通过构造器设置初始化内容
this.name = name;
}
display_play_count() { // methods 方法
return `${this.play_count} 次数`
}
}
Inheritance and Polymorphism 继承和多态
继承(extends)
class A extends class B
A类继承B类
- 会覆盖父类 -> 非要用时 -> 父类的 super(指向父类)
多态
let B: classB = new classB( )
classB可以当作一个类型
当B继承于A类时候可以采用
let B: classA = new classB( )
来进行子实例化父
public、private 成员可见性
成员的可见性 -> public、private -> 对方法或者属性进行修饰
-
默认为public
- public 任何属性和方法都可以在实例化的对象中调用
-
private 私有的,只有在内部对象内才能访问,生成的对象无法调用
-
使用get、set方法(通过class内部定义public的方法来调用)
-
继承来的private属性和方法 和 原本父类没有区别
protected 成员可见性
- 权限范围 public > protected > private
- 意思是public可以调用protected和private
- 用protected对方法或者属性进行修饰
- 类似保护一个基石、在基石上的就不管
- 跟private一样需内部调用
- 还可以通过继承子类调用
- 子类均可继承成员可见性属性
- 若是继承调用一个为private、一个为protected
- 利用set、get来操作private再调用
constructor 成员可见性
可被继承
成员可见性修饰构造方法
- 在constructor中调用super( ) -> 重写父类的构造函数
- 必要时需传入相关的参数
- protected或private修饰constructor时
- 当前类不能new
- 当父类申明protected时
- 子类通过super( ) 重写后new
作用
-
当不想被实例化时
- 用protected修饰
- 类似于模版
-
都不想被子类和父类实例化或继承
- 用private修饰
- 私有的
Abstract Classes 抽象类
// 定义抽象的class
abstract class Person {
name: string;
constructor(name: string) {
this.name = name;
}
display(): void {
console.log(this.name);
}
// 抽象方法 没有方法体 -> 由后面继承的来定义 -> 子类必须重写
abstract find(string): Person;
// 也有抽象属性 -> 同理子类必须定义该抽象属性
}
class Employee extends Person {
// 除了继承的属性和方法体 -> 还可以自定义扩展自身
// 调用父类的方法属性时 -> this指自身类 -> super指父类
constructor(name: string) {
super(name);
}
find(name: string): Person { //必须有抽象方法
//重写方法体
return
}
}
// let p: Person = new Person('xxx'); // 报错不能实例化抽象类
let p: Person = new Employee('xxx');
// 抽象前 -> 也可以定义成员可见属性
static 静态属性和方法
-
静态属性和方法(可以添加成员可见性修饰)
-
static age: number
= 10 -> 调用- 静态属性
- className.age(在内部调用静态属性)
-
static getStaticAge( ) { return
my age is ${ className.age }}
- 静态方法 -> 可用来对应数据库的一个表模型
- className:getStaticAge -> 调用静态方法
readonly 只读修饰符
- 在类中 readonly name: string = 'lindada'
- 修饰name为只读
- 只读属性不能修改
- 类似于常量
enum 枚举
enum DaysOfTheWeek {
SUN, MON, TUE, WED, THU, FRI, SAT
}
// 定义枚举 -> 是一个集合
let day: DaysOfTheWeek
day = DaysOfTheWeek.MON
// 使用提取出MON -> 值为 1 -> 从 0 开始的number
// 定义时枚举默认值 -> SUN = 100, MON, …
作用
- 数据库存的类型的number
- 表现出来是一个有意义的字符串
- 利用有意义可读性的字符串
- 更方便操作处理数据
ts-node、nodemon自动化
ts-node
-
npm install -D ts-node
- 安装 ts-node
-
ts-node ts文件名 -> 运行该文件 -> 自动编译和运行ts
- 把编译后的js文件放在内存中
- 若要编译后的js文件 -> 还是需要tsc 文件名
nodemon
nodemon: 监控改变并自动化重启
- npm install nodemon -S -D
- 安装 nodemon 插件
- nodemon —exec 文件名 文件目录
- nodemon —exec ts-node ./index.ts
Interfaces 接口介绍 、鸭子类型
- duck typing -> 鸭子类型
- 看起来像鸭子,我们就认为它是鸭子
- Interface -> 接口 -> 定义一个函数 -> 函数中设定一定规范 -> 操作一些特定的事
// 函数 -> value是接口类型,这个接口类型要有name属性
const sayName = (value) => {
console.log(value.name)
}
// 对象
const person = {
age: 17,
name: "dada"
}
// 对象
const bottle = {
litres: 1,
name: "漂流瓶"
}
sayName(person) // sayName(bottle)
// 有个name属性的对象就可以传过去
Interfaces 接口
//定义接口 -> 跟规则性质差不多
interface Named {
// 属性
name: string
}
// 定义为接口类型 -> 设置规则
const sayName = (value: Named) => {
console.log(value.name)
}
// 对象
const person = {
age: 27,
name: "dada"
}
// 有多个重名的接口 -> 内部规则会合并在一起
在编译时候就知道是否符合规则,输入需要有name属性
Interfaces 方法体
interface Named {
// 方法体
print(): void;
}
const sayName = (value: Named) => {
value.print()
}
// 对象
const person = {
name: "dada",
print: ()=> { console.log(this.name) }
}
sayName(person)
需要符合具有该方法体
type alias 类型别名
定义类型
type myName = string;
也可以定义为数组对象的数据类型,具有方法体等
let my_name: myName = 'dada'
class、type、interface
interface Person {
name: string;
greet( ): void;
}
// 类连接接口 -> 必须有name属性和greet( )方法体 -> 可以接多接口
class Employee implements Person {
name: string
greet( ): void { console.log('I am employee') }
}
// 实例化的对象本身也可以定义一个类型来new class
一般是 interface -> class(implements) -> new object
? 可选属性
// 可以用class 和 变量来接接口 -> 设置可选属性
interface Person {
first_name?: string
last_name: string
}
interface readonly 只读
interface Person {
readonly first_name: string
last_name: string
}
// Person接口的first_name属性为只读的 -> 当使用了这个接口时 -> 无法修改该属性
-
ReadonlyArray
- 只读数组
-
readonly 和 const 的区别
- const是常量 readonly是接口、类中使用
interface function type 函数类型
interface PrintClassBack {
// 可以简单理解为匿名函数 -> 没有函数名
(success: boolean): void
}
let printCallBack: PrintClassBack
printCallBack = ( suc: boolean ): void => { // 函数名可以加上 类型需一致
console.log("callback", suc)
}
<数据类型> - 断言
断言概念
-
typeScript中编译时 -> 编译时告诉编译器变量是哪种类型的方法
- 程序员操作时 -> 更可能知道变量是哪种类型
-
跟类型转换有点相似 -> 但只用于编译时期 -> 让操作时可能变换方法
- 但是不改变数据类型 只告诉编译器可以当作某数据类型操作
-
let x: any = "dada"
- 任意一个类型 -> 不能明确变量是哪种类型
<string>
表示编译时 -> 断言为字符串 -> 可以才可以使用字符串方法操作let s = (<string>x).substring(0, 3)
-> 截取出从0开始三个字符串
extends 接口继承
interface Person {
name: string
}
// 继承另一个接口Person -> 有name和age属性
interface Person1 extends Person {
age: number
}
extends 接口继承 - 类
// 定义一个类
class Component {
private width: number
private height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
display( ): void {
console.log(this.width)
}
}
// 接口继承类
// 可重写属性和方法 -> 对应接的class也要
interface Widget extends Component {
hide( ): void
}
// 类接接口 -> 需要继承有接口继承后的属性和方法体 -> 才能接该接口
class Button extends Component implements Widget {
hide( ): void {
console.log('hiding')
}
}
// 调用对象 -> 类接接口 -> 重写属性和方法 -> new对象操作
let w: Widget = new Button(1, 2);
w.display( )
w.hide( )
Indexable Types 索引型类型 - 1
interface States {
[index: string]: boolean; // 可以指定索引的类型
}
let s: States = {
'enabled': true,
'maximized': false
}
// 使用中括号代表索引
// s['enabled'] -> true
// s['maximized'] -> false
interface States {
[index: number]: boolean; // s[0] 调用
}
Indexable Types 索引型类型 - 2
let s2: number[] = [1, 2, 3]
// 也定义为索引类型为number的数组
// 为数组类型 -> 有其属性和方法
// s不为数组类型 -> 无其属性和方法
// 接口需要自己添加属性和方法
interface States2 {
[index: number]: boolean; // 可以指定索引的类型number
length: number;
push(): void;
pop(): boolean
}
let s3: States2 = [true, false, false]
s3.length // 4
s3.pop() // false // 数组最后一位
// 嵌套复杂结构接口
interface NestedCss {
color?: string,
nest?:{
[selector: string]: NestedCss // 调用自身
}
}
let example: NestedCss = {
color: 'red',
nest:{
'example': {
color: 'bule',
nest: {} // 嵌套
}
}
}
list 定义列表
interface Todo {
userId: number;
id: number;
used: boolean
}
let todo: Todo[ ] = [
{
"userId": 1,
"id": 1,
"used": false
},
{
"userId": 2,
"id": 2,
"used": false
},
{
"userId": 3,
"id": 3,
"used": false
}
]
Parameter Properties - 参数属性
class Person {
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
}
let p: Person = new Person("dada", 18);
class Person {
constructor(private _name: string, private _age: number) {
// 等效于上面类的代码
}
}
getter、setter - 类中读写方法
class Person {
constructor(private _name: string, private _age: number) {
}
// 读取
getName( ): string{ return this._name; }
// 写入
setName(name: string): void{ this._name = name; }
// 需要调用setName 和 getName方法
// 读取
get name( ): string{ return this._name; }
// 写入
set name(name: string) { this._name = name; } // 不需要return type
// 直接可以Person.name
}
// get 和 set会报错 -> tsc - -target es5 使用这个命令编译
let p: Person = new Person('dada', 29);
noImplicit This - this 指向
class Rectangle {
constructor(private w: number, private h: number) {}
getAreaFunction() {
return function(): number { // 返回一个匿名的function
return this.w * this.h; // this指向调用上下文
}
}
}
let rectangle: Rectangle = new Rectangle(2, 5);
// 返回function
let areaFunction = rectangle.getAreaFunction;
// 得到面积
let area = areaFunction( );
console.log(area); //NaN -> 与想得到的值不同
// this 是指向调用的上下文 -> 导致this不明确
// 解决this指向问题
// function改造成箭头函数
return function(): number { // 返回一个匿名的function
return this.w * this.h; // this指向对象
}
function type - 函数类型
// 定义一个函数的类型
// 1.
let a: any;
a = function(): void {
console.log('dada');
}
// 2.
let c: Function;
c = function(): void {
console.log('dada');
}
// 3.
let d: (params: string) => string; // 规定类型是一个function、参数为string、返回值为string
// 4.
type fun = (params: string) => string; //类型别名
let a: fun // 定义function类型
// 5.
interface fun { // function 类型的接口
(params: string): string;
}
let a: e = (pass: string) => pass; // 定义function类型
Function Overloading - 函数重载1
- 函数重载允许用相同的名字与不同的参数来创造多个函数
- 在javaScript中会被覆盖
- 先提供没有实现的函数定义列表
- 须提供所有函数列表组合的实现
// 定义函数体 -> 参数不同
function sum(x: number, y: number): number;
function sum(x: number, y: number, z: number): number; // 重载
// 上面两种函数定义的组合实现
function sum(x: number, y:number, z?:number): number {
return z ? x+y+z : x+y;
}
Function Overloading - 函数重载2
// 定义函数体 -> 参数类型不同,返回类型也不同
function divide(x: number, y: number): number;
function divide(str: string, y: number): string[ ];
function divide(x: any, y: number): any {
// 判断类型不同时 -> 不同操作
}
- 放在class中的静态方法和实例方法都可以重载方法
- 实例方法 -> new之后调用
- 静态方法 -> 直接className调用
Guards 1 守卫 - typeof
// 1. 利用 typeof 来进行守卫操作
function show(x: number | string): void {
console.log(typeof x);
if(typeof x === 'number') {
console.log('a number' + x);
} else {
console.log('a string' + x);
}
}
show('test string');
show(4);
- typeof
- undefined -> undefined
- null -> object
Guards 2 守卫 - instanced
- 利用类型断言 as 来判断该类型(class)中有无相关方法和属性
- 再抽出isClass方法 -> 方法中理由类型断言
最好使用
instanceof
- 判断是否为new出来的实例,无需断言。
if(vehicle instanceof Car) {
console.log('typ is Car');
}
strictNullChecks - 严格空值检查
- undefined -> 没有值 -> 类型为undefined -> 可以为任意类型的子类型
- null -> 值为空 -> 类型为null -> 可以为任意类型的子类型
function show(x: number | undefined | null): void {
if (x === undefined) {
console.log('value is undefined');
} else if (x === null) {
console.log('value is null');
} else {
console.log('value is number');
}
}
let x = 10;
let y;
let z = null;
show(x); // 10
show(y); // value is undefined
show(z); // value is null
- 当编译时 tsc index.ts - -strictNullChecks
- undefined和null类型不为其他任意类型的子类型
Assertion Operator - 非空检查
- 将传入字符串分为两半
- return str!.substring(0, str!.length / 2); -> ! 告诉编译器该字符串不能为空(编译时)
- 编译时需加严格空值检查
never - never类型
// 告诉编译器没有返回值的类型
function loopForever( ): never {
// 无限循环
while(true) {
}
}
// void -> 返回的类型为空 undefined
// 用于 1. 无限循环时(执行不到底部) 2.扔出异常throw时 -> 不会有返回值
ajax封装
// 定义请求数据接口
interface Config {
type: string;
url: string;
data?: string;
dataType: string;
}
// 定义ajax请求方法
function ajax(config: Config) {
let xhr = new XMLHttpRequest(); // 建立一个http对象
xhr.open(config.type, config.url, true); // 创建连接 -> true为异步
xhr.send(config.data); // 发送连接
xhr.onreadystatechange = function() { // 当状态改变时
if(xhr.readyState===4 && xhr.status===200) {
console.log('成功!');
if(config.dataType==='json') {
console.log(JSON.parse(xhr.responseText))
} else {
console.log(xhr.responseText);
}
}
}
}
// 调用ajax请求
ajax({
type: 'get',
url: 'www.baidu.com',
data: 'xxx',
dataType: 'json'
})
加密的函数类型接口
作用: 对方法传入的参数 以及返回值进行约束
// 接口约束
interface encrypt{
(key: string, value: string):string;
}
// 定义方法
let md5: encrypt = function(key: string, value: string):string{
// 模拟操作加密算法
return key+value;
}
// 使用方法
console.log(md5('name', 'zhangsan'));
let sha1: encrypt = function(key: string, value: string):string{
// 模拟操作加密算法
return key + '————' + value;
}
console.log(sha1('name', 'zhangsan'));
可索引接口 / 类类型接口
- 可索引接口 -> 对数组、对象的约束(不常用)
- 通常ts定义数组约束方法
let arr: number[ ]
- 数组内的类型必须为number
let arr1: Array<string>
- 数组内的类型必须为string
// 定义可索引接口
// 对数组的约束
interface UserArr {
[index: number]:string; // 索引值类型必须为number / 且内容必须为string
}
// 对对象的约束
interface UserObj {
[index: string]:string; // 索引值类型必须为string / 且内容必须为string
}
// 类类型接口:对类的约束 -> 与抽象类有点相似
interface Animal{ // 多态 -> 可让子类均可重写
name: string;
eat(str: string):void;
}
class Dog implements Animal{ // 对类进行接口约束
name: string;
constructor(name: string){
this.name = name;
}
eat(): void{
console.log(this.name + 'is eating…');
}
}
let d: Animal = new Dog('小黑');
d.eat('骨头');
接口的扩展、接口的继承
// 扩展 -> 接口可以继承接口
interface Animal{
eat():void;
}
interface Person extends Animal{ // extends 接口继承
work(): void;
}
// 父类
class Programmer{
public name:string;
constructor(name: string) {
this.name = name
}
coding(){
console.log(this.name + 'is coding…')
}
}
// 继承父类和接Person接口
class Web extends Programmer implements Person{
public name:string;
constructor(name: string) {
this.name = name
}
// Person接口的方法体
eat(){
console.log(this.name + 'eating');
}
work(){
console.log(this.name + 'working');
}
// 继承了Programmer类的方法体 -> coding()
}
泛型接口
// 方法1
interface ConfigFn{
// 泛型方法约定
<T>(value1:T, value2:T):T;
}
// 实现泛型方法接口
let setData:ConfigFn = function<T>(value1:T, value2:T):T{
return value2+value2;
}
// 调用时指定方法
setData<string>('name', '张三'); // 指定是string类型
// 方法2
interface ConfigFn<T>{
// 泛型方法约定
<T>(value1:T, value2:T):T;
}
// 定义泛型方法
function getData<T>(value1:T, value2:T):T{
return value2+value2;
}
// 实例化指定类方法 并 接接口
let myGetData:ConfigFn = getData;
// 调用方法
myGetData<string>('20', '30')
把类作为参数类型的泛型类
泛型:可以帮助我们避免重复的代码以及对不特定数据类型的支持
// 比如有个最小堆算法 普通类型 -> T泛型类
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; this.list.length; i++) {
if(minNum > this.list[i]) {
minNum = this.list[i];
}
}
return minNum;
}
}
// 调用泛型类 -> 指定传入类型
let m1 = new MinClass<number>( ); // 实例化类,指定类的T代表类型为number
m1.add(11);
m1.add(22);
let m2 = new MinClass<string>( ); // 实例化类,指定类的T代表类型为string
m2.add('a');
m2.add('c');
// 定义一个User类 -> 映射数据库表字段
class User{
userName: string | undefined;
password: string | undefined;
}
// 定义一个文章分类类
class ArticleCate{
title: string | undefined;
desc: string | undefined;
constructor(params: { // 要求实例化时必须传入参数
title:string | undefined,
desc:string | undefined
}) {
this.title = params.title;
this.desc = params.desc;
}
}
// 定义一个MySqlDb类 -> 操作数据库的泛型类
class MySqlDb<T> {
add(value:T):boolean{ // 将类当作参数类型来验证
console.log(value);
return true;
}
}
// 实例化后再传值
let user = new User( );
user.userName = '张三';
user.password = '123456';
let Db = new MySqlDb<User>(); // 校验传入的是User类
// 将User类作为参数传入MySqlDb中
Db.add(user)
// 实例化articleCate时传值
let a = new ArticleCate({
title: '分类',
desc: '泛类型类'
});
let Db = new MySqlDb<ArticleCate>(); // 校验传入的是ArticleCate类
Db.add(a)
TypeScript 封装统一操作数据库
// 定义一个操作数据库的库 -> 可以支持Mysql、Mssql、Mongodb
// 需要约束统一规范、以及代码重用 -> 接口(统一规范)、重用(泛型)
// 定义数据库操作接口
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:T):boolean{
return true;
}
update(info:T, id:number):boolean{
return true;
}
delete(id:number):boolean{
return true;
}
get(id:number):any[]{
return [true];
}
}
// 同理定义MsSqlDb和MongoDb的类
// 增加Mysql数据库
// 定义一个User类和数据库表做映射
class User{
userName:string | undefined;
password:string | undefined;
}
let u = new User();
u.userName = '张三';
u.password = '123456';
let mysql = new MysqlDb<User>(); // 类作为参数来约束数据传入的类型
mysql.add(u); // 传入u实例数据
// 切换对应数据库就切换类
TypeScript模块化
// 文件1
let dbUrl = 'xxxx';
// 暴露方法1
export function getData():any[]{
console.log('获取数据库的数据')
return [
{
title: 'aaa'
}
]
}
// 暴露方法2
export{
dbUrl,
getData
}
// 文件2
import { getData as get } from './xxx文件' // 引入方法
getData( );
get( ); // 相同重命名
// export default getData -> 默认暴露
// import getData from './文件1' -> 默认引入
命名空间模块化
命名空间:避免各变量命名冲突,可将相似功能的函数、类、接口等放置命名空间。
命名空间和模块的区别:
- 命名空间
- 内部模块
- 主要用户组织代码,避免命名冲突
- 模块
- ts外部模块,侧重代码的复用
- 一个模块里可能会有多个命名空间
// 模块文件A
export namespace A { // 命名空间默认私有 -> 可暴露
export interface Animal{ }; // 使用内部接口和方法需要暴露
export class Dog implements Animal{};
}
// 不同命名空间
namespace B {
interface Animal{ };
class Dog implements Animal{};
}
// 引入模块文件A
import { A, B } from './modules/a';
let a = new A.Dog('小黑');
a.eat(); // 调用
类装饰器
- 装饰器
- 是JS过去几年中最大的成就之一,是ES7标准特性之一。
- 实际是一个方法,可以注入到类、方法、属性参数上来扩展相关的功能。
- 常见的装饰器 -> 类装饰器、属性装饰器、方法装饰器、参数装饰器
// 定义普通装饰器 -> 不能传入参数(默认为当前类)
function logClass(params:any){
console.log(params); // params 就是当前类
params.prototype.apiUrl = 'xxxx'; // 给这个类动态扩展一个apiUrl属性
params.prototype.run = () => {}; // 给这个类动态扩展一个run方法
}
@logClass // 调用装饰器 -> 动态扩展类的功能(不能加分号)
class HttpClient {
constructor() {
}
getData() {
}
}
// 实例化
let http:any = new HttpClient();
console.log(http.apiUrl);
// 定义装饰器工厂 -> 可传入参数
function logClass(params:string){
return function(target:any){ // 类装饰器接收当前类的一个参数
console.log(target); // 当前类
console.log(params); // 'hello'
}
// 同样可以动态扩展target当前类的功能
}
@logClass('hello') // 传入参数
class HttpClient {
constructor() {
}
getData() {
}
}
// 实例化
let http:any = new HttpClient();
console.log(http.apiUrl);
// 类装饰器 -> 用装饰器修改当前类的构造函数
function logClass(target:any){
console.log(target);
return class extends HttpClient {
apiUrl:any = '我是装饰器修改后的apiUrl';
getData(){
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 logProperty(params:any) {
return function(target:any, attr:any){ // 属性装饰器接收两个参数
console.log(target); // http(实例化的对象)
console.log(attr); // apiUrl
target[attr] = params; // 将对象的attr属性修改成xxxxx
// target[attr] === params.prototype.attr
// 因为target为当前原型对象
}
}
@logProperty // 调用类装饰器
class HttpClient {
@logProperty('xxxxx')
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '我是构造函数里的apiUrl';
}
getData() {
console.log(this.apiUrl);
}
}
let http = new HttpClient();
http.getData(); // '我是装饰器修改后的apiUrl'
方法装饰器
方法装饰器:用来监视,修改或者替换方法定义
方法装饰器3个参数
- 类的构造函数/类的实例化原型对象
- 成员名字
- 成员的属性描述符
// 定义方法装饰器
function logMethod(params:any) {
return function(target:any, methodName:any, desc:any){ // 接收三个参数
console.log(target); // 类的原型对象
console.log(methodName); // getData
console.log(desc); // {方法描述信息}
// 扩展当前类的属性和方法
target.apiUrl = 'xxx';
target.run = function() {
console.log('run');
}
// 修改装饰器的当前方法
let oMethod = desc.value;
desc.value = function(...args:any[]){ // 替换且将接收到的参数放入数组
args = args.map((value) => {
return String(value); // 修改该方法转换为String类型
})
// 再替换回原来方法 -> 变为修改当前方法
oMethod.apply(this, args);
}
}
}
class HttpClient {
@logMethod('xxxxx')
public apiUrl:string | undefined;
constructor() {
this.apiUrl = '我是构造函数里的apiUrl';
}
@logMethod('www.baidu.com')
getData(...args:any[]) {
console.log(args) // ["123", "xxx"] // 均修改为string类型
console.log(this.apiUrl);
}
}
let http = new HttpClient();
http.getData(123, 'xxx'); // '我是装饰器修改后的apiUrl'
- 还有方法参数装饰器:为方法参数动态增加元素
- 类的构造函数/类的实例化原型对象
- 参数名字
- 参数再函数参数列表中的索引
- 调用 -> getData(@logParams a:any, b:any) { };
- 可以装饰多个装饰器
- 多个同样的装饰器 -> 由后至前
- 装饰器执行顺序
- 属性 > 方法 > 方法参数 > 类
keyof
key:提供正确的属性名称
class A {
x: number = 5;
}
let y: keyof A;
y = 'x'; // y只能是class A的一个属性名
class Test {
x: number = 6;
}
function getProp(a: keyof Test, test: Test): any {
return test[a];
}
let t: Test = new Test();
let prop = getProp('x', t);
console.log(prop); // 6
class B {
y: keyof A; // y只能是x
}
tsconfig.js - 配置文件
- 配置文件:约束ts文件
- 在终端初始化:
tsc - -init
- 在根目录生成了
tsconfig.json
配置文件 - 使用该配置文件,在终端 tsc,不会忽略代码 查看是否符合配置。
lodash - 使用第三方插件
引入 lodash 现代化的工具库:操作数组、函数等
- 初始化项目
npm init
- 主要生成package.json文件
npm install lodash -S
- 进入文件中导入
import * as _ from 'lodash';
- 若是第三方插件不是用ts编写
- 编译器不会提示和补全代码
- 去找相关库的ts文档(是声明相关库的ts源码)
- 可能还需要引入源码库
- 若是第三方插件不是用ts编写
结尾
简单写一下个人在学习TypeScript遇到的重点和难点,若有错误的地方,还望各位小伙伴指出噢。
要是这篇文章对您有那么一丢丢的帮助,点一个赞👍 吧