TypeScript
TypeScript是一门基于JavaScript之上的编程语言,它重点解决了JavaScript的自有类型系统的不足,通过TypeScript可以大大提高代码的可靠程度。
强类型和弱类型
从类型安全的角度,编程语言分为强类型和弱类型, 这种强弱类型的概念最早是1974年的时候美国两个计算机专家Liskov,Zilles提出的。当时对强类型定义的概念就是在语言层面限制函数的实参类型必须与形参类型相同。例如有一个foo函数,它需要接收一个整型的参数,当我们调用的时候不允许传入其他类型的值。
class Main{
static void foo(int num){
System.out.println(num);
}
public static void main(String[] args){
Main.foo(100); //ok
Main.foo("100"); //error "100" is a string
Main.foo(Integer.parseInt("100")); //ok
}
}
而弱类型完全相反,在语言层面不会限制实参的类型,即便参数为整型的,在调用时可以传入任意类型的数据,语法上不会报错,运行上可能会有点问题。
function foo(num){
console.log(num);
}
foo(100); //ok
foo('100'); //ok
foo(parseInt('100')); // ok
由于这种强弱之分根本不是某一个权威机构的定义,而且当时两位专家也未给出具体的规则,这就导致后人对这种界定方式的细节出现了一些不一样的理解,但是整体上大家界定方式都是强类型有更强的类型约束,而弱类型几乎没有什么约束。那么个人的理解就是强类型语言中不允许任意的隐式类型转换,而弱类型语言则允许任意的数据隐式类型转换。变量类型允许随时改变的特点,不是强弱类型的差异。例如python是强类型的语言。
静态类型与动态类型
从类型检查的角度,将编程语言分为静态类型和动态类型。对于静态类型语言它的表现就是一个变量声明时它的类型就是明确的,而且在声明过后,它的类型就不允许再修改。相反,动态类型语言的特点就是在运行阶段才能够明确变量类型,而且变量的类型能够随时发生变化,也可以说在动态语言中它的变量没有类型,变量中存放的值是有类型的。那么JavaScript就是一门动态类型的语言。
JavaScript类型系统特征
由于JavaScript是一门弱类型而且是动态类型的语言,语言本身的类型系统是薄弱的,甚至可以说JavaScript根本没有类型系统,所以说它是极其灵活多变的。同时它也丢掉了类型系统的可靠性,我们在代码中的每一个变量都要去担心是不是想要的类型。
那么为什么JavaScript不是一门强类型/静态类型的语言呢?首先,早期的JavaScript应用非常简单,很多时候几百行代码,甚至几十行代码就搞定了,这个时候类型系统就很麻烦。其次,JavaScript是一门脚本语言,脚本语言的特点是不需要编译,直接在运行环境当中去运行,换句话说JavaScript是没有编译环节的,即便把它设计成静态类型语言也没有意义。因为静态类型语言需要在编译阶段进行类型检查,而JavaScript根本没有这样的环节。所以JavaScript就形成了灵活多变的弱类型/动态类型语言。
这在以前那个时候是没有什么问题的,而现如今前端都是大规模的应用,JavaScript代码越来越复杂,开发周期越来越长,在大规模应用下,JavaScript这种优势就变成了短板。
弱类型的问题
例如我们调用了obj不存在的foo方法,在语法层面这么写是没有问题的,而在运行的时候就会报错。而且有时候它在某个时间运行。例如放在setTimeout回调函数中,延迟一段时间执行。
const obj = {};
obj.foo()
如果是强类型的话,会在语法上报错误。
第二个例子,我们定义了一个计算两个数和的函数,如果我们传入两个数字的话,这个函数调用正常。如果我们传入其中一个参数为字符串,这个时候,该函数的作用完全发生了变化,它会返回两个字符串连接过后的结果。这就是类型不确定造成的典型问题。
function sum(a,b){
return a + b;
}
console.log(sum(100,100));
console.log(sum(100,'100'));
第三个例子,我们给对象添加属性,对象的属性名只能是字符串或者ES6中的Symbol,但是由于JavaScript是弱类型的,所以我们索引器的时候使用任意类型的值作为属性,而在它的内部会自动转换成字符串。强类型则不会有这种问题。
const obj = {};
obj[true] = 100;
console.log(obj['true']);
所以弱类型的弊端是十分明显的,在代码上可以通过约定来规避,但是大项目则会有问题。强类型语言的优势就体现出来了。
强类型的优势
强类型的优势可以总结四个方面:
- 错误更早暴露,也就是在编码方面可以消灭一大部分的类型异常问题,不用等到运行阶段再去查找错误。
- 强类型的代码更加智能,编码更准确。因为弱类型的无法推断出对象有哪些成员,导致获取对象的成员属性没有提示,导致编写错误。而强类型的可以。
- 重构更牢靠。在对象的属性名发生变化,在重新编译时就会检查出问题,可以轻松定位所有使用该成员属性的地方。
- 减少代码层面不必要的类型判断,JavaScript是弱类型,我们在使用方法参数的时候,不可避免的去判断参数类型。如果是强类型,则不需要这种判断。
Flow
Flow是JavaScript静态类型检查器,它是2014年由facebook推出的一款工具,使用它可以弥补JavaScript弱类型所带来的弊端,可以说它给JavaScript提供了更完善的类型系统,目前在React、Vue项目中都可以看到Flow的使用。它的工作原理就是在代码中添加类型注解的方式,来去标记变量或者是参数是什么类型的,然后Flow就会根据这些类型注解检查代码当中是否会存在类型使用上的异常,从而实现开发阶段类型的检查。
function sum(a:number,b:number){
return a + b;
}
例如上述在a后面加上:number这种方式就叫做类型注解,表示该成员必须要接收number类型的值。在代码中添加的这种额外类型注解可以在运行之前通过babel或Flow官方提供的模块自动去除,所以在生产环境不会有影响。Flow只是一个小工具,而TypeScript则是一门语言。
TypeScript
TypeScript是基于JavaScript基础之上的编程语言,很多时候我们都说它是JavaScript的超集或扩展集,那多出来的就是更强大的类型系统以及ECMAScript新特性的支持,它最终会编译为原始的JavaScript。
另外,因为TypeScript最终会编译成JavaScript,所以任何一种JavaScript运行环境下的应用程序都可以使用TypeScript来开发,例如浏览器应用、Node应用、React Native以及Electron应用。
它作为一门编程语言,功能更加强大,生态也更健全、更完善。目前很多大型开源项目都采用TypeScript来开发,例如Angular、Vue.js3.0。TypeScript可以说是前端领域的第二语言,如果是小项目可以采用JavaScript灵活使用,如果是长周期的大项目,那么建议采用TypeScript来进行开发。
有优点也会有缺点,TypeScript的缺点就是这门语言相比较JavaScript多了很多概念,例如接口、泛型、枚举等等。这些概念会提高学习成本,但是TypeScript是渐进式的,即便我们不明白这些特性,依旧可以当做JavaScript来使用。其次在项目初期,TypeScript会增加一些项目成本,因为在项目初期我们会编写很多的类型声明,比如对象和函数有很多类型声明去编写。但是对大型项目则编写这些是一劳永逸的,这些成本也不算什么。TypeScript在前端发展过程中是必要的一门编程语言。
Playground
官方也提供了一个在线开发 TypeScript 的云环境——Playground。
基于它,我们无须在本地安装环境,只需要一个浏览器即可随时学习和编写 TypeScript,同时还可以方便地选择 TypeScript 版本、配置 tsconfig,并对 TypeScript 实时静态类型检测、转译输出 JavaScript 和在线执行。
而且在体验上,它也一点儿不逊色于任何本地的 IDE,对于学习 TypeScript 的我们来说,算是一个不错的选择。
安装TypeScript
执行以下命令安装TypeScript
npm i -g typescript
TypeScript 安装完成后,我们输入如下所示命令即可查看当前安装的 TypeScript 版本。
tsc -v
我们也可以通过安装在 Terminal 命令行中直接支持运行 TypeScript 代码(Node.js 侧代码)的 ts-node 来获得较好的开发体验。
通过 npm 全局安装 ts-node 的操作如下代码所示:
npm i -g ts-node
如果你是 Mac 或者 Linux 用户,就极有可能在 npm i -g typescript 中遭遇 “EACCES: permission denied” 错误,此时我们可以通过以下 4 种办法进行解决:
- 使用 nvm 重新安装 npm
- 修改 npm 默认安装目录
- 执行 sudo npm i -g xx
- 执行 sudo chown -R [user]:[user] /usr/local/lib/node_modules
TS配置文件
我们可以使用tsc --init
来初始化ts的配置文件tsconfig.json,该配置中有compilerOptions属性,这个属性就是TypeScript编译器对应的配置选项。其中绝大多数配置都被注释掉了,而且附有一些说明。例如:
- target:设置编译后的ECMAScript的标准
- module:输出的代码采用什么样的模块化方式
- outDir:设置输出结果的文件夹目录
- rootDir:设置源代码即TypeScript的文件夹目录
- sourceMap:开启源代码映射,调试的时候可以调试TypeScript源代码
- strict:严格模式,开启所有严格类型检查,要为每一个成员指定明确的类型,不允许不写类型而隐式推断为any这种情况
- lib:编译过程所需要的标准库,标准库即内置对象所应用的声明文件,在代码中使用内置对象必须要引用对应的标准库,否则TypeScript找不到对应的类型,会报错
当我们使用tsc编译某一个文件的时候,配置文件是不生效的,而当我们使用tsc编译整个项目的时候tsc才生效。
然后,我们输入如下所示代码即可新建一个 HelloWorld.ts 文件:
function say(word: string) {
console.log(word);
}
say('Hello, World');
在以上代码中,word 函数参数后边多出来的 “: string” 注解直观地告诉我们,这个变量的类型就是 string。如果你之前使用过其他强类型的语言(比如 Java),就能快速理解 TypeScript 语法。
当然,在当前目录下,我们也可以通过如下代码创建一个同名的 HelloWorld.js 文件,而这个文件中抹掉了类型注解的 TypeScript 代码。
function say(word) {
console.log(word);
}
say('Hello, World');
这里我们可以看到,TypeScript 代码和我们熟悉的 JavaScript 相比,并没有明显的差异。
.ts 文件创建完成后,我们就可以使用 tsc(TypeScript Compiler) 命令将 .ts 文件转译为 .js 文件。
注意:指定转译的目标文件后,tsc 将忽略当前应用路径下的 tsconfig.json 配置,因此我们需要通过显式设定如下所示的参数,让 tsc 以严格模式检测并转译 TypeScript 代码。
tsc HelloWorld.ts --strict --alwaysStrict false
同时,我们可以给 tsc 设定一个 watch 参数监听文件内容变更,实时进行类型检测和代码转译,如下代码所示:
tsc HelloWorld.ts --strict --alwaysStrict false --watch
我们也可以直接使用 ts-node 运行 HelloWorld.ts,如下代码所示:
ts-node HelloWorld.ts