typescript
typescript简介
- 由微软开发的一款开源的编程语言
- 扩展了JavaScript的语法,是Javascript的超集,并遵循最新的
es6、es5规范 - 有点像java、C#这样的面向对象语言,可以让
js开发大型企业项目 - 最新的 Vue 、React 也可以集成 TypeScript
- 谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+就是基于 Typescript 语法
- Nodejs框架 Nestjs、midway 中用的也是 TypeScript 语法
安装
npm i -g typescript
或
cnpm i -g typescript
或
yarn global add typescript
验证:
重启终端后,执行命令:
tsc -v
注:用yarn安装typescript后,可能无法直接使用tsc命令,需要配置环境变量才行。
自动编译
方式一: 命令行直接编译
// 在当前目录下编译生成js文件
tsc a.ts
// 指定生成目录
tsc a.ts --outDir './dist'
方式二: 通过tsconfig.json配置文件自动编译
自动生成tsconfig.json:
tsc --init
修改tsconfig.json配置参数:
{
...
"outDir": "./dist"
...
}
配置vscode自动监听并编译:
点击vscode菜单中的 Terminal(快捷键shift + command + p)
=> Run Task
=> typescript
=> 找到当前项目下的tsconfig.json
=> 选择"tsc: watch: xxx/tsconfig.json
修改a.ts文件后,会自动watch并编译生成a.js文件
编译上下文
tsconfig.json配置
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 生成的 ECMAScript 目标语言版本: ES3(default), ES5, ES2015, ES2016, ES2017, ESNEXT
"module": "commonjs", // 指定生成的模块类型: amd, cmd, umd commonjs es2015, system
"outDir": "./out", // 编译后的输出目录
"rootDir": "./", // 用来控制输出的目录结构,比如把那一层目录放到outDir目录下
"outFile": "./output.js", // 将输出文件合并为一个文件,只支持module是amd、system
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
/* 严格类型校验选项 */
"strict": true, // 是否启用严格模式
"strictNullChecks": false, // 检查undefined、null是否赋值给了其他类型,默认true不允许赋值给其他类型
/* 模块解析选项 */
"moduleResolution": "node", // 模块解析策略,以什么顺序方式来加载引入的模块(见下链接)
"baseUrl": "./", // 解析非相对模块的基地址
"paths": {
// 路径映射,相对于 baseUrl
"@/*": ["./src/*"], // 就可以在代码里 import test from '@/utils';
"jquery": [
"node_modules/jquery/dist/jquery.slim.min.js"
]
},
"incremental": true, // 增量编译
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印编译信息
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在 AMD 模块中
"lib": [], // TS 需要引用的库,即声明文件,es5 默认 "dom", "es5", "scripthost"
"allowJs": true, // 允许编译 JS 文件(js、jsx)
"checkJs": true, // 允许在 JS 文件中报错,通常与 allowJS 一起使用
"rootDir": "./", // 指定输入文件目录(用于输出)
"declaration": true, // 生成声明文件
"declarationDir": "./d", // 声明文件的路径
"emitDeclarationOnly": true, // 只生成声明文件
"sourceMap": true, // 生成目标文件的 sourceMap
"inlineSourceMap": true, // 生成目标文件的 inline sourceMap
"declarationMap": true, // 生成声明文件的 sourceMap
"typeRoots": [], // 声明文件目录,默认 node_modules/@types
"types": [], // 声明文件包
"removeComments": true, // 删除注释
"noEmit": true, // 不输出文件
"noEmitOnError": true, // 发生错误时不输出文件
"noEmitHelpers": true, // 不生成 helper 函数,需额外安装 ts-helpers,目前也可以用 importHelpers 解决。
"importHelpers": true, // 通过 tslib 引入 helper 函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器的实现(es3/5)
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": false, // 在代码中注入 "use strict";
"noImplicitAny": false, // 不允许隐式的 any 类型
"strictNullChecks": false, // 不允许把 null、undefined 赋值给其他类型变量
"strictFunctionTypes": false, // 不允许函数参数双向协变
"strictPropertyInitialization": false, // 类的实例属性必须初始化
"strictBindCallApply": false, // 严格的 bind/call/apply 检查
"noImplicitThis": false, // 不允许 this 有隐式的 any 类型
"noUnusedLocals": true, // 检查只声明,未使用的局部变量
"noUnusedParameters": true, // 检查未使用的函数参数
"noFallthroughCasesInSwitch": true, // 防止 switch 语句贯穿,分支没有 break
"noImplicitReturns": true, // 每个分支都要有返回值
"esModuleInterop": true, // 允许 export = 导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中访问 UMD 全局变量
"rootDirs": [
"src",
"util"
], // 将多个目录放在一个虚拟目录下,用于运行时
"listEmittedFiles": true, // 打印输出的文件
"listFiles": true // 打印编译的文件(包括引用的声明文件)
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/global.tsx"
],
"exclude": [
"dist"
]
}
声明空间
包括:类型声明空间 和 变量声明空间
- 类型声明空间
class Foo {} // 类可以作为变量赋值
interface Bar {} // 接口类型不能作为变量赋值
type Tow {} // 类型别名
- 变量声明空间
const a = Foo; // 类可以
const b = Bar; // 接口不能赋值
const c = 1;
const d: c; // 变量不能作为类型声明
模块
包括:全局模块和文件模块。文件模块存在本地作用域,变量只在当前文件生效,除非对其进行导出export。
只要某个ts文件中出现import或export则就被自动标记为文件模块,否则是全局模块。
全局模块中的全局变量,在多个全局模块里可以互相引用,编译时不会出错,但执行时会报错找不着变量。
文件模块用import/export对变量进行导入/导出操作。
示例:
全局模块 global.ts
const global1 = '这是全局模块里的变量';
文件模块 scope.ts
console.log(global1); // 引用全局模块里变量,编译时正常,执行时报错
import { scope2 } from '@/scope2';
import { scope2 as s } from '@/scope2'; // 重命名变量或类型
文件模块 scope2.ts
// es modle方式导出
// error TS2691: An import path cannot end with a '.ts' extension. Consider importing './scope.js' instead.
// 去掉文件后缀即可
export const scope2 = '这是文件模块里的变量';
数据类型
ts中的数据类型包括:
number 数字类型
string 字符串类型
boolean 布尔类型
undefined
null
any 任意类型
void
never
array 数组类型
tuple 元组类型
enum 枚举类型
readonley 只读类型
| 联合类型
object
Object
{}
- 定义方式:定义变量时,在变量后边加冒号和类型,如
let a: boolean = false;。 - 如果定义时未赋值,则结果为 undefined
let unknown1: number; // undefined
let unknown2: undefined; // undefined
let unknown3: number | undefined; // undefined
- 在变量被定义好类型后,不允许再被赋值为其他类型的数据。如:
let x: number = 2;
x = 'abc'; error TS2322: Type 'string' is not assignable to type 'number'
number、string、boolean
let num: number = 2;
// num = 'abc'; // error TS2322: Type 'string' is not assignable to type 'number'
let str: string = 'ab';
let bool: boolean = false;
any、unknown、undefined、null、void、never
let any1: any = 20;
any1 = 'abc';
// 如果未指定dom结点类型时会报错:error TS2339: Property 'style' does not exist on type 'Element'
let dom1: any = document.querySelector('.app'); // ok
dom1.style.background = '#ff0';
// 设置dom类型:
let dom2: any = document.querySelector('.app');
let dom3 = <HTMLElement>document.querySelector('.app');
unknown 与 any 区别:
- 任何类型的值都可以赋值给
any,any类型的值也都可以赋值给任何类型 - 任何类型的值都可以赋值给
unknown,但unknown只能赋值给unknown和any
let s: number | string = 2;
let any1: any = [];
let un1: unknown = 3;
s = any1;
any1 = s;
un1 = s;
un1 = any1;
any1 = un1;
// s = un1; // error Type 'unknown' is not assignable to type 'number'
void:
- 只有设置
strictNullChecks: false时,void类型可赋值给null和undefined,否则不能直接赋值 - 一般用于定义函数返回值类型。函数无返回值时可以定义成
void,虽然默认返回undefined,但不能定义成undefined,否则会报错
never:
表示的是那些永不存在的值的类型。永远不存在值的情况有两种:
- 函数抛出异常(程序执行终端,达不到终点)
- 死循环(始终达不到终点)
function error(msg: string): never { // OK
throw new Error(msg);
}
function loop(): never { // OK
while (true) {};
}
注意:
never同null和undefined,也是任何类型的子类型,也可以赋值给任何类型- 除了
never本身之外,任何类型都不能赋值给never类型(没有子类型)
数组
let ary1: string[] = ['a', 'b'];
let ary2: Array<any> = [2, 'b'];
// 使用接口定义数组成员:
interface IAryObj {
id: number;
name: string;
}
let ary3: Array<IAryObj> = [
{id: 1, name: 'zhang'},
];
let ary4: IAryObj[] = [
{id: 1, name: 'zhang'},
]
元组
数组中定义不同类型的成员
let tuple1: [number, boolean, any] = [3, false, {a: 2}]; // 每项单独指定类型
let tuple2: Array<string> = ['a', 'b']; // 指定所有项都是string
let tuple3: Array<any> = [5, 'b'];
解构赋值:
let info: [number, string] = [3, 'abc'];
可选类型:(如:一维、二维、三维坐标)
let pixel: [number, number?, number?] = [2, 3];
let info: [number?, string] = [3, 'abc']; // error 可选类型后面不能有必选类型
枚举
enum Color {
red, // 0
green = 6, // 6
blue // 7
};
// 如果没指定值,则默认从索引0开始,后一个是前一个值加1
let color: Color = Color.blue;
console.log(Color.red, Color.green, Color.blue); // 0 6 7
enum Status {
success = 0,
error = -1,
unLogin = 'unlogin'
};
let state: Status = Status.unLogin; // unlogin
联合
let s: number | string = 2;
let ary1: (number | string)[] = ['a', 3];
let ary2: Array<number | string> = ['a', 3];
readonly只读
let on: readonly[number, boolean] = [3, false];
// on[0] = 2; // error 只读
object、Object、{}
js 中的原始类型包括:string、boolean、number、bigint、symbol、null 和 undefined。
- 小 object 代表的是所有非原始类型,不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,
null和undefined类型也不能赋给 object - 大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object
- 大 Object 包含原始类型,小 object 仅包含非原始类型,所以大 Object 似乎是小 object 的父类型。实际上,大 Object 不仅是小 object 的父类型,同时也是小 object 的子类型
- Object 即是 object 的父类,也是 object 的子类,但两者并不等价,官方说用小 object
- {} 空对象类型和大 Object 一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null 和 undefined 也不能赋给 {}
let o1: object;
// obj = 1; // error 不能赋值原始类型
// obj = undefined; // error 严格模式下不允许
// obj = null; // error 严格模式下不允许
o1 = {}; // ok
o1 = []; // ok
let o2: Object;
o2 = 1;
// o2 = undefined; // error 严格模式下不允许
// o2 = null; // error 严格模式下不允许
o2 = {};
// Object 即是 object 的父类,也是 object 的子类
type x = object extends Object ? true : false; // true
type y = Object extends object ? true : false; // true
o1 = o2;
o2 = o1;
let o3: {}; // {} 和 大 Object 一样
// o3 = null; // error 严格模式下不允许
部分参考:2021 typescript史上最强学习入门文章(2w字)
函数
// 默认参数
function func2(id: number, name: string = 'zhang'): string {
return name;
}
// 匿名函数
// 可选参数:参数后边加?,可选参数后边不能出现必选参数
let func4 = function (id?: number, name?: string) {
return name;
}
// 剩余参数
let func = (...ret: string[]): string => {
return ret.join('-');
}
// 函数重载
// 为同一个函数提供多个函数类型定义(和java稍微不一样)
function getUser(name: string): string;
function getUser(name: string, age: number): string;
function getUser(name: any, age?: any): any {
if (age) {
return name + ' ' + age;
} else {
return name;
}
}
// 使用接口定义函数类型
interface IFc7 {
(id: number, name: string): object;
}
let fc7: IFc7 = (id: number, name: string) => ({id, name});
let fc8: IFc7 = function(id: number, name: string): object {
return {id, name};
}
类
- 继承:extends
- 初始化父类构造方法:super()
- 修饰符
- public:在
当前类内部、子类内部、类外部均可访问 - protected:在
当前类内部、子类内部中可访问 - private:在
当前类内部可访问
- public:在
如果成员属性、成员方法前面没有修饰符,则默认是共有public
类的定义
class Father {
name: string; // ts中定义成员属性,需要先声明其类型。es6中不需要(默认共有属性,省略public关键字)
public sex: string; // 共有属性
constructor(name: string, sex?: string) { // 实例化类时触发
this.name = name; // 当前类内部访问公有属性
this.sex = 'male';
}
getName(): void { // 默认共有方法,省略public关键字
console.log(this.name);
}
public sleep() {
this.smile(); // 当前类内部访问受保护方法
this.drive(); // 当前类内部访问私有方法
console.log(`${this.name} is sleeping`);
}
protected smile() {
console.log('can smile');
}
private drive() {
console.log('can drive');
}
}
let f = new Father('zhangsan');
f.getName();
f.name; // 在类外部访问public共有属性
// f.smile(); // error 外部无法访问受保护成员
// f.drive(); // error 类外部不能访问私有方法
类的继承
- 通过
extends、super实现继承
class Child extends Father {
constructor(name: string) { // 貌似子类不写构造方法也ok?
super(name); // 初始化父类构造方法
}
// 子类有和父类同名成员方法,优先调用子类本身的
getName(): void {
this.smile(); // 子类内部也可以访问受保护成员
// this.drive(); // error 子类内部无法访问父类私有方法
console.log(`I am ${this.name}`);
}
}
console.log('----------------');
let c = new Child('Child');
c.getName(); // 优先访问自身方法,没有时再访问父类的
c.name; // 类外部访问公有属性
c.sleep(); // 子类访问父类公有方法
// c.smile(); // error 外部无法访问受保护成员
// c.drive(); // error 类外部不能访问私有方法
静态属性和方法
- 通过
static定义,一般不常用 - es6中的静态属性和方法,只能通过类、或类内部的静态方法去调用,不能通过this调用
es5中定义静态属性和静态方法
function AbsFunc () {
this.name = 'abs'; // 实例属性
this.go = function () {} // 实例方法
}
AbsFunc.sleep = function () {}; // 静态方法
let abs = new AbsFunc();
abs.go(); // 实例属性和方法,需要通过实例对象去调用
AbsFunc.sleep(); // 静态属性和方法,需要通过类名去调用
// 例如jquery中:
function $ () {
return new Base(ele); // 返回Base实例对象
}
function Base(ele) {
this.css = function () {}
}
$.get = function() {} // 静态方法
$('.app').css(); // 实例方法:通过实例对象调用,$('.app')返回实例对象
$.get(url); // 静态方法:通过类名调用,$返回静态类
ts中的静态属性和方法:
class StaticCls {
name: string;
static sex: string = 'male'; // 静态属性
constructor(name: string) {
this.name = name;
}
static getSex(sex: string) {
StaticCls.sex = sex;
console.log('static class:', StaticCls.sex); // 不能通过this.sex调用
}
}
StaticCls.getSex('female'); // female
多态
- 属于继承
- 父类定义完方法后不去实现,由继承的子类去实现,每个子类有不同的表现
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
eat() { // 定义完方法后不去实现
}
}
class Boy extends Person {
constructor(name: string) {
super(name);
}
eat() {
return this.name + ' eat';
}
}
抽象类和抽象方法
- 用
abstract关键字定义抽象类和抽象方法,抽象方法只能放在抽象类里面 - 抽象类不能直接被实例化,只是提供其他类继承的基类
- 抽象类中的抽象方法不包含具体的实现,必须在派生类中实现
abstract class Person2 {
constructor(name: string) {}
abstract eat(): any;
}
// let per2 = new Person2('li'); // 抽象类不能被实例化
class Boy2 extends Person2 {
constructor(name: string) {
super(name);
}
eat(): any { // 必须实现父类中的抽象方法
return 'eating';
}
}
let b2 = new Boy2('boy');
b2.eat(); // eating
接口
interface IBook {
id: number;
type?: string; // 可选属性
}
let book: IBook = {id: 1}
function get(param: IBook) {}
接口是对行为的抽象,定义标准,起到一种限制和规范作用,并不关心具体的实现和内部状态,而具体的实现是由类去完成。
ts与node
es6(es2015)中引入模块化机制,node基本支持es6中的所有语法,但不支持模块化机制,即import/export
如何让node运行es6 module呢?
- es6 module 改成commonjs
- rollup转换
- babel-node直接允许es6
1. es6 module 改成commonjs
scope.ts文件:
//commonjs方式导出
// error TS2580: Cannot find name 'exports',加上declare声明即可
declare const exports: any;
exports.scope1 = '这是文件模块(本地作用域)';
或
declare const module: any;
module.exports = {
scope1: '这是文件模块(本地作用域)'
};
index.ts文件:
// error TS2580: Cannot find name 'require',加上declare声明即可
declare const require: any;
const {scope1} = require('./scope');