TypeScript

一、概述

TypeScript(简称 TS)是微软公司开发的一种基于 JavaScript (简称 JS)语言的编程语言。它的目的并不是创造一种全新语言,而是增强 JavaScript 的功能,使其更适合多人合作的企业级项目。

TypeScript 可以看成是 JavaScript 的超集(superset),即它继承了后者的全部语法,所有 JavaScript 脚本都可以当作 TypeScript 脚本(但是可能会报错),此外它再增加了一些自己的语法。

TypeScript 对 JavaScript 添加的最主要部分,就是一个独立的类型系统。

二、TypeScript中的基本类型

2.1.TypeScript 包含的基础类型如下:

类型例子描述
number 1, -33, 2.5 任意数字
string 'hi', "hi", hi 任意字符串
boolean true、false 布尔值true或false
字面量 其本身 限制变量的值就是该字面量的值
any * 任意类型
unknown * 类型安全的any
void 空值(undefined) 没有值(或undefined)
never 没有值 不能是任何值
object {name:'孙悟空'} 任意的JS对象
array [1,2,3] 任意JS数组
tuple [4,5] 元素,TS新增类型,固定长度数组
enum enum{A, B} 枚举,TS中新增类型

2.2.类型声明

类型声明是TS非常重要的一个特点;

  • 通过类型声明可以指定TS中变量(参数、形参)的类型;
  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错;
  • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值;

语法:

let 变量名: 类型;
​
let 变量名: 类型 = 值;
​
function fn(参数: 类型, 参数: 类型): 类型{
    ...
}

2.3.自动类型判断

  • TS拥有自动的类型判断机制

  • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型

  • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明

2.4.语法说明

官方提供的 Playground进行ts的在线练习https://typescript.p2hp.com/play?#code/Q

1)布尔值

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean(其它语言中也一样)。

let isDone: boolean = false;

2)数字

TypeScript和JavaScript一样,里面所有数字都是浮点数(不分什么整数、浮点数之类的)。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,Typescript还支持ECMAScript 2015中引入的二进制和八进制字面量。

let decLiteral: number = 6; //十进制
let hexLiteral: number = 0xf00d;  // 十六进制
let binaryLiteral: number = 0b1010;// 二进制
let octalLiteral: number = 0o744; // 八进制

3)字符串

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。使用 string表示文本数据类型。 和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串。

let uname: string = "Augus";
uname = "ls";

还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围( ``),并且以${expr}`这种形式嵌入表达式

let info:string = `Hi my name is ${uname}. I'll be + ${age+1} + years old next month.`;
console.log(info)

下面定义的方式效果相同:

let info:string = `Hi my name is ${uname}. I'll be + ${age+1} + years old next month.`;
console.log(info)

4)数组

TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[],表示由此类型元素组成的一个数组:

let list: number[] = [100, 200, 300];

第二种方式是使用数组泛型,Array<元素类型>

let list: Array<number> = [11, 22, 33];

5)元组 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为stringnumber类型的元组。

 
//定义元组
let x: [string, number];
//正确的赋值,需要跟上面的类型保持一致
x = ['helloworld', 23]
console.log(x)
//错误的赋值方式
x = ['helloworld', "your"]
console.log(x)
 

TS新增类型,固定长度数组,注意当访问一个已知索引的元素,会得到正确的类型:

 
//定义元组
let x: [string, number];
//正确的赋值,需要跟上面的类型保持一致
x = ['helloworld', 23]
console.log(x)
//错误的赋值方式
​
console.log(x[0])
console.log(x[1])
//索引越界, 索引是从0开始, 会出现undefined 
console.log(x[2])
 

注意:索引是从0开始,所以从左往右,最后一个元素的索引是元组的长度减一

//字符串可以赋值给(string | number)类型
x[2] = "world";
console.log(x[2])

6)object类型

object`是一个对象,在`ts`中定义对象类型的语法为:`let 变量名 :object = { }``在object类型中,对象内部定义的值是不受类型约束的,只要是一个object类型即可,例如:
 
let obj: object = {
    name: '张三', //字符串
    age: 23 //数字
}
console.log(obj)
 

object 类型在函数中的使用

function getObj(obj: object): object{
    console.log(obj);
    return {name: "岳不群", message: "华山派掌门人"}
}

定义object类型,传入的值必须是object类型,否则会报错

 
//由于定义的时候参数是object类型,调用的时候参数也是object类型
console.log(getObj({name:"左冷禅", message:"嵩山派掌门人"}))
​
//这是错误调用
//console.log(getObj(123))
 

某些方法本质是一个对象,所以也可以传入,比如 new String

//正确使用
console.log(getObj(new String('暮霭沉沉楚天阔')));

7)枚举

enum类型是对JavaScript标准数据类型的一个补充。 使用枚举类型可以为一组数值赋值。

 
//定义枚举enum
enum  Color {Red, Green, Blue};
​
//输出是对应枚举值的索引
let c1: Color = Color.Green;
console.log(c1)
 

默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。 例如,将上面的例子改成从 100开始编号:

 
//定义枚举enum
enum  Color {Red=100, Green, Blue};
​
//输出是对应枚举值的索引
let c1: Color = Color.Green;
console.log(c1)
 

输出的c1的值为101

或者,全部都采用手动赋值:这时候输出的c1的索引是8

 
//定义枚举enum
enum  Color {Red=1, Green=8, Blue=20};
​
//输出是对应枚举值的索引
let c1: Color = Color.Green;
console.log(c1)
 

枚举类型提供的一个功能是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,可以查找相应的名字:

 
//定义枚举enum
enum  Color {Red=1, Green, Blue};
​
//根据索引获取值
let colorName: string = Color[2];
console.log(colorName);
 

8)任意值

有时候会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

 
//数字
let notSure: any=4;
console.log(notSure)
​
//字符串
notSure = "my name is Augus"
console.log(notSure)
​
//布尔值
notSure = false;
console.log(notSure)
当只知道一部分数据的类型时,any类型也是有用的。 假如:有一个数组,它包含了不同的类型的数据:
​
//定义一个数组
let list: any[] = [1, true, "free"];
​
//设置索引1的值修改为100
list[1] = 100;
console.log(list)
 

9)空值

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

 
//定义函数,返回值void表示为空
function getUser(): void{
    alert("this is Augus")
}
​
//调用函数
getUser()
 

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefinednull

let hi: void = undefined;
console.log(hi)

10)Null 和 Undefined

TypeScript里,undefinednull两者有自己的类型分别叫做undefinednull。 和 void相似,它们的本身的类型用处不是很大:

 
let a1: undefined = undefined;
console.log(a1)
​
let b1: null = null;
console.log(b1)
 

默认情况下nullundefined是所有类型的子类型。 就是说你可以把 nullundefined赋值给number类型的变量

11)Never

never类型表示的是那些永不存在的值的类型。

never 类型是 TypeScript 中的底层类型。它在以下情况中很好的被使用:

  • 一个从来不会有返回值的函数,即死循环(如:如果函数内含有 while(true) {});

  • 一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') },foo 的返回类型是 ne

下面是一些返回never类型的函数:

 
//永远不会返回结果 
function foo(): never {
    // 死循环 
    while(true) {}
}
​
//总是会抛出错误
function bar(): never {
    throw new Error()
} 
 

注意:never 仅能被赋值给另外一个 never 类型,因此可以用它来进行编译时的全面的检查

12)字面量

在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型。对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型:

let str: 'hello world' = 'hello world';
let num: 996 = 996;
let bool: true = true

①.字面量类型的使用 字符串字面量:字符串字面量类型其实就是字符串常量,与字符串类型不同的是它是具体的值:

 
let s1: "success" | "error" | "pending";
s1 = "success"; // 合法
s1 = "error"; // 合法
s1 = "pending"; // 合法
s1= "in progress"; // 错误,只能取指定的字面量值
 

②.数字字面量类型:

 
let age: 18 | 21 | 30;
age = 18; // 合法
age = 21; // 合法
age = 30; // 合法
age = 25; // 错误,只能取指定的字面量值
 

③.布尔字面量类型:

let isTrue: true;
isTrue = true; // 合法
isTrue = false; // 错误,只能取指定的字面量值

④.结合联合类型使用字面量类型

 
//定义了一个类型别名 Status,它是一个字符串字面量类型,只能取值为 “active” 或 "inactive":
type Status = "active" | "inactive";
//声明了一个变量 userStatus,它的类型是 Status,即只能取值为 “active” 或 "inactive":
let userStatus: Status;
userStatus = "active"; // 合法
userStatus = "inactive"; // 合法
userStatus = "pending"; // 错误,只能取指定的字面量值
 

