TS(TypeScript)
旧 JS 是弱类型语言,一个变量先后可以保存不同类型的数据,这样极不可靠;而且旧 JS 是解释执行语言,一边解释一边执行,导致一 些低级错误无法提前检查和预警;此外旧 JS 对对象要求不够严格,开发人员想怎么写就 怎么写,不便于大项目协作。
TypeScript 是 JavaScript 的一个超集,由微软开发,支持 ECMAScript6 标准,设计的目的就是开发大型应用。TypeScript 不能被浏览器直接执行,但是 TypeScript 可以先编译成 JavaScript,再在浏览器或 nodejs 上运行。
1. TS的安装和配置
a. 全局安装 TypeScript 语言的编译器:cmd 命令提示行下输入npm i -g typescript;只需首次安装即可。
b. 用 vscode 打开项目文件夹后,右键选择在终端中打开,在终端中输入tsc -init(tsc是ts语言的编译器, c是compile的意思, 编译),之后会在当前项目文件夹中生成 tsconfig.json 文件;
其中保存的是将 ts 编译为 js 时所需的配置,如:
target: “ES5”,在将ts文件编译为js文件时,编 译为ES5的版本,兼容更多浏览器;
module: "commenJS“,将来ts文件中模块化开发所 采用的标准;
strict: true,将ts文件编译js文件时,自动启用 严格模式。
如果报tsc.ps1错误:
Windows 开始菜单搜 powershell,右键单击,选择用管理员身份运行;
在弹出的 powershell 窗口中输入:set-ExecutionPolicy RemoteSigned --> 回车 --> 输入a --> 回车。
2. 第一个TS程序(举例)
创建 .ts 文件 1_first.ts;
打开命令行窗口,输入 tsc 1_first.ts,再编译 ts 文件为 js 文件;
如上图,tsc 编译器将 .ts 文件的内容翻译为等效的 js 文件, 保存在 ts 文件旁边的同名 1_first.js 文件中;
可以发现,在 ts 文件中写的很多 ts 语法,在 js 中都省略了,这是因为 ts 语言的检查是发生在编译阶段,而不是运行阶段;ts 中写的专用的语法是在编译阶段使用的,编译后将来运行时,不需要再重新检查,所以不需要特殊语法。
接下来用 node 运行 1_first.js 文件即可。
3. 配置自动编译
在 vscode 中选择 ”终端” --> "运行生成任务" --> “tsc: 监视xxx“;配置完成后只要修改 ts 文件保存后,就会自动编译并创建 .js 文件。
此外,运行 js 文件也可以不用输入命令,先打开要运行的 js 文件,点左边小虫+三角图标 --> 运行和调试 --> nodejs,即可看到执行结果。
4. 变量声明
旧 JS 是弱类型语言,一个变量先后可以保存不同类型的数据,这样是不可靠的;typescript 规定声明变量必须用”: 数据类型”格式。如图:
var/const/let 变量名:数据类型=初始值
//该变量只能保存规定类型的数据
变量的数据类型可以是 boolean、number、string、数组,还有 any,any 表示可以匹配任何数据类型;当类型为数组时有两种写法:
let 数组名: 数据类型[]=[值1, 值2, ...]
let 数组名: Array<数据类型>=[值1, 值2,...]
需要注意 TypeScript 是强类型语言,即使首次定义变量时没有用 : 指定变量的数据类型, TypeScript也会自动将变量第一次赋值时的变量值类型,作为当前变量的数据类型。
5. 函数
当函数有返回值时,写法如下:
function 函数名():返回值的类型{
...
return 返回值
}
如果函数有参数:
function 函数名(
形参1:数据类型, 形参2:数据类型, ... ){
...
}
如果既有形参,又有返回值:
function 函数名(
形参1:数据类型, 形参2:数据类型, ...
):返回值类型 {
...
return 返回值
}
当需要默认值时:
function 函数名(
形参1:数据类型, 形参2:数据类型=默认值) {
... ...
}
当实参值个数不确定:
function 函数名(
固定形参:数据类型, ...数组名:数据类型[]){
}
举例1:定义自我介绍的函数(使用默认值);
// 形参:数据类型=默认值(如果没有传参,默认为此值)
function intr(str: string = "这家伙有点懒,什么都没写。。。") {
console.log(`自我介绍:${str}`);
}
intr("我是吕布,三国第一猛将!");
intr();
举例2:计算任意项之和(实参值个数不确定);
function jisuan(ename: string, ...arr: number[]) {
return `${ename}的总工资为:${arr.reduce((box, elem) => box + elem)}`;
}
console.log(jisuan("张飞", 10000, 200, 144, 5000));
console.log(jisuan("刘备", 14000, 2000, 544));
例1例2结果如下:
6. 重载
在旧 js 中,重载定义一个函数,在函数内根据传入的参数不同执行不同的逻辑;
而在 TS 中:先定义多个形参列表不同的同名函数声明,且先不实现函数体,
function 函数名():void;
function 函数名(形参:数据类型):void;
//void代表这个函数,没有返回值
//如果函数有返回值,必须将void改为返回值的具体类型
其次定义一个可实际执行多种任务的函数来支持上方多种重载的情况。
function 函数名(){
}
举例:使用重载实现三种支付方式;
// 先定义三种重载的情况
// void代表这个函数没有返回值
function pay(): void;
function pay(money: number): void;
function pay(card: string, pwd: string): void;
function pay(...arr: any[]) {
if (arr.length == 0) {
console.log(`手机支付`);
} else if (arr.length == 1) {
console.log(`微信支付`);
} else {
console.log(`刷卡支付`);
}
}
pay(); //手机支付
pay(10000); //微信支付
pay("123456", "000"); //刷卡支付
//pay(123,456) 不符合最初定义的三种重载情况时直接报错
运行结果如下:
7. class
class 类型的定义:
class 类型名{
属性名1:数据类型=初始值;
属性名2:数据类型=初始值;
constructor(形参1:数据类型, 形参2:数据类型){
this.属性名1=形参1;
this.属性名2=形参2;
}
方法名():数据类型{ ... }
}
注意:TS 中 class 的属性必须在构造函数上方提前定义并指定数据类型,这样才能在构造函数中执行 this.属性名=赋值。
举例:定义学生类型,描述学生统一结构;
class student {
sname: string = "";
sage: number = 0;
constructor(sname: string, sage: number) {
this.sname = sname;
this.sage = sage;
}
intr(): any {
console.log(`我是${this.sname},我今年${this.sage}岁。`);
}
}
var zhang = new student("张飞", 52);
zhang.intr();
console.log(zhang);
运行结果如下:
class 中的所有成员无论在 class 内还是在子类型内或者在全局,都可用 ”this.属性名” 或” 对象名.属性名”方式访问;但如果有些数据不想让随意访问,就需要用到访问控制修饰符。
访问修饰符是专门修饰一个属性或一个方法的可用范围的关键字;格式如下:
访问控制修饰符 属性名 : 数据类型 = 初始值
访问控制修饰符的三种类型:
(1)public 公有(默认),表示子类型和类外部都可访问到的类成员;
(2)protected 受保护,表示只有父子类型范围内才能使用,外部不可用;
(3)private 私有,表示仅 class 内可用,子类型和外部都不能用。
举例:通过一个一家人管钱的例子说明访问控制修饰符的三种类型;
class Father {
public moneyPublic: string = "Father公开的钱";
protected moneyProtected: string = "Father和Son的钱";
private moneyPrivate:string="Father私有的钱";
fatherPay() {
console.log(`Father用${this.moneyPublic}给自己买了一包烟!`);
console.log(`Father用${this.moneyProtected}给自己买了一块表!`);
console.log(`Father用${this.moneyPrivate}给自己买了一身衣服!`);
}
}
class Son extends Father {
sonPay() {
console.log(`Son用${this.moneyPublic}给自己买了一个玩具!`);
console.log(`Son用${this.moneyProtected}给自己买了零食!`);
}
}
var f1 = new Father();
var s1 = new Son();
f1.fatherPay();
s1.sonPay();
// 全局mother
console.log(`mother用${f1.moneyPublic}给自己买了一个包包!`);
运行结果如下:
8. 接口
如果希望开发人员一定要按照架构师的要求实现程序结构时,都用接口来规范。首先是定义接口:
interface I接口名{
规定的属性名: 类型;
规定的方法名(参数:数据类型):返回值;
}
接下来“实现接口”:
class 类型名 implements I接口名{
//必须包含接口中规定的属性和方法
}
举例:使用接口规范实现飞机飞行;
interface IPlane {
x: number;
y: number;
fly(): void;
}
class Plane implements IPlane {
x: number = 0;
y: number = 0;
score: number = 0;
constructor(x: number, y: number, score: number) {
this.x = x;
this.y = y;
this.score = score;
}
fly(): void {
console.log(`飞到x=${this.x},y=${this.y}的位置`);
}
getScore(): void {
console.log(`击落敌机得${this.score}分`);
}
}
var p = new Plane(100, 20, 20);
p.fly();
p.getScore();
console.log(p);
运行结果如下:
9. 模块化开发
模块化开发即哪个文件想使用另一个文件的内容,直接 引入即可。
只导出一个接口或类型时:
//先创建类型/接口
//导出
export default 类型名/接口名
//引入
import 类型名 from “相对路径”
导出多个接口或类型:
//先创建多个类型/接口
//导出
export { 类型名, … }
//引入
import { 类型名, … } from “相对路径”