typescript 是什么
一个工具,主要是能让 JS 开发者给 JS 代码加上独特的“注释”。当然这“注释”就是类型。
所以 TS 本质上就是 JS,只是多了一些类型“注释”。
题外话:说它本质是 JS 是错误的,因为编译器就是万能的,能把 TS 变成任何底层开发语言(webassembly、java 等都可以),但 大佬们开出 TS 的最终目的,是为了去服务 JS。
const a = 100;
const a: number = 100;
有什么用,明明还多写了代码?
1、给人“注释”,当然这个例子太简单了,假如我们导入一个依赖库,我们看类型就能知道它有什么方法和属性,而没必要去读源代码。
2、给编辑器“注释”,编辑器可以在接下来的代码里智能提示。
3、给编译器“注释”,限制接下来的代码对变量的使用。
在传统的强类型语言中,类型标识不仅仅只有限制作用,还决定着如何给变量分配内存,对运行时有影响。但在 TS 中的类型标识是不会对运行时有影响的。
因此在 TS 中即使编译出错了,但还是可以生成运行时的 JS 代码(在 tsconfig.json 中有一个noEmitOnError
健,用来配置是否在出错时不生成代码)。
当然重要的还是这个限制作用和代码的智能提示,都能在一定程度上减少错误。
为什么要限制类型
这里说的作用仅限于 TS,传统强类型语言中的类型,作用不仅限于如此。
主要还是减少未知错误的发生,例如像下面的代码:
let a = '2132131';
// 省略一堆代码
// ...
a = 123321321;
// ....
// 省略一堆代码
// 访问a上面的属性或方法
a.length;
以上代码的错误只有在 JS 运行的时候,我们才能发现。
但假如我们用 TS 的话,这种错误,就会在我们写代码的时候,直接被编辑器识别。
强类型语言中的类型
说到底,类型到底是什么?为什么要有这个东西?
最开始的时候,编程面对的东西都是直接操作二进制数据。这个时候没有类型,有的只有 0 和 1。
后面慢慢的出现了汇编语言、高级程序语言。
语言中开始有了 一个 byte 的 Byte 类型、8 个 byte 的字节类型、2 个字节的短整型、4 个字节的整型...等等。
足以可见,不同的类型也决定着数据的存储。
在那个内存还只有 64k 的年代,一个 byte 都不浪费的严谨是有必要,且有意义的。
给数据分以不同的类型能够做到优化资源的配置。
在强类型语言中,指定类型,可以使得编译器优化数据的存储方式。
在 JS 这种动态语言中是否也有类型。
也是有的。只要存在着不同类型的数据,那就必然存在着类型。只是它没有强类型语言那么细致。
一般来说,在动态语言中,存在着两种数据的存放的方式。
一种是放在堆中,也就是所谓的引用类型,对应的特点是,数据大、数据可变、数据格式不定、动态分配内存空间,不连续存储。
一种是放在栈中,也就是所谓的值类型,对应的特点是,数据大小固定、格式固定、值固定、连续的存储空间。
在 JS 中,引用类型有,对象(包括但不限于数组[]、对象{}、类实例、函数等)。值类型有,数字、字符串、undefiend、null、Symbol、boolean、bigint
引用类型和值类型的区别在于,对变量另外赋值的时候,是重新拷贝一份值,还是说只是改变了引用地址。
TS 中的主要类型有哪些
由于 TS 是为 JS 而生的,所以它里面定义的类型都只是为了更好的服务 JS。
主要类型 number、boolean、string、null、undefined、any、enum、object、数组([]或者 Array两种表示方法)、Symbol、Bigint、Function 等
其中 undefined 和 null 是所有类型的子类型。言外之意如下:
// 都是合法的
const a: number = undefined;
const b: string = null;
其中 any 类型表示这个变量可以被赋值为任何数据类型
// 以下都是合法的
const b: number = 1;
let a: any = b;
a = 'ddd';
其中还有几种类型需要注意,便是 Object(大写)、Boolean、Number、String。他们和小些的区别在于,一般这些大写的表示是这些构造函数构造出的对象,小写的代表字面量对象。
interface
和type
区别,一般 interface 用来定义全新的类型,type 用来定义组合类型(交叉类型|、联合类型&)
interface Demo {
a: number;
b?: string; // 可选类型
}
// d 既可以是字符串也可以是数字
type d = string | number;
// 类型别名
type e = Demo;
在 Typescript 中还有一个特殊的存在,便是Class
,类,即是有效的值也是有效的类型。
范型,其实本质上就是定义类型的时候可以传参数。例如:
interface demoFunc<T> {
(num: T): T;
}
// 其中 T 就是一个类型参数,参数不同,它最终表示的类型也是不同的。
let demo:demoFunc<string> = (str: string) => str;
let demo1:demoFunc<number> = (str: number) => str;
// 不能将类型“(str: number) => number”分配给类型“demoFunc<string>”。
// 参数“str”和“num” 的类型不兼容。
// 不能将类型“string”分配给类型“number”。
// let demo: demoFunc<string> = (str: number) => str;
在 TS 中,类型具有可编程性,这也是
TS
比较高级和复杂的部分。这样的灵活性使得TS
能在很多复杂的边界情况下定义类型。
项目中的tsconfig.json
文件是什么?与 jsconfig.json
有什么区别?
jsconfig.js
的唯一作用就是用来指导编辑器(vscode)如何更好的工作。与tsconfig.json
的区别,就是jsconfig.json
默认allowJs
为true
tsconfig.json
除了能够指导编辑器如何工作之外,还能配置typescript
默认的编译器tsc
命令工具,如何去编译代码和输出代码。
例如,我们可以通过设置removeComments
为true
来移除tsc
命令生成的 js 代码中的注释。
tsconfig.json
文件的存在,同时也标志着这是个项目根目录,当我们在项目目录下执行npx tsc
时,tsc
编译器工具会自动使用项目目录中的tsconfig.json
来输出编译后的代码
webpack、babel 怎么处理 TS 文件
webpack 打包工具本身并不能直接识别.ts
文件,需要通过 babel-loader 来处理它。
babel-loader 不同于 typescript 自带的tsc
命令工具,babel-loader 只是单纯的把所有的类型标识删除掉。并不会在处理期间去检查 ts 中的类型错误。
由于 webpack 使用的是 babel 这个编译工具,因此 tsconfig.json 中的配置其实对于 webpack 这类工作流来说是没用的。
因此不能通过配置tsconfig.json
,来配置我们项目的打包结果。
例如不能通过设置tsconfig.json
中的removeComments
来移除我们打包 bundle 中的注释。
TS 自带的编译器主要做什么
当我们安装好typescript
时,我们同时也安装好了它提供的编译器。在项目中可以通过npx tsc
命令来使用。
// index.ts
const add = (n1: number, n2: number) => n1 + n2;
var add = function (n1, n2) {
return n1 + n2;
};
当我们在终端执行npx tsc ./index.ts
时,tsc
编译器会给我们生成一个 js 文件。假如我们不带其他配置执行这句命令,那么tsc
会自动生成 es5 的代码。
不管什么样的编译器,对于它来说,我们写的代码,都是字符串。无论它要分析或者做其他什么操作,都需要将其转换成 书写编译器本身那门语言所能理解的对象。那个对象便是抽象语法树,也就是 AST。
源代码 -> 扫描器 -> token 流 -> 解析器 -> AST -> 目标代码。
有了 AST,就可以做很多事情。
写 react 的 JSX、Vue 的模版语言、babel 做的转换、Less、Sass 等,都是先将源代码转换成 AST 后,最终处理 AST 结构,并且根据处理后的 AST 转换成目标代码。