通过使用字面量类型,我们可以限制变量的取值范围,提高代码的可读性和可维护性。请注意,字面量类型在某些情况下可能需要与联合类型或类型别名一起使用,以便更灵活地定义变量的类型。

2.5.类型断言

TypeScript中的类型断言是一种将变量或表达式的类型强制转换为开发者指定的类型的方式。可以使用尖括号(<>)语法或者as语法进行类型断言。

使用尖括号语法的类型断言:

let str: any = "hello";
let len1: number = (<string>str).length;

注意:使用了尖括号形式的类型断言 <string>str,然而,这种形式的类型断言在 React/JSX 代码中容易与 JSX 的语法产生冲突,因此会出现错误。

使用as语法的类型断言(推荐):

let value: any = "Hello World";
let length: number = (value as string).length;
console.log(length); // 输出:11

使用场景:

  1. 消除类型检查错误:有些情况下,开发者明确知道某个变量的类型,但是TypeScript的类型检查器并不能推断出这个类型。这时可以使用类型断言,将变量的类型强制转换为开发者指定的类型,从而消除类型检查错误。

  2. 处理联合类型:当一个变量的类型是多种类型的联合类型时,如果开发者想要使用其中一个类型的属性或方法,可以使用类型断言将其转换为该类型,以便进行后续操作。

  3. 处理any类型:有时开发者需要使用any类型的变量,但是any类型会降低代码的类型安全性。如果能够明确知道该变量的类型,可以使用类型断言将其转换为该类型,以提高代码的类型安全性。

需要注意的是,尽管使用类型断言可以让编译器相信一个变量的类型,但是在运行时尝试访问一个变量不存在的属性或方法时,仍然会引发运行时错误。因此,在使用类型断言时,应该谨慎地确保变量的类型与断言的类型相符,并尽可能使用类型注释或类型推论来减少使用类型断言的情况。

三、TypeScript条件语句

条件语句用于基于不同的条件来执行不同的动作。TypeScript 条件语句是通过一条或多条语句的执行结果(True 或 False)来决定执行的代码块。在 TypeScript 中,我们可使用以下条件语句:

  • if 语句(单分支):只有当指定条件为 true 时,使用该语句来执行代码

  • if...else 语句(双分支):当条件为 true 时执行代码,当条件为 false 时执行其他代码

  • if...else if....else 语句(多分支):使用该语句来选择多个代码块之一来执行

switch 语句:使用该语句来选择多个代码块之一来执行

3.1.单分支

TypeScript if 语句由一个布尔表达式后跟一个或多个语句组成。语法格式如下所示:

if(boolean_expression){
    # 在布尔表达式 boolean_expression 为 true 执行
}

如果布尔表达式 boolean_expression为 true,则 if 语句内的代码块将被执行。如果布尔表达式为 false,则 if 语句结束后的第一组代码(闭括号后)将被执行。实例

let num:number = 5
if (num > 0) {
    console.log("数字是正数")
}

执行以上 JavaScript 代码,输出结果为:数字是正数

3.2.双分支

一个 if 语句后可跟一个可选的 else 语句,else 语句在布尔表达式为 false 时执行。语法如下所示:

 
if(boolean_expression){
    # 在布尔表达式 boolean_expression 为 true 执行
}else{
    # 在布尔表达式 boolean_expression 为 false 执行
}
 

如果布尔表达式 boolean_expression 为 true,则执行 if 块内的代码。如果布尔表达式为 false,则执行 else 块内的代码。案例如下:

 
var num:number = 12; 
if (num % 2==0) { 
    console.log("偶数"); 
} else {
    console.log("奇数"); 
}
 

执行以上 JavaScript 代码,输出结果为:10

3.3.多分支

if…else if…else 语句在执行多个判断条件的时候很有用。语法格式如下所示:

 
if(boolean_expression 1) {
    # 在布尔表达式 boolean_expression 1 为 true 执行
} else if( boolean_expression 2) {
    # 在布尔表达式 boolean_expression 2 为 true 执行
} else if( boolean_expression 3) {
    # 在布尔表达式 boolean_expression 3 为 true 执行
} else {
    # 布尔表达式的条件都为 false 时执行
}
 

注意:

  • 一个 if 判断语句可以有 0 或 1 个 else 语句,它必需在 else…if 语句后面。

  • 一个 if 判断语句可以有 0 或多个 else…if,这些语句必需在 else 之前。

  • 一旦执行了 else…if 内的代码,后面的 else…if 或 else 将不再执行。

案例如下:

 
let num:number = 2 
if(num > 0) { 
    console.log(num+" 是正数") 
} else if(num < 0) { 
    console.log(num+" 是负数") 
} else { 
    console.log(num+" 不是正数也不是负数") 
}
 

执行以上 JavaScript 代码,输出结果为:2 是正数

3.4.swith语句

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。switch 语句的语法:

 
switch(expression){
    case constant-expression  :
       statement(s);
       break; /* 可选的 */
    case constant-expression  :
       statement(s);
       break; /* 可选的 */
  
    /* 您可以有任意数量的 case 语句 */
    default : /* 可选的 */
       statement(s);
}
 

switch 语句必须遵循下面的规则:

  • switch 语句中的 expression 是一个常量表达式,必须是一个整型或枚举类型。

  • 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。

  • case 的 constant-expression 必须与 switch 中的变量具有相同的数据类型,且必须是一个常量或字面量。

  • 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。

  • 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。

  • 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。

  • 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。

案例如下:

 
let grade:string = "A";
​
switch(grade){
    case "A":{
        console.log("优")
        break;
    }
    case "B": { 
        console.log("良"); 
        break; 
    } 
    case "C": {
        console.log("及格"); 
        break;    
    } 
    case "D": { 
        console.log("不及格"); 
        break; 
    }  
    default: { 
        console.log("非法输入"); 
        break;              
    }
}
 

执行以上 JavaScript 代码,输出结果为:优

四、TypeScript循环语句

4.1.for 循环

1)语句描述

TypeScript for 循环用于多次执行一个语句序列,简化管理循环变量的代码。

2)语法

语法格式如下所示:

for (init; condition; increment ){
  statement(s);
}

下面是 for 循环的控制流程解析:

  • init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。

  • 接下来,会判断 condition。如果为 true,则执行循环主体。如果为 false,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。

  • 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。

  • 条件再次被判断。如果为 true,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为 false 时,for 循环终止。

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。condition 可以是任意的表达式,当条件为 true 时执行循环,当条件为 false 时,退出循环。

3)实例

以下实例计算 5 的阶乘, for 循环生成从 5 到 1 的数字,并计算每次循环数字的乘积。

 
let num:number = 5;
let i:number;
let factorial = 1;
​
for(i = num; i>=1; i--){
    factorial *= i;
}
​
console.log(factorial)
 

4.2.for...in 循环

1) 语句描述

for...in 语句用于一组值的集合或列表进行迭代输出。

2)语法

语法格式如下所示:

for (let val in list) {
    //语句
}

let需要为 string 或 any 类型。

3)实例

 
let j:any; 
let n:any = "a b c" 
 
for(j in n) {
    console.log(n[j])  
}
 

注意:上面的循环取出的是n中每一个元素的是索引下标

4.3.for…of 、forEach、every循环

1)语句描述

此外,TypeScript 还支持 for…of 、forEach、every 循环。for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。

2)TypeScript for...of 循环

let someArray = [1, "hello world", false];
​
for (let entry of someArray) {
  console.log(entry); // { 1, "hello world", false }

forEach、every 和 some 是 JavaScript 的循环语法,TypeScript 作为 JavaScript 的语法超集,当然默认也是支持的。因为 forEach 在 iteration 中是无法返回的,所以可以使用 every 和 some 来取代 forEach。

3)TypeScript forEach 循环

 
let list = [4, 5, 6]
​
list.forEach((val, idx, array)=>{
    console.log(val);//val当前值
    console.log(idx);//当前index
    console.log(array);//循环的数组
    
})
 

4)TypeScript every 循环

 
let list = [4, 5, 6]
​
list.every((val, idx, array)=>{
    console.log(val);//val当前值
    console.log(idx);//当前index
    console.log(array);//循环的数组
    
    //返回 false 将退出迭代,只有返回为true的时候才会继续,
    return true;
})
 

4.4.while 循环

1)语句描述

while 语句在给定条件为 true 时,重复执行语句或语句组。循环主体执行之前会先测试条件。

2)语法

语法格式如下所示:

while(condition){
  statement(s);
}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。condition 可以是任意的表达式,当条件为 true 时执行循环。 当条件为 false 时,程序流将退出循环。

3)实例

 
let num:number = 5;
let factorial:number = 1;
​
while(num >= 1){
    factorial = factorial * num;
    num--
}
# 计算的是5*4*3*2*1
console.log("5的阶乘为:"+factorial)
 

