这是我参与更文挑战的第七天,活动详情查看:更文挑战
从移动终端到后端服务,从 IOT 到神经网络,JavaScript 几乎无处不在。 如此广阔的应用领域,自然对语言的安全性,健壮性,和可维护性有更高的要求。尽管,ECMAScript 标准在近几年有长足的进步,但在类型检查方面依然无所建树。 你是否经常遇到这样的场景
场景一: 你调用一个别人写的函数,但是别人没有留下任何注释,为了搞清楚参数类型,只能硬着头皮去看里面的逻辑
场景二: 为了保证代码的健壮性,你对一个函数的输入参数进行了各种假设(写了 N+1 个参数类型判断)
场景三:领导很看好你,让你维护一个重要的底层类库,你殚精竭虑,优化了一个参数类型,但是不知道有多少处引用,在提交代码前,是否感到如履薄冰呢?
场景四: 明明定义好了接口,可一联调就报错了,于是你生气的找后端理论:"这个字段是字符串!那个字段是数组!还有那个字段是数字!!!....."
以上情况,归根结底,是因为 JavaScript 是一门动态弱类型语言,对变量的类型非常宽容, 而且不会在变量与调用者之间建立结构化契约。如果你长期在没有类型约束的环境下开发,就会造成类型思维的缺失,养成不良的编程习惯!
Typescript 是什么
拥有类型系统的 JavaScript 超集,可以编译成纯 JavaScript,(为 JavaScript 提供静态类型检查)
Typescript 的特点
类型检查
TypeScript 会在编译代码时,进行严格的静态类型检查。意味着可以**在编码阶段发现存在的隐患,而不用把他们带到线上去
语言扩展
TypeScript 会包括 来自 ES6 和未来提案中的特性,比如异步操作和装饰器,也会从其他语言借鉴特性,比如接口和抽象类
工具属性
TypeScript 可以编译成 JavaScript,在任何浏览器,操作系统上运行。无需任何运行时的额外开销
为什么要使用 TypeScript
VSCODE 具备强大的 自动补全,导航,重构功能。使接口定义可以直接代替文档,同时可以提高开发效率,降低维护成本
TypeScript 可以帮助团队重塑"类型思维",接口的提供方将被迫去思考 API 的边界,他们将从代码的编写者蜕变为代码的设计者。
如果 JavaScript 是一匹野马,TypeScript 就是束缚野马的缰绳,作为"骑士"的你,自然可以张开双臂,放飞自我。但是,如果不是技艺超群,恐怕会摔的很掺。然而如果抓住了缰绳,你即可闲庭信步,亦可策马扬鞭,这就是 TypeScript 的价值,它可以让你在前端开发之路上走得更稳,更远。
学习 TypeScript 后,能让你在编程中形成"类型思维", 因为思维方式决定编程习惯、编程习惯奠定了工程质量,工程质量划定了能力边界。面对越来越复杂的前端应用,TypeScript 提供的思维方法,能够让你在未来的开发中长期受益!
强类型语言
解释一: 在 1974 年,一位美国科学家定义为:在强类型语言中,当一个对象从调用函数传递到被调用函数时,其类型必须与被调用的函数中声明的类型兼容
解释二: 不允许改变变量的数据类型,除非强制类型转换
弱类型语言
变量可以被赋予不同的数据类型
静态类型语言
在编译阶段确定所有变量的类型
- 编译阶段确定偏移量
- 用偏移量访问代替属性名访问
- 偏移量信息共享
动态类型语言
在执行阶段确定所有变量的类型
- 在程序运行时,动态计算属性偏移量
- 需要额外的空间存储属性名
- 所有对象的偏移量各存一份
| 静态类型语言 | 动态类型语言 |
|---|---|
| 对类型极度严格 | 对类型非常宽松 |
| 立即发现错误 | Bug 可能隐藏数月甚至数年 |
| 运行时性能好 | 运行时性能差 |
| 自文档化 | 可读性差 |
动态类型语言的支持者认为:
- 性能是可以改善的(V8 引擎),而语言的灵活性更重要
- 隐藏的错误可以通过单元测试发现
- 文档可以通过工具生成
说明任何语言都具有两面性,同时也是在不断发展和进化的,不能一概而论,要看具体的场景和性价比。
编写第一个 typescript
初始化前端项目
新建"typescript-leanring" 目录,进入该目录,执行以下命令
// 第一步
npm init -y
// 第二步
npm i typescript -g
//第三步
npm tsc --init
// 第四步
npm i webpack webpack-cli webpack-dev-server --save-dev
新建src目录,进入改目录,创建 index.ts文件
let hello: string = "hello world";
基本类型
| ES6 的数据类型 | typescript 的数据类型 |
|---|---|
| Boolean | Boolean |
| Number | Number |
| String | String |
| Array | Array |
| Function | Function |
| Object | Object |
| Symbol | Symbol |
| undefined | undefined |
| null | null |
| void | |
| any | |
| 元组 | |
| 枚举 | |
| 高级类型 |
类型注解(TS 中声明类型的方式)
格式 变量/函数:type
解释: 在变量或函数后加一个冒号, 冒号跟类型。变量的数据类型是一般不能改变的
原始类型
boolean , number , string
let bool: boolean = true;
let num: number | null | undefined = 1;
let str: string = "1";
数组
1. 数字类型的数组
let ar1: number[] = [1, 2, 3];
let arr2: Array = [1, 2, 3];
2. 不同类型的数组 --联合类型
let arr3: Array<number | null | string | undefined> = [
1,
"2",
3,
"4",
null,
undefined,
];
3. 元组--一种特殊的数组,限制了数组的类型,顺序,个数. 建议: 只用来访问
let tuple: [number, string] = [1, "2"];
函数
let add = (x: number, y: number) => x + y;
在函数参数括号之后可以加上函数返回值的类型, 但通常返回值类型可以省略(因为使用了 ts 的类型推导功能)
let add1 = (x: number, y: number): number => x + y;
对象
let obj: object = { x: 1, y: "2" };
/*
obj.x= 6 出现警告
因为 在 ts中 obj只知道是object,并没有具体到某一个属性的类型
*/
let obj1: { x: number; y: string } = { x: 1, y: "2" };
obj1.x = 1;
symbol
let x1: symbol = Symbol();
let x2 = Symbol();
undefined null
let un: undefined = undefined;
let nu: null = null;
void
在 JavaScript 中: void 让任何表达式返回 undefined。如: void 0; 在 ts 中 表示 没有任何返回值得类型
let noReturn = () => {};
any 如过不设置任何类型默认就是 any
let x;
never 永远不会有返回值的类型
// 第一种情况,函数抛出异常
let error = () => {
throw new Error("ERROR");
};
// 第二种情况 , 死循环
let endless = () => {
while (true) {}
};
使用联合类型 声明变量的类型
let num: number | null | undefined = 1;
num = null;
一个角色判断的例子
function initByRole(role) {
if (role === 1 || role === 2) {
// do some
} else if (role === 3 || role === 4) {
// dom some
} else if (role === 5) {
//
} else {
//
}
}
/*
一个系统有多种角色,
一个角色有很多种权限, 权限又对应不同的界面
问题:
1. 可读性差,很难记住数字的含义
2. 可维护性差,硬编码,牵一发动全身
如何解决:使用枚举
*/
枚举
枚举: 一组有名字的常量集合, 枚举成员的值定义后不能修改
数字枚举
第一个枚举成员的值默认是 0 ,往后递增
enum Role {
Reporter,
Developer,
Owner,
Guster,
}
可以设置枚举成员的默认值, 默认往后递增
enum Role1 {
Reporter = 1,
Developer,
Owner,
Guster,
}
console.log(Role1);
/*
{
1: "Reporter",
2: "Developer",
3: "Owner",
4: "Guster",
Developer: 2,
Guster: 4,
Owner: 3,
Reporter: 1,
}
*/
枚举成员实际上是一个对象
- 可以使用枚举成员的值进行访问
- 可以使用枚举成员的名字进行访问
var Role1;
(function (Role1) {
Role1[(Role1["Reporter"] = 1)] = "Reporter";
Role1[(Role1["Developer"] = 2)] = "Developer";
Role1[(Role1["Owner"] = 3)] = "Owner";
Role1[(Role1["Guster"] = 4)] = "Guster";
})(Role1 || (Role1 = {}));
枚举的实现原理: 反向映射 在编译成 JavaScript 对象后, 内层:枚举的成员的名称作为 key,值作为 value,返回 value(A) 外层: A 作为 key ,枚举的成员的名称作为 value
字符串枚举
enum Message {
Success = "恭喜你,成功了",
Fail = "抱歉",
}
编译成 JavaScript 代码后
var Message;
(function (Message) {
Message["Success"] = "\u606D\u559C\u4F60,\u6210\u529F\u4E86";
Message["Fail"] = "\u62B1\u6B49";
})(Message || (Message = {}));
/*
只有字符串的名称作为了key,
因此字符串枚举是不能进行反向映射的
*/
异构枚举
把数字枚举与字符串枚举混用,就组成了异构枚举(不推荐使用)
enum Answer {
N,
Y = "yes",
}
枚举成员
分类
- 常量枚举(会在编译时计算出结果,然后以常量的形式出现在运行时环境)
- 没有初始值的情况
- 对已有枚举成员的引用
- 常量表达式
- 需要被计算的枚举(一些非"常量"的表达式,枚举成员的值不会在编译阶段进行计算,而会被保留到运行时环境进行计算)
enum Char {
// 没有初始值的情况
a,
// 对已有枚举成员的引用
b = Char.a,
// 常量表达式
c = 1 + 3,
// 需要被计算的枚举
d = Math.random(),
e = "123".length,
// 需要被计算的枚举一定需要被赋值,否则会有警告
// f= 4
}
编译成 JavaScript 代码后
var Char;
(function (Char) {
Char[(Char["a"] = 0)] = "a";
Char[(Char["b"] = 0)] = "b";
Char[(Char["c"] = 4)] = "c";
Char[(Char["d"] = Math.random())] = "d";
Char[(Char["e"] = "123".length)] = "e";
})(Char || (Char = {}));
/*
常量枚举成员已经被计算出了结果
需要被计算的枚举成员的值被保留了,在运行时环境才会被计算
*/
常量枚举
用 const 声明的枚举就是常量枚举
const enum Month {
Jan,
Feb,
Mar,
}
/*
编译后没有任何代码
*/
特点: 在编译阶段会被移除 作用:当我们不需要一个对象,而需要一个对象的值得时候,就可以使用常量的值。这可以减少在编译环境的代码。
const enum Month {
Jan,
Feb,
Mar,
}
let month = [Month.Jan, Month.Feb, Month.Mar];
编译成 JavaScript 代码后
var month = [0 /* Jan */, 1 /* Feb */, 2 /* Mar */];
枚举直接被替换成了常量,在运行时的代码就会变得非常简洁
枚举类型
在某些情况下,枚举和枚举成员都可以成为一种单独的类型出现
// 1. 枚举成员没有初始值
enum E {
a,
b,
}
// 2. 所有枚举成员都是数字枚举
enum F {
a = 0,
b = 1,
}
// 3. 所有枚举成员都是字符串枚举
enum G {
a = "apple",
b = "banana",
}
// 将数值赋值给 1,2种情况的枚举,并且值可以超出范围
let e: E = 3;
let f: F = 3;
// 不同类型的枚举是不能进行比较的,会有报错
// e===f
let e1: E = E.a;
let e2: F = F.b;
let e3: E = E.a;
// 不同类型的枚举是不能进行比较的,会有报错
// e1===e2
// 相同类型的枚举是可以进行比较的
e1 === e3;
// 字符串枚举的取值只能是枚举成员的类型
let g1: G = G.a;
let g3: G = G.b;
let g2: G.a = G.a;
// 报错
// let g4: G.a = G.b;