4.5. do...while 循环

1)语句描述

不像 for 和 while 循环,它们是在循环头部测试循环条件。do...while 循环是在循环的尾部检查它的条件。

2)语法

语法格式如下所示:

do{
  statement(s);
}while( condition );

请注意,条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。如果条件为 true,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为 false 为止。

3)实例

 
let n:number = 10;
do {
  console.log(n);
  n--;
} while(n>=0);
 

4.6. break 语句

1)语句描述

break 语句有以下两种用法:

  • 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。

  • 它可用于终止 switch 语句中的一个 case。

如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。

2)语法

语法格式如下所示:

break;

3)实例

 
let i:number = 1;
​
while(i<=10){
    if(i % 5 == 0){
        console.log("在 1~10 之间第一个被 5 整除的数为 : "+i)
        //找到第一个后退出循环
        break 
    }
    i++
}//输出5然后程序执行结束
 

4.7. continue 语句

1)语句描述

continue 语句有点像 break 语句。但它不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 while 和 do...while 循环,continue 语句重新执行条件判断语句。

2) 语法

语法格式如下所示:

continue;

3)实例

 
let num:number = 0;
let count:number = 0;
​
for(num=0; num<=20; num++){
    if(num % 2 == 0){
        continue;
    }
    //每次增加1
    count++
}
​
console.log ("0 ~20 之间的奇数个数为: "+count)
 

4.8. 无限循环

1) 语句描述

无限循环就是一直在运行不会停止的循环。 for 和 while 循环都可以创建无限循环。

2)语法

  • for 创建无限循环语法格式:

for( ; ;) { 
   // 语句
}

注意:for循环的三个部分都被省略了,因此循环将一直执行下去,形成一个类似无限循环的效果。你可以在循环体内编写你需要执行的代码逻辑。

  • 使用while循环,并设置一个始终为true的条件。以下是一个示例:

while (true) {
  // 无限循环的代码逻辑
}

五、TypeScript变量和常量

5.1.什么是常量和变量?

  • 常量就是 程序运行中 不可以改变的量(数据)
  • 变量就是 程序运行中 可以改变的量(数据)

5.2.为什么一定要声明变量?

因为不声明的变量被编译器识别的代价高于声明后识别的代价。从编译速度来看,声明即有利于统一管理,也有利于编程人检查代码。

5.3.TypeScript 变量的命名规则是什么?

  • 变量名称可以包含数字和字母。
  • 除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。
  • 变量名不能以数字开头。

5.4.变量的声明方式是什么?

  • 命名方法: 小驼峰式命名法。(除第一个单词之外,其他单词首字母大写。)
  • 命名规范:前缀为形容词。(函数前缀为动词, 以此来区分函数和变量)
  • 支持ES6的环境下,禁止使用var定义变量。
  • 变量命名要求见名知意。
// 变量命名方式
let maxCount : number = 1;
let tableTitle : string = '字符串';

5.5.常量的声明方式是什么?

定义用const。
命名方法: 大驼峰式命名法。(所有单词首字母全部大写。)如果有多个单词,用“_”连接。

// 常量命名方式
const MAX_COUNT : number = 1;
const AAA : string = '字符串';

5.6.变量的作用域?

变量的作用域是变量的生命期和作用范围,它取决于变量的声明位置。TypeScript 有以下几种作用域:

  • 全局作用域 − 全局变量定义在程序结构的外部,它可以在你代码的任何位置使用。
  • 类作用域 − 这个变量也可以称为 字段。类变量声明在一个类里头,但在类的方法外面。 该变量可以通过类的对象来访问。类变量也可以是静态的,静态的变量可以通过类名直接访问。
  • 局部作用域 − 局部变量,局部变量只能在声明它的一个代码块(如:方法)中使用。

5.7.letvar 和 const 关键字的区别个联系

在 TypeScript 中,letvar 和 const 是用来声明变量的关键字,它们之间有一些区别和联系。、

1)var

  • var 是在 ES5 中引入的声明变量的关键字。
  • 使用 var 声明的变量存在变量提升,即可以在声明之前访问到变量,但其值为 undefined
  • var 声明的变量作用域是函数作用域,而不是块级作用域。

let

  • let 是在 ES6 中引入的声明变量的关键字。
  • 使用 let 声明的变量不存在变量提升,即在声明之前访问会报错。
  • let 声明的变量作用域是块级作用域,例如在 {} 内声明的变量只在该块内有效。

const

  • const 也是在 ES6 中引入的声明变量的关键字,用于声明常量,其值在声明后不可修改。
  • 使用 const 声明的变量必须进行初始化赋值,且不能再次赋值。
  • const 声明的变量同样存在块级作用域。

联系:

  • let 和 const 都是在 ES6 中引入的,它们都具有块级作用域和不存在变量提升的特性。
  • const 用于声明常量,其值不可修改,而 let 用于声明可变的变量。
  • var 在 ES6 中已经不推荐使用,因为它存在变量提升和函数作用域的特性,容易导致意外的行为。

总之,推荐在 TypeScript 中优先使用 let 和 const 来声明变量,避免使用 varlet 用于声明可变的变量,而 const 用于声明常量。

六、TypeScript运算符

运算符用于执行程序代码运算,会针对一个以上操作数项目来进行运算。例如:2+3,其操作数是2和3,而运算符则是“+”。运算符大致可以分为5种类型:算术运算符、连接运算符、关系运算符、赋值运算符、逻辑运算符等。

6.1.算术运算符

假定 y=5,下面的表格解释了这些算术运算符的操作:

运算符描述例子x 运算结果y 运算结果
+ 加法 x=y+2 7 5
- 减法 x=y-2 3 5
* 乘法 x=y*2 10 5
/ 除法 x=y/2 2.5 5
% 取模(余数) x=y%2 1 5
++ 自增 x=++y、 x=y++ 6 、5 6、 6
-- 自减 x=--y 、x=y-- 4 、5 4、4

++y 和 y++ 都是 JavaScript 中的自增运算符,它们的区别在于它们的执行顺序和返回值。

  • ++y 是前置自增运算符,它会先将变量 y 的值加 1,然后返回增加后的值。换句话说,++y 先执行加法操作,再返回结果。
  • y++ 是后置自增运算符,它会先返回变量 y 的值,然后再将 y 的值加 1。换句话说,y++ 先返回结果,再执行加法操作。

下面是一个示例来说明它们的区别:

 
let y = 5;
let a = ++y;
console.log(a); // 输出 6
console.log(y); // 输出 6

let x = 5;
let b = x++;
console.log(b); // 输出 5
console.log(x); // 输出 6
 

注意:

  • 在第一个示例中,++y 先将 y 的值加 1,然后将增加后的值赋给 a,所以 a 的值为 6,y 的值也为 6。
  • 在第二个示例中,x++ 先返回 x 的值(为 5),然后将 x 的值加 1,所以 b 的值为 5,x 的值为 6。
  • 总结起来,++y 和 y++ 的区别在于前者先执行加法操作再返回结果,后者先返回结果再执行加法操作。

6.2.逻辑运算符

逻辑运算符用于测定变量或值之间的逻辑。给定 x=6 以及 y=3,下表解释了逻辑运算符:

运算符描述例子
&& and (x < 10 && y > 1) 为 true
|| or (x==5 || y==5) 为false
! not !(x==y) 为true

6.3.关系运算符

关系运算符用于计算结果是否为 true 或者 false。x=5,下面的表格解释了关系运算符的操作:

运算符描述比较返回值
== 等于 x==8 / x==5 false / true
!= 不等于 x!=8 true
> 大于 x>8 false
< 小于 x<8 true
>= 大于或等于 x>=8 false
<= 小于或等于 x<=8 true

6.4.按位运算符

运算符 描述 例子 类似于 结果 十进制

运算符描述例子类似于结果十进制
& AND,按位与处理两个长度相同的二进制数,两个相应的二进位都为 1,该位的结果值才为 1,否则为 0。 x = 5 & 1 0101 & 0001 0001 1
| OR,按位或处理两个长度相同的二进制数,两个相应的二进位中只要有一个为 1,该位的结果值为 1。 x = 5 | 1 0101 | 0001 0101 5
~ 取反,取反是一元运算符,对一个二进制数的每一位执行逻辑反操作。使数字 1 成为 0,0 成为 1。 x = ~ 5 ~0101 1010 -6
^ 异或,按位异或运算,对等长二进制模式按位或二进制数的每一位执行逻辑异按位或操作。操作的结果是如果某位不同则该位为 1,否则该位为 0。 x = 5 ^ 1 0101 ^ 0001 0100 4
<< 左移,把 << 左边的运算数的各二进位全部左移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补 0。 x = 5 << 1 0101 << 1 1010 10
>> 右移,把 >> 左边的运算数的各二进位全部右移若干位,>> 右边的数指定移动的位数。 x = 5 >> 1 0101 >> 1 0010 2
>>> 无符号右移,与有符号右移位类似,除了左边一律使用0 补位。 x = 2 >>> 1 0010 >>> 1 0001 1

6.5.赋值运算符

赋值运算符用于给变量赋值。给定 x=10 和 y=5,下面的表格解释了赋值运算符:

运算符
例子
实例
x值
= (赋值)
x = y
x = y
x = 5
+= (先进行加运算后赋值)
x += y
x = x + y
x = 15
-= (先进行减运算后赋值)
x -= y
x = x - y
x = 5
*= (先进行乘运算后赋值)
x *= y
x = x * y
x = 50
/= (先进行除运算后赋值)
x /= y
x = x / y
x = 2

6.6.三元/条件运算符

三元运算有 3 个操作数,并且需要判断布尔表达式的值。该运算符的主要是决定哪个值应该赋值给变量。

Test ? expr1 : expr2
# 案例
console.log(5>4?1:2)

说明如下:

  • Test − 指定的条件语句
  • expr1 − 如果条件语句 Test 返回 true 则返回该值
  • expr2 − 如果条件语句 Test 返回 false 则返回该值

6.7.字符串运算符

+ 运算符可以拼接两个字符串。

6.8.类型运算符

typeof 是一元运算符,返回操作数的数据类型。

七、TypeScript函数

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。

和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。通过下面的例子可以迅速回想起这两种JavaScript中的函数:

 
//命名函数
function add(x:number, y:number): number {
    return x + y;
}

// 匿名函数
let myAdd = function(x:number, y:number) { return x + y; };
 

7.1.语法如下:

 
// typescript定义函数的方法
// 命名函数
function say1(name:string):void {
  console.log(name);
}
 
// 匿名函数
let say2 = function (name:string):void {
  console.log(name);
}
 
// 箭头函数
let say3 = (name:string):void =>{
  console.log(name);
}

say1("hi")
say2("helloworld")
say3("hi Augus")
 

7.2.ts中函数声明和实现分离的写法

(1)利用type声明函数

 
//利用type声明一个函数
type AddFun = (x:number, y:number) =>number;

// 在根据声明去实现这个函数
// 此时函数的参数和返回值可以不需要写类型声明了,因为ts可以通过这个函数声明推断出来类型了
let add:AddFun = function(x,y){
    return x+y;
};

let res = add(32, 18);
console.log(res)
 

(2)利用interface声明函数

 
// 先利用interface声明一个函数
interface AddFun{
    (a:number,b:number):number
}

let add:AddFun = function(x,y){
    return x+y;
};

//调用函数
let res = add(23, 27)
console.log(res)
 

7.3.函数参数的三种用法

1)可选参数

 
// 需求: 要求定义一个函数可以实现2个数或者3个数的加法
function add(x:number, y:number, z?:number):number{
    //(z ? z : 0) 是一个条件表达式(也称为三元运算符),如果条件 z 为真(即非零、非空、非假等),则返回 z 的值;否则返回 0。
    return x+y+(z?z:0);
}

let res1 = add(11, 22)
console.log(res1)

let res2 = add(11, 22, 33)
console.log(res2)
 

注意事项:可选参数可以是一个或多个。

2)默认参数

如果不传入则使用默认值。传入参数,则使用新值:

 
function add(x:number, y:number=10):number {
    return x + y;
}

let res1 = add(10);
console.log(res1)

let res2 = add(10, 30);
console.log(res2)
 

3)剩余参数

 
function add(x:number, ...ags:number[]): number{
    console.log(x)
    console.log(ags);

    //定义变量报错累加的结果
    let num:number = 0;

    //变量累加
    for(let i in ags) {
        console.log(i)
        //i是索引,所以根据索引取值累加
        num += ags[i];
    }

    //返回结果
    return num + x
};

let res = add(10, 20, 30, 40, 50);
console.log(res);
 

函数是一组一起执行一个任务的语句。可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。

八、TypeScript 联合(union)

TypeScript1.4开始为了使程序能够组合一种或两种类型,创建了联合的概念。union(联合)类型是一种强大的方法,用于表示可以是几种类型之一的值。使用管道符号(|)组合两个或多个数据类型以表示联合类型。换句话说,一个联合类型被写成由竖线分隔类型的序列。

联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值。

注意:只能赋值指定的类型,如果赋值其它类型就会报错。

8.1.联合类型的创建

1)语法:联合literal

Type1|Type2|Type3 

2)示例:联合类型变量

 
let va1:string|number;

va1 = 23;
console.log("va1的值为:"+va1)

va1 = "this is a string";
console.log("va1的值为:"+va1)
 

在上述的例子中,变量的类型是union。这意味着该变量可以包含数字或字符串作为其值。它的输出如下:

  •  "va1的值为:23"
  •  "va1的值为:this is a string" 

3)示例:联合类型和功能参数

 
//参数name可以是string类型,或者string类型的数组
function disp(name:string|string[]){
    //判断name的类型是否是string
    if(typeof name == "string"){
        console.log(name)
    }else{
        let i;
        //根据传入的数组长度,遍历输出
        for(i = 0; i<name.length; i++){
            console.log(name[i])
        }
    }
}

//传入string类型
disp("mark")

//传入string类型数组
disp(["mark", "tom", "mary", "augus"])
 

函数disp()可以接受类型字符串或字符串数​​组参数。输出如下:

  • "mark"
  • "mark"
  • "tom"
  • "mary"
  •  "augus" 

8.2.联合类型和数组

联合类型也可以应用于数组,属性和接口。以下说明了联合类型与数组的使用。示例:联合类型和数组

 
//定义联合类型
let arr:number[]|string[];

let i:number;

//赋值数字类型数组
arr = [1, 2, 4];

//遍历输出数组中的数据
for(i = 0; i<arr.length; i++){
    console.log(arr[i])
}


//赋值字符类型数组
arr = ["李明辉", "张雨瑶", "宋晓宇"];

//遍历输出数组中的数据
for(i = 0; i<arr.length; i++){
    console.log(arr[i])
}
 

程序中声明数组。该数组可以表示数字集合或字符串集合。

九、TypeScript接口

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

9.1.接口定义

接口语法如下:

interface interface_name { 
}

以下实例中,我们定义了一个接口 IPerson,接着定义了一个变量 customer,它的类型是 IPerson。customer 实现了接口 IPerson 的属性和方法。

//定义接口
interface IPerson{
    //定义了一个属性firstName,类型为字符串(string)。
    firstName:string,
    //定义了一个属性lastName,类型为字符串(string)
    lastName:string,
    //定义了一个方法sayHi,它是一个无参数的箭头函数,返回类型为字符串(string)。
    sayHi:()=>string
}

//实现接口
let customer:IPerson = {
    firstName:"雨瑶",
    lastName:"张",
    sayHi:():string => {return "hi 雨瑶"}
}

//访问属性和方法
console.log(customer.firstName) 
console.log(customer.lastName) 
console.log(customer.sayHi())  

9.2.联合类型和接口

以下实例演示了如何在接口中使用联合类型:

//定义接口
interface RunOptions{
    program:string;
    //字符串数组、字符串、无参函数返回string类型
    commandline:string[]|string|(()=>string);
}

// 1.commandline是字符串
let options:RunOptions = {
    program:"test1",
    commandline:"Hello World"
};
//输出
console.log(options.program)
console.log(options.commandline)

// 2.commandline是字符串数组
options = {
    program:"test2",
    commandline:["Hi", "Hello World", "say"]
};
console.log(options.commandline[0])
console.log(options.commandline[1])
console.log(options.commandline[2])

// 3.commandline 是一个函数表达式
options = {
    program:"test3",
    commandline:()=>{return "**Hello World**";}
};

//获取函数赋值给变量fn
let fn:any = options.commandline;
//调用函数
console.log(fn())

9.3.接口和数组

接口中可以将数组的索引值和元素设置为不同类型,索引值可以是数字或字符串。设置元素为字符串类型:

interface nameList{
    //[index:number]:string:这是索引签名的语法,用于定义索引类型和对应的值类型。在这里,索引类型是 number,值类型是 string。
    //这表示可以使用数字作为索引来访问 nameList 对象,并且对应的值是字符串类型。
    [index:number]:string
}

//正确:类型一致
let list1:nameList = ["Baidu", "Bing", "Google"];
console.log(list1);

//错误: 23 不是string类型
let list2:nameList = ["Baidu", 23, "Google"]

执行后报错如下,显示类型不一致:

设置索引类型为string

interface ages { 
   [index:string]:number 
} 
// 类型正确
let agelist:ages = {
   //索引值是string值是数字
   "1":23
   "a": 67
}

//输出
console.log(agelist)

9.4.接口继承

接口继承就是说接口可以通过其他接口来扩展自己。Typescript 允许接口继承多个接口。继承使用关键字 extends。

1)单继承

单接口继承语法格式:

Child_interface_name extends super_interface_name

案例代码如下:

 
interface Person {
   uname:string
   age:number 
} 

//继承Person
interface YellowMan extends Person { 
   instrument:string 
} 

//
let y:YellowMan = {
   uname:"张云",
   age:23,
   instrument:"hi 张云"
}; 

//访问某个属性的值
console.log(y.age)
 

2)多继承

多接口继承语法格式:

Child_interface_name extends super_interface1_name, super_interface2_name,…,super_interfaceN_name

注意:多继承的时候,继承的各个接口使用逗号 , 分隔。

 
interface Person1 {
   uname:string
   age:number 
} 

interface Person2 {
   skill:string
} 

//继承Person 同时继承Person1和 Person2
interface YellowMan extends Person1, Person2 { 
   instrument:string 
} 

//实现接口
let y:YellowMan = {
   uname:"张云",
   age:23,
   skill:"街舞",
   instrument:"hi 张云"
}; 

//访问某个属性的值
console.log(y)

十、TypeScript类

TypeScript是面向对象的JavaScript。TypeScript支持面向对象的编程功能,如类,接口等。OOP中的类是用于创建对象的蓝图。类封装了对象的数据。Typescript为这个名为类的概念提供内置支持。JavaScript ES5或更早版本不支持类。TypeScript会从ES6获得此功能。

10.1.类的创建

使用class关键字的TypeScript声明一个类。语法如下所示:

class class_name {
  //class scope
}

class关键字后跟类名。在命名类时必须考虑标识符的规则。一个类定义可以包括以下内容:

  • 字段 - 字段是在类中声明的任何变量。字段表示与对象有关的数据

  • 构造函数 - 负责为类的对象分配内存

  • 函数 - 函数表示对象可以采取的操作。它们有时也被称为方法。

在TypeScript中,可以使用class关键字来创建类。下面是一个简单的示例,演示了如何在TypeScript中创建一个类:

 
class Animal{
    //定义属性
    name:string;
    age:number;
​
    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
​
    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }
}
​
//创建类的实例
let myAnimal = new Animal("Augus", 45);
​
//调用方法
myAnimal.displayInfo()
 

在上面的示例中,我们首先使用class关键字定义了一个名为Animal的类。类中包含了属性name和age,以及构造函数和方法displayInfo。构造函数用于初始化对象的属性,而方法displayInfo用于显示动物的信息。然后,我们创建了Animal类的一个实例myAnimal,并调用了displayInfo方法来展示动物的信息。

10.2.创建实例对象

要创建类的实例,请使用new关键字后跟类名。语法如下所示:

let object_name = new class_name([arguments])

注意:

  • new关键字负责实例化。

  • 表达式的右边调用构造函数。如果参数化,构造函数应该传递值。

例如:实例化一个类

// 创建类的实例
let myAnimal = new Animal("Tom", 3);

10.3.访问属性和函数

可以通过对象访问类的属性和函数。使用“.”点表示法(称为句点)访问类的数据成员。

 
//创建类的实例
let myAnimal = new Animal("Augus", 45);
​
//调用方法
myAnimal.displayInfo();
​
//访问属性
console.log(myAnimal.name);
console.log(myAnimal.age);
 

完整的代码如下:

 
class Animal{
    //定义属性
    name:string;
    age:number;
​
    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }
​
    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }
}
​
//创建类的实例
let myAnimal = new Animal("Augus", 45);
​
//调用方法
myAnimal.displayInfo();
​
//访问属性
console.log(myAnimal.name);
console.log(myAnimal.age);
 

执行后如下:

  • "Name: Augus, Age:45"

  • "Augus"

  • 45

10.4.类继承

TypeScript支持继承的概念。继承是一种程序从现有的类中创建新类的能力。扩展为创建较新类的类称为父类/超类。新创建的类被称为子类。

一个类使用“extends”关键字从另一个类继承。子类继承父类的私有成员和构造函数之外的所有属性和方法。语法如下:

class child_class_name extends parent_class_name

注意:TypeScript不支持多重继承。

下面是一个简单的示例,演示了如何在TypeScript中创建一个类的继承:

 
class Animal{
    //定义属性
    name:string;
    age:number;

    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }

    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }
}

//子类
class Dog extends Animal{
    breed:string;

    constructor(name: string,age:number,breed:string){
        super(name,age);//调用父类的构造函数
        this.breed = breed;
    }

    bark(){
        console.log("Woof! Woof!");
    }
}

//创建子类实例
let myDog = new Dog("buddy", 12, "Golden Retriever");

//调用继承的方法
myDog.displayInfo(); //继承自父类
myDog.bark();        //子类自己的方法

//访问属性
console.log(myDog.name);
console.log(myDog.age);
 

执行后如下:

  • "Name: buddy, Age:12"
  • "Woof! Woof!"
  • "buddy"
  •  12 

10.5.类的继承和方法重写

方法重写是由子类重新定义父类方法的机制。 以下示例说明了相同的情况:

 
class Animal{
    //定义属性
    name:string;
    age:number;

    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }

    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }

    //定义方法move,定义一个默认值参数
    move(distance: number=0):void{
        console.log(`Name: ${this.name} moved Age:${this.age}  ${distance}km`)
    }
}

//子类
class Dog extends Animal{
    breed:string;

    constructor(name: string,age:number,breed:string){
        super(name,age);//调用父类的构造函数
        this.breed = breed;
    }

    bark(){
        console.log("Woof! Woof!");
    }

    //重新父类的move方法
    move(distance: number=0):void{
        console.log("Slithering...");

        //调用父类的move方法
        super.move(distance);
    }
}

//创建子类实例
let myDog = new Dog("buddy", 12, "Golden Retriever");

//调用继承的方法
myDog.move(23)
 

注意:

  • 在上面的示例中,我们定义了一个父类Animal和一个子类Dog。子类继承了父类的move方法,并在子类中重新定义了move方法。
  • 在子类的move方法中,我们使用super关键字调用了父类的move方法,以保留父类方法的功能,并在此基础上添加了新的行为。
  • 通过这样的方式,子类可以继承父类的方法,并且可以根据需要重新定义方法,实现方法的重写。创建子类的实例后,调用重写后的方法时会执行子类中重新定义的方法。

10.6.static关键字

在TypeScript中,static关键字用于定义类的静态成员。静态成员是属于类本身而不是类的实例的成员,可以通过类名直接访问,而不需要创建类的实例。下面是一个示例,演示了如何使用static关键字定义静态成员:

 
class MathUtils{
    //静态成员变量
    static PI: number = 3.14;

    //静态方法
    static calculatCircumference(radius:number):number{
        return 2 * MathUtils.PI * radius;
    }
}

//访问静态成员
console.log(MathUtils.PI); //输出 3.14
console.log(MathUtils.calculatCircumference(5)); //输出 31.4
 

说明:

  • 上面定义了一个MathUtils类,并使用static关键字定义了一个静态成员PI和一个静态方法calculateCircumference。
  • 静态成员PI是一个常量,可以通过类名直接访问。
  • 静态方法calculateCircumference接收一个半径参数,并返回圆的周长,可以通过类名直接调用。
  • 通过使用static关键字,我们可以在类中定义静态成员,这些成员可以在不创建类的实例的情况下直接访问和使用。静态成员在类的所有实例之间共享,可以用于存储和访问与类相关的共享数据或提供与类相关的实用方法。
  • 需要注意的是,静态成员不能通过类的实例访问,只能通过类名访问。因此,在访问静态成员时,不需要创建类的实例。

10.7.instanceof运算符

在 TypeScript 中,instanceof 运算符的使用方式与 JavaScript 中基本相同。它用于检查一个对象是否是一个特定类型(类)的实例,并返回一个布尔值。语法如下:

object instanceof constructor

其中,object 是要检查的对象,constructor 是要检查的类型(类)。

 
//父类
class Animal{
    move(){
        console.log("Moving ...");
    }
}

//子类
class Dog extends Animal{
    bark(){
        console.log("Woof! Woof!");
    }
}

//创建对象
let animal = new Animal();
let dog = new Dog();

//判断类中是否包含实例
console.log(animal instanceof Animal); //true
console.log(dog instanceof Animal);    //true
console.log(dog instanceof Dog);       //true

10.8.数据隐藏

类可以控制其数据成员的其他类成员的可见性。此功能称为Data Hiding(数据隐藏)或Encapsulation(封装)。

在 TypeScript 中,可以使用访问修饰符来实现类中的数据隐藏。访问修饰符可以限制类的属性和方法的访问权限,以确保类的内部状态不会被外部直接访问或修改。 TypeScript 提供了三种访问修饰符:publicprivate 和 protected

  • public:默认的访问修饰符,表示属性或方法可以在类的内部和外部被访问。
  • private:表示属性或方法只能在定义它们的类的内部被访问。
  • protected:表示属性或方法可以在定义它们的类的内部和派生类中被访问。

下面是一个使用访问修饰符实现数据隐藏的 TypeScript 案例如下:

 
//定义类
class Person{
    private name:string;
    public age:number;
    protected gender:string;

    constructor(name:string,age:number,gender:string){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public displayInfo():void{
        console.log(`Name: ${this.name}, Age: ${this.age}, Gender: ${this.gender}`);
    }
}

//创建对象
let person = new Person("Augus", 22, "男");

console.log(person.age);    // 可以访问,输出 22
console.log(person.name);   // 编译错误,无法访问私有属性
console.log(person.gender); // 编译错误,无法访问受保护属性
person.displayInfo();       // 可以调用公共方法,输出 "Name: Augus, Age: 22, Gender: 男"
 

在这个例子中:

  • name 属性被声明为 private,只能在 Person 类的内部访问;
  • age 属性被声明为 public,可以在内部和外部访问;
  • gender 属性被声明为 protected,可以在 Person 类的内部和派生类(就是子类)中访问。

通过使用访问修饰符,可以控制类的成员对外部的可见性,从而实现数据的隐藏和封装。

注意:在 TypeScript 中,派生类是指通过 extends 关键字从另一个类(称为基类或父类)派生出新的类。派生类继承了基类的属性和方法,并可以添加新的属性和方法,或者重写基类的方法。

10.9.类和接口

在 TypeScript 中,类可以实现接口。类可以遵循接口定义的结构,并提供接口中定义的属性和方法的具体实现。通过类实现接口,可以确保类符合接口定义的结构,从而增强了代码的可读性和可维护性。
 
//定义接口
interface Shape{
    color:string;
    area():number;
}

//通过类实现接口
class Circle implements Shape{
    color:string;
    radius:number;//半径

    constructor(color:string,radius:number){
        this.color = color;
        this.radius = radius;
    }

    //实现方法
    area(){
        return Math.PI * this.radius **2;
    }

}

//创建对象
let circle = new Circle("RED", 5);
console.log(circle.area());// 输出 78.53981633974483
 

上面Circle 类实现了 Shape 接口,确保了类具有 color 属性和 area 方法,并提供了这些属性和方法的具体实现。通过类实现接口,可以明确指定类应该具有的结构,并在编译时进行类型检查,以确保类符合接口定义的要求。

十一、TypeScript 模块

11.1.模块的基本概念

TypeScript 模块的设计理念是可以更换的组织代码。模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js。此外还有有 SystemJs 和 Webpack。

TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,可以编译成纯 JavaScript。TypeScript 支持模块化编程,可以使用模块来组织代码并提高代码的可维护性和可重用性。

在 TypeScript 中,模块是指将代码分割成可重用的单元,每个模块都有自己的作用域,这意味着模块内部的变量、函数、类等对外部是不可见的,除非明确地导出。模块可以包含导出的成员,以便其他模块可以使用它们,同时也可以引入其他模块的导出成员。

11.2.模块的导出

要在 TypeScript 中导出一个变量、函数或类,可以使用 export 关键字。moduleA.ts 内容如下:

 
//定义变量
export const myVariable: number = 10;

//定义函数
export function myFunction(): void {
  console.log("This is my function");
}

//定义类
export class Animal{
    //定义属性
    name:string;
    age:number;
    
    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }

    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }
}

//定义接口
export interface Shape{
    color:string;
    area():number;
}
 

11.3.模块的导入方式一(推荐):

要在 TypeScript 中引入其他模块的导出成员,可以使用 import 关键字。moduleB.ts 例如:
 
import { myVariable,myFunction,Animal,Shape} from "./moduleA";

//访问变量
console.log(myVariable)

//调用函数
myFunction()

//创建对象
let animal = new Animal("Augus", 33);
//调用方法
animal.displayInfo()

//通过类实现导入的接口
class Circle implements Shape{
    color:string;
    radius:number;//半径

    constructor(color:string,radius:number){
        this.color = color;
        this.radius = radius;
    }

    //实现方法
    area():number{
        return Math.PI * this.radius **2;
    }

}

//创建对象
let circle = new Circle("RED", 5);
//调用访问
let n = circle.area()
console.log(`周长为:${n}`)
 

执行后结果如下:

11.4.模块的导入方式二(老语法,不推荐):

在 TypeScript 中,可以使用 require 语法来导入函数、变量、类和接口。下面我将分别演示如何使用 require 语法来导入这些不同类型的模块成员。依然导入moduleA.ts中变量、函数和类,如下:
 
//导入
let moduleA = require('./moduleA');

//访问变量:定义的时候需要和导入的变量类型一致
let myVariable: number = moduleA.myVariable;
console.log(myVariable);

//调用函数
let myFunction:()=>void = moduleA.myFunction;//先获取
myFunction()//后调用

//获取对象
let Animal = moduleA.Animal;
//创建对象
let myAniaml = new Animal("Augus",23);
//调用方法
myAniaml.displayInfo()
 

执行后如下:

十二、TypeScript 模块

12.1.模块的基本概念

TypeScript 模块的设计理念是可以更换的组织代码。模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js。此外还有有 SystemJs 和 Webpack。

TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,可以编译成纯 JavaScript。TypeScript 支持模块化编程,可以使用模块来组织代码并提高代码的可维护性和可重用性。

在 TypeScript 中,模块是指将代码分割成可重用的单元,每个模块都有自己的作用域,这意味着模块内部的变量、函数、类等对外部是不可见的,除非明确地导出。模块可以包含导出的成员,以便其他模块可以使用它们,同时也可以引入其他模块的导出成员。

12.2.模块的导出

要在 TypeScript 中导出一个变量、函数或类,可以使用 export 关键字。moduleA.ts 内容如下:

 
//定义变量
export const myVariable: number = 10;

//定义函数
export function myFunction(): void {
  console.log("This is my function");
}

//定义类
export class Animal{
    //定义属性
    name:string;
    age:number;
    
    //构造函数名称固定
    constructor(name:string,age:number){
        this.name = name;
        this.age = age;
    }

    //定义方法
    displayInfo():void{
        console.log(`Name: ${this.name}, Age:${this.age}`);
    }
}

//定义接口
export interface Shape{
    color:string;
    area():number;
}
 

12.3.模块的导入方式一(推荐):

要在 TypeScript 中引入其他模块的导出成员,可以使用 import 关键字。moduleB.ts 例如:
 
import { myVariable,myFunction,Animal,Shape} from "./moduleA";

//访问变量
console.log(myVariable)

//调用函数
myFunction()

//创建对象
let animal = new Animal("Augus", 33);
//调用方法
animal.displayInfo()

//通过类实现导入的接口
class Circle implements Shape{
    color:string;
    radius:number;//半径

    constructor(color:string,radius:number){
        this.color = color;
        this.radius = radius;
    }

    //实现方法
    area():number{
        return Math.PI * this.radius **2;
    }

}

//创建对象
let circle = new Circle("RED", 5);
//调用访问
let n = circle.area()
console.log(`周长为:${n}`)
 

执行后结果如下:

12.4.模块的导入方式二(老语法,不推荐):

在 TypeScript 中,可以使用 require 语法来导入函数、变量、类和接口。下面我将分别演示如何使用 require 语法来导入这些不同类型的模块成员。依然导入moduleA.ts中变量、函数和类,如下:
 
//导入
let moduleA = require('./moduleA');

//访问变量:定义的时候需要和导入的变量类型一致
let myVariable: number = moduleA.myVariable;
console.log(myVariable);

//调用函数
let myFunction:()=>void = moduleA.myFunction;//先获取
myFunction()//后调用

//获取对象
let Animal = moduleA.Animal;
//创建对象
let myAniaml = new Animal("Augus",23);
//调用方法
myAniaml.displayInfo()
 

执行后如下:

十三、TypeScript 中 Math 类

在开发 TypeScript 应用程序时,经常需要进行各种数学计算。幸运的是,TypeScript 提供了一个内置的 Math 类,其中包含了许多有用的数学方法,可以帮助我们更轻松地进行数值操作和数学计算。本文将深入探索 Math 类,并介绍其多种使用方法。

13.1.基本数值操作方法

1.Math.abs(x)
获取一个数的绝对值。 示例:

const num = -10;
const absNum = Math.abs(num); // absNum 的值为 10

2.Math.ceil(x)
向上取整,返回大于等于给定数的最小整数。 示例:

const num = 3.14;
const ceilNum = Math.ceil(num); // ceilNum 的值为 4

3.Math.floor(x)
向下取整,返回小于等于给定数的最大整数。 示例:

const num = 3.14;
const floorNum = Math.floor(num); // floorNum 的值为 3

4.Math.round(x)
四舍五入,返回最接近给定数的整数。 示例:

const num = 3.6;
const roundNum = Math.round(num); // roundNum 的值为 4

13.2.三角函数方法

1.Math.sin(x)

计算给定角度的正弦值。 示例:

const angle = 45;
const sinValue = Math.sin(angle * Math.PI / 180); // sinValue 的值为 0.7071067811865475

2.Math.cos(x)

计算给定角度的余弦值。 示例:

const angle = 60;
const cosValue = Math.cos(angle * Math.PI / 180); // cosValue 的值为 0.5

3.Math.tan(x)

计算给定角度的正切值。 示例:

const angle = 30;
const tanValue = Math.tan(angle * Math.PI / 180); // tanValue 的值为 0.5773502691896257

4.Math.asin(x)

计算给定值的反正弦值。 示例:

const value = 0.5;
const asinValue = Math.asin(value) * 180 / Math.PI; // asinValue 的值为 30

5.Math.acos(x)

计算给定值的反余弦值。 示例:

const value = 0.5;
const acosValue = Math.acos(value) * 180 / Math.PI; // acosValue 的值为 60

6.Math.atan(x)

计算给定值的反正切值。 示例:

const value = 1;
const atanValue = Math.atan(value) * 180 / Math.PI; // atanValue 的值为 45

13.3.指数运算方法

1.Math.exp(x)

计算 e 的 x 次幂。 示例:

const exponent = 2;
const result = Math.exp(exponent); // result 的值为 7.38905609893065

2.Math.log(x)
计算给定数的自然对数。 示例:

const num = 10;
const logValue = Math.log(num); // logValue 的值为 2.302585092994046

3.Math.pow(x, y)
计算 x 的 y 次幂。 示例:

const base = 2;
const exponent = 3;
const result = Math.pow(base, exponent); // result 的值为 8

4.Math.sqrt(x)
计算给定数的平方根。 示例:

const num = 16;
const sqrtValue = Math.sqrt(num); // sqrtValue 的值为 4

13.4.随机数生成方法

1.Math.random()

生成一个介于 0 到 1 之间的随机数。 示例:

const randomNum = Math.random(); // randomNum 的值为 0.123456789

2.Math.floor(Math.random() * (max - min + 1)) + min
生成一个指定范围内的随机整数。 示例:

const min = 1;
const max = 10;
const randomInt = Math.floor(Math.random() * (max - min + 1)) + min; // randomInt 的值为介于 1 到 10 之间的整数

十四、TypeScript 装饰器

14.1.装饰器及其语法说明

TypeScript 装饰器是一种特殊类型的声明,它可以附加到类声明、方法、属性或参数上,以修改它们的行为。装饰器以 @decorator 的形式应用于类的声明、方法、属性或参数之前,可以传递参数,并且可以被嵌套使用。

TypeScript 装饰器可以用来解决一些重复性的问题,例如验证、性能监控、数据绑定等。通过装饰器,我们可以在不修改类代码的情况下,为类增加新的功能,使得代码更加灵活和可维护。

1)在语法上,装饰器有如下几个特征。

(1)第一个字符(或者说前缀)是@,后面是一个表达式。

(2)@后面的表达式,必须是一个函数(或者执行后可以得到一个函数)。

(3)这个函数接受所修饰对象的一些相关值作为参数。

(4)这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。

2)TypeScript配置

在TypeScript代码中配置TS编译选项,修改tsconfig.json中内容,如果不添加都会报错,同时typescript的版本修改为ES5,添加信息如下:

{
    "compilerOptions":{
        "experimentaDecorators":true
    }
}

设置如下:

 

3)普通装饰器

举例来说,有一个函数Injectable()当作装饰器使用,那么需要写成@Injectable,然后放在某个类的前面。

@Injectable 
class A { // ... }

上面示例中,由于有了装饰器@Injectable,类A的行为在运行时就会发生改变。案例如下:

 
// 加上通过类型提示
interface Person {
    name: string
    address: string
}

function enhancer(target: any) {
    console.log('class enhancer');
    target.prototype.name = 'TS 装饰器';
    target.prototype.address = 'ECMAScript 2022';
}

@enhancer // 普通装饰器
class Person implements Person{
    constructor() {}
}

//创建对象
const person = new Person()
//访问属性
console.log(person.name)
console.log(person.address)
 

4)装饰器工厂

可以通过传递参数的方式注入需要的数据

 
// options 传递过来的对象都可以注入到Class的原型上
function Component(options: {id:number; addres:string}){
    return function(target:any){
        target.prototype.id = options.id
    }
}


@Component({id:1,addres:"2"})
class Cat{
    id:number|undefined;

    constructor(){
    }

    printId(prefix:string){
        console.log(prefix+":"+this.id);
    }
}


//创建对象
let cat = new Cat();
cat.printId("studentId: ") //studentId: :1
 

14.2.装饰器的版本 

TypeScript 从早期开始,就支持装饰器。但是,装饰器的语法后来发生了变化。ECMAScript 标准委员会最终通过的语法标准,与 TypeScript 早期使用的语法有很大差异。

目前,TypeScript 5.0 同时支持两种装饰器语法。标准语法可以直接使用,传统语法需要打开--experimentalDecorators编译参数。需要通过管理员权限打开cmd执行

$ tsc --target ES5 --experimentalDecorators

这里介绍装饰器的标准语法

14.3.装饰器的分类

在 TypeScript 中,装饰器可以分为四种类型:

  • 类装饰器
  • 方法装饰器
  • 属性装饰器
  • 参数装饰器

1)类装饰器

类装饰器在类声明之前声明(紧靠着类声明),用来监视修改或者替换类定义:

 
// 加上通过类型提示
interface Person {
    name: string
    address: string
}

function enhancer(target: any) {
    console.log('class enhancer');
    target.prototype.name = 'TS 装饰器';
    target.prototype.address = 'ECMAScript 2023';
}

@enhancer // 普通装饰器
class Person implements Person{
    constructor() {}
}

//创建对象
const person = new Person()
//访问属性
console.log(person.name)
console.log(person.address)
 

2)属性装饰器

属性装饰器表达式会在运行时当做函数被调用,传入下列两个参数

  • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 第二个参数: 是属性的名称

案例如下:

 
function propertyDecorator(target:any,propertyKey:string){
    console.log("target的值为:"+target)
    console.log("propertyKey的值为:"+propertyKey)
}

class Car{
    @propertyDecorator
    carName:string

    constructor(carName:string){
        this.carName = carName
    }
}
let car = new Car("QQ")
console.log(car.carName)
 

执行后如下:

我们再修改一下实例成员为静态成员

 
function propertyDecorator(target:any,propertyKey:string){
    console.log("target的值为:"+target)
    console.log("propertyKey的值为:"+propertyKey)
}

class Car{
    @propertyDecorator
    static carName:string

    constructor(){
        Car.carName = "QQ"
    }
}

//访问静态成员
console.log(Car.carName) 
 

执行如下:

3)方法装饰器

方法装饰器用来装饰方法

  • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 第二个参数: 是方法的名称
  • 第三个参数: 是方法的描述修饰方法
 
function methodDecorator(target:any,propertykey:string,descriptor:PropertyDescriptor){
    console.log("方法装饰器,开始装饰方法");

    //获取原来的方法,将其保存在originalMethod变量中
    let originalMethod = descriptor.value;

    //重写方法的实现,使用一个新的函数来替代原来的方法。这个新函数接受任意类型的参数。
    descriptor.value = function(...args:any[]){
        console.log("方法装饰器装饰方法前")
        /**
         * 在这里,this代表当前对象的上下文,而args是一个包含了所有传入参数的数组。通过使用apply方法,可以在装饰器中调用原来的方法,并且保持原来方法的上下文和参数
         * 对象的上下文指的是在代码执行时,当前代码所处的环境和状态
         * 具体来说,this的指向取决于函数的调用方式:
         *      1.当函数作为对象的方法被调用时,this指向调用该方法的对象实例。
         *      2.当函数作为普通函数被调用时,this指向全局对象(在浏览器中通常是window对象)。
         *      3.当使用箭头函数时,this的指向由外层作用域决定,而不是动态绑定。
         */
        let result = originalMethod.apply(this,args);
        console.log("方法装饰器装饰方法后")
        return result;//返回原来方法的返回值
    };
    return descriptor;
}

class MyClass{
    //装饰方法
    @methodDecorator
    myMethod():string{
        console.log("这是我的方法");
        return "helloworld"
    }
}

//创建对象
let myclass = new MyClass();
//调用方法
let hello = myclass.myMethod()
//输出返回值
console.log(hello)
 

在这个例子中,我们使用了TypeScript来定义方法装饰器。方法装饰器是一个函数,它接受三个参数:

  • target表示类的构造函数或原型对象,
  • propertyKey表示方法的名称,
  • descriptor表示方法的属性描述符。

在方法装饰器内部,我们可以访问和修改原始方法,并返回一个新的属性描述符。在MyClass类的myMethod方法上通过@methodDecorator语法应用了这个装饰器。当我们实例化MyClass并调用myMethod方法时,装饰器会在方法执行前后输出相应的信息,实现了对方法的装饰。

4)参数装饰器

参数装饰器用来装饰参数

  • 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 第二个参数: 成员的名字
  • 第三个参数: 参数在函数参数列表中的索引
使用参数装饰器来装饰类的方法参数:
 
function LogParameter(target:any, propertyKey: string, parpameterIndex:number){
    
    console.log(`Parameter decorator called on: ${propertyKey}, index: ${parpameterIndex}`);
}

class Example{
    //装饰参数
    greet(@LogParameter message:string):void{
        console.log(`Hello, ${message}`)
    }
}

//创建对象
let example = new Example();
//调用方法
example.greet("World")
 

上面案例中,我们定义了一个参数装饰器LogParameter,它接收三个参数:

  • target表示类的构造函数
  • propertyKey表示成员的名字(实际上是方法名)
  • parameterIndex表示参数在函数参数列表中的索引,索引从0开始。

Example类的greet方法中,我们使用了参数装饰器@LogParameter来装饰message参数。当创建Example类的实例并调用greet方法时,参数装饰器会被调用,并输出参数装饰器被调用的信息。执行如下:

14.4.装饰器执行顺序

执行顺序 属性>方法>方法参数>类 注:如果有多个同样的装饰器,它会先执行后面的装饰器。
  • 属性装饰器最先执行,谁先写先执行谁
  • 参数装饰器再执行,
  • 类装饰器最后执行
  • 方法装饰器再执行
  • 如果同类型,先执行离类近的
  • 属性/方法/访问器装饰器而言,执行顺序取决于声明它们的顺序,两个同样的方法先声明先执行。
案例代码如下:
 
//类装饰器
function classDecorator(target: any) {
    console.log(`**************类装饰器 :${target}*************`)
}

//属性装饰器
function propertyDecorator(target: any, propertyKey: string) {
    console.log(`**************属性装饰器  属性的名称:${propertyKey}*************`)
}

//参数装饰器
function parameterDecorator(target: any,propertyKey: string,parameterIndex: number) {
    console.log(`**************参数装饰器: target:${target} ,propertyKey: ${propertyKey}, parameterIndex: ${parameterIndex}*************`)
}

//方法装饰器
function methodDecorator(target: any,propertyKey: string, descriptor: PropertyDescriptor) {

    //获取原来的方法,将其保存在originalMethod变量中
    let originalMethod = descriptor.value;

    //重写方法的实现,使用一个新的函数来替代原来的方法。这个新函数接受任意类型的参数。
    descriptor.value = function(...args:any[]){
        console.log("**************方法装饰器装饰方法前**************")
        /**
         * 在这里,this代表当前对象的上下文,而args是一个包含了所有传入参数的数组。通过使用apply方法,可以在装饰器中调用原来的方法,并且保持原来方法的上下文和参数
         * 对象的上下文指的是在代码执行时,当前代码所处的环境和状态
         * 具体来说,this的指向取决于函数的调用方式:
         *      1.当函数作为对象的方法被调用时,this指向调用该方法的对象实例。
         *      2.当函数作为普通函数被调用时,this指向全局对象(在浏览器中通常是window对象)。
         *      3.当使用箭头函数时,this的指向由外层作用域决定,而不是动态绑定。
         */
        let result = originalMethod.apply(this,args);
        console.log("**************方法装饰器装饰方法后**************")
        return result;//返回原来方法的返回值
    };
    return descriptor;
}

@classDecorator
class OrderDecorator {
    @propertyDecorator
    order: number[] = [1, 3, 4, 5, 6]

    constructor() {}

    @methodDecorator
    getOrder(@parameterDecorator id: number): number {
        return this.order[id]
    }
}


//创建对象
let order = new OrderDecorator()
//调用方法:测试参数装饰器
let id = order.getOrder(3);
//输出返回值
console.log(`返回的id值为:${id}`)
 

执行后如下,顺序是:1.属性装饰器、2.参数装饰器、3.类装饰器、4.方法装饰器

如果同类型,先执行离类近的,在上面的代码基础先,在创建一个方法装饰器,装饰getOrder方法,添加如下方法装饰器second,修饰getOrder方法:

 
//类装饰器
function classDecorator(target: any) {
    console.log(`**************类装饰器 :${target}*************`)
}

//属性装饰器
function propertyDecorator(target: any, propertyKey: string) {
    console.log(`**************属性装饰器  属性的名称:${propertyKey}*************`)
}

//参数装饰器
function parameterDecorator(target: any,propertyKey: string,parameterIndex: number) {
    console.log(`**************参数装饰器: target:${target} ,propertyKey: ${propertyKey}, parameterIndex: ${parameterIndex}*************`)
}

//方法装饰器
function methodDecorator(target: any,propertyKey: string, descriptor: PropertyDescriptor) {

    //获取原来的方法,将其保存在originalMethod变量中
    let originalMethod = descriptor.value;

    //重写方法的实现,使用一个新的函数来替代原来的方法。这个新函数接受任意类型的参数。
    descriptor.value = function(...args:any[]){
        console.log("**************parameterDecorator方法装饰器装饰方法前**************")
        /**
         * 在这里,this代表当前对象的上下文,而args是一个包含了所有传入参数的数组。通过使用apply方法,可以在装饰器中调用原来的方法,并且保持原来方法的上下文和参数
         * 对象的上下文指的是在代码执行时,当前代码所处的环境和状态
         * 具体来说,this的指向取决于函数的调用方式:
         *      1.当函数作为对象的方法被调用时,this指向调用该方法的对象实例。
         *      2.当函数作为普通函数被调用时,this指向全局对象(在浏览器中通常是window对象)。
         *      3.当使用箭头函数时,this的指向由外层作用域决定,而不是动态绑定。
         */
        let result = originalMethod.apply(this,args);
        console.log("**************parameterDecorator方法装饰器装饰方法后**************")
        return result;//返回原来方法的返回值
    };
    return descriptor;
}

//添加方法装饰器,测试同类型
function second(target: any,propertyKey: string, descriptor: PropertyDescriptor) {
    //获取原来的方法,将其保存在originalMethod变量中
    let originalMethod = descriptor.value;

    //重写方法的实现,使用一个新的函数来替代原来的方法。这个新函数接受任意类型的参数。
    descriptor.value = function(...args:any[]){
        console.log("**************second方法装饰器装饰方法前**************")
        /**
         * 在这里,this代表当前对象的上下文,而args是一个包含了所有传入参数的数组。通过使用apply方法,可以在装饰器中调用原来的方法,并且保持原来方法的上下文和参数
         * 对象的上下文指的是在代码执行时,当前代码所处的环境和状态
         * 具体来说,this的指向取决于函数的调用方式:
         *      1.当函数作为对象的方法被调用时,this指向调用该方法的对象实例。
         *      2.当函数作为普通函数被调用时,this指向全局对象(在浏览器中通常是window对象)。
         *      3.当使用箭头函数时,this的指向由外层作用域决定,而不是动态绑定。
         */
        let result = originalMethod.apply(this,args);
        console.log("**************second方法装饰器装饰方法后**************")
        return result;//返回原来方法的返回值
    };
    return descriptor;
}

@classDecorator
class OrderDecorator {
    @propertyDecorator
    order: number[] = [1, 3, 4, 5, 6]

    constructor() {}

    @second
    @methodDecorator
    getOrder(@parameterDecorator id: number): number {
        return this.order[id]
    }
}


//创建对象
let order = new OrderDecorator()
//调用方法:测试参数装饰器
let id = order.getOrder(3);
//输出返回值
console.log(`返回的id值为:${id}`)
 

执行后如下:methodDecorator距离近先执行了,second后执行:

14.5.装饰器的执行时机

装饰器只在解释执行时应用一次,所有装饰器的执行在编译时而不是运行时

 
function f(c) {
  console.log('类装饰器')
  return c
}

@f
class A {}
posted @ 2023-12-09 11:46  酒剑仙*  阅读(181)  评论(0)    收藏  举报