TypeScript 的问题
ts 是 js 的超集。
string, String 的区别是什么?
string, js 的基本类型。(字符串类型)
String, js 中 string 的包装类。
何时使用 string? 何时使用 String?
99% 的情况下,使用 string
let nameObj: String = new String("Alice"); 时才会使用到 String。
ts 可以使用哪些类型?
js 基本类型:string, number, bool,void, null/undefined, Symbol, bigint
ts 定义的类型: void, unknown, never, Array<number>, enum
如何初始化 tsconfig.json ?
tsc --init
TS 类型
- top type(顶级类型): any unknown
- Object
- Number String Boolean
- number string boolean
- 1 'wwww' false
- never
unknown 和 any
任何类型都可以赋值给 any, unknown 类型。
unknown 只能赋值给自身, 或者 any 类型。
let a: any = "1";
let b: unknown = 2;
let c: number = 3;
a = b;
a = c;
b = a;
b = c;
c = a;
// c = b; // unknown 类型只能赋值给 unknown 或 any 类型
console.log(c);
unknown 没有办法读任何属性, 方法也不可以。 (编译器会提示)
let w: unknown = {
name: "wangzhy",
};
// console.log(w.name); //编辑器会报错。 'w' is of type 'unknown'.
unknown 比 any 会更安全。
object, Object, 三个类型的区别
Object 表示包含了所有类型 (string, number, boolean, [], , 函数)
object 表示非原始类型 (非 string, number, boolean, bigint, Symbol, null, undefined 的类型)
{}(字面量) 表示 new Object, 等同于 Object
接口(interface)和对象类型
重名, 重合
interface 定义的结构体,在创建的时候,字段必须一致(不多不少)。
interface A {
name: string;
}
let a: A = {
name: "wang",
};
遇到重名时,会自动合并。
interface A {
name: string;
}
interface A {
age: number;
}
let a: A = {
name: "wang",
age: 123,
};
任意 key
interface A {
age: number;
// 注意, 这里 propName 要保存所有字段的类型。 比如,此代码案例中,必须包含 number 类型
[propName: string]: any;
}
interface A {
age: number;
[propName: string]: number | string;
}
let a: A = {
name: "wang",
age: 123,
aaa: "123",
};
可选字段
使用 ?
interface A {
age?: number;
[propName: string]: number | string;
}
let a: A = {
name: "wang",
aaa: "123",
};
readonly
interface A {
readonly age?: number;
[propName: string]: number | string;
}
let a: A = {
name: "wang",
age: 123,
aaa: "123",
};
// a.age = 11; // Cannot assign to 'age' because it is a read-only property.
接口继承
使用 extends 关键字
函数类型
interface Fn {
// 定义一个参数为 string 类型,返回类型为 number[] 的函数
(name: string): number[];
}
const fn: Fn = function (name: string) {
return [1];
};
数组类型
定义一个数组
const arr: number[] = [1, 2, 3, 4];const arr: Array<number> = [1, 2, 3, 4]; // 泛型
多维数组
const arr: number[][] = [
[1, 2, 3],
[2, 3, 4],
];
const arr: Array<Array<number>> = [
[1, 2, 3],
[2, 3, 4],
];
大杂烩数组
let arr: (string | number | boolean)[] = ["q", 1, "1", true];
...args
...args 是剩余参数语法,将函数多余参数收集到一个数组中。
function fn(...args: number[]) {
console.log(args);
}
fn(1, 2, 3);
IArguments
function arr6(...args: number[]) {
console.log(args);
let a: A = arguments;
for (const key in a) {
console.log(key, a[key]);
}
}
interface A {
callee: Function;
length: number;
[index: number]: any;
}
函数
函数参数默认值
function add(a: number = 10, b: number = 20): number {
return a + b;
}
可选参数
function add(a: number = 10, b?: number): number {
return b ? a + b : a;
}
需要注意的是:可选参数与参数默认值不能同时使用对一个函数参数使用。
ts 中可以定义 this 的类型,在 js 中无法使用。必须第一个参数定义 this 的类型
interface User {
name: string;
age: number;
add: (num: number) => void;
}
let u: User = {
name: "w",
age: 18,
// 第一个参数定义 this 的类型, 传参的时候忽略第一个参数
add(this: User, num: number) {
this.age++;
},
};
u.add(1);
函数重载
let users: number[] = [1, 2, 3];
function findNum(): number[];
function findNum(id: number): number[];
function findNum(add: number[]): number[];
function findNum(ids?: number | number[]): number[] {
if (typeof ids === "number") {
return users.filter((v) => v === ids);
} else if (Array.isArray(ids)) {
users.push(...ids);
return users;
} else {
return users;
}
}
console.log(findNum([5, 1, 7]));
类型断言|联合类型|交叉类型
联合类型
let phone: number | string = "123111111111";
交叉类型
interface People {
name: string;
}
interface Man {
age: number;
}
const w = (man: People & Man): void => {
console.log(man);
};
w({
name: "w",
age: 18,
});
class
借助下面的代码来理解 class
interface Options {
el: string | HTMLElement;
}
// vue 接口
interface VueCls {
options: Options;
init(): void;
}
interface Vnode {
tag: string; // html 标签
text?: string;
children?: Vnode[]; // 子节点
}
// 虚拟 dom
class Dom {
// 创建节点
private createElement(el: string) {
return document.createElement(el);
}
// 填充文本
private setText(el: HTMLElement, text: string | null | undefined) {
el.textContent = text ?? null;
}
// 渲染函数
protected render(data: Vnode) {
let root: HTMLElement = this.createElement(data.tag);
if (data.children && Array.isArray(data.children)) {
data.children.forEach((item) => {
let child = this.render(item);
root.appendChild(child);
});
} else {
this.setText(root, data.text);
}
return root;
}
}
class Vue extends Dom implements VueCls {
// 定义属性
options: Options;
// 构造函数
constructor(options: Options) {
super(); // 调用父类的构造函数 // 父类.prototype.constructor.call
this.options = options; // 初始化属性
this.init();
}
static version(): string {
return "0.0.1";
}
init(): void {
// 虚拟 DOM
let data: Vnode = {
tag: "div",
children: [
{
tag: "section",
text: "我是子节点 1",
children: [
{ tag: "option", text: "o1" },
{ tag: "option", text: "o2" },
{ tag: "option", text: "o3" },
{ tag: "option", text: "o4" },
],
},
{
tag: "section",
text: "我是子节点 2",
},
],
};
let app =
typeof this.options.el === "string"
? document.querySelector(this.options.el)
: this.options.el;
// 渲染
app!.appendChild(this.render(data));
}
}
new Vue({
el: "#app",
});
console.log(Vue.version());
class Ref {
private _value: any;
constructor(value: any) {
this._value = value;
}
get value() {
return this._value + " ^-^";
}
set value(newVal) {
this._value = newVal + "wzy";
}
}
const ref = new Ref("哈哈哈");
console.log(ref.value);
ref.value = "呼呼呼";
console.log(ref.value);
抽象类 abstract
通过 abstract 关键字定义。
抽象方法只是用来描述方法原型,具体实现由字类实现。
抽象类不难被实例化。
import { log } from "node:console";
import { loadavg } from "node:os";
abstract class AVue {
name: string | undefined;
constructor(name?: string) {
this.name = name;
}
abstract init(name: string): void;
}
class React extends AVue {
constructor() {
super();
}
init(name: string): void {}
setName(name: string) {
this.name = name;
}
}
const react = new React();
react.setName("wangzhy");
元组类型
let arr: readonly [x: number, y?: boolean] = [1];
type first = (typeof arr)[0];
枚举类型
enum Color {
red = 1,
green = "green",
blue = 3,
}
const enum Types {
success,
fail,
}
类型推断
let arr = [1, 2, 3]; // number[]
let str1; // any
type S = string & B;
interface A extends B {
name: string;
}
interface B {
age: number;
}
extends 在与 type 使用时表示包含的意思
type num = 1 extends number ? 1 : 0; // 1

never
不存在的状态, 无法达到的状态
type A = number & string; // never
function fn(): never {
throw new Error("err");
}
type B = void | number | never; // void | number
type C = "c" | "x" | "k" | "xxx";
function kun(value: C) {
switch (value) {
case "c":
break;
case "x":
break;
case "k":
break;
default:
// 兜底逻辑, 如果 C 新增了类型,这里会出现报错提示。
// Type '"xxx"' is not assignable to type 'never'.
const error: never = value;
break;
}
}
symbol 类型
let a1: symbol = Symbol(1); // 支持三种类型, string, number, undefined
let a2: symbol = Symbol(1);
console.log(a1 === a2); // false
// 如何让 2 个 symbol 返回 true
console.log(Symbol.for("w") === Symbol.for("w")); // true
// 解决 key 重复的问题
let obj = {
name: "obj",
[a1]: 1,
// ...
[a2]: 20,
};
// for in 不能读到 symbol 类型的 key
for (let key in obj) {
console.log(key);
}
console.log(Object.keys(obj)); // 无法获取到 symbol 类型的 key
console.log(Object.getOwnPropertyNames(obj)); // 无法获取到 symbol 类型的 key
console.log(Object.getOwnPropertySymbols(obj)); // 只能获取 symbol 类型的 key
console.log(Reflect.ownKeys(obj)); // 能够获取到所有的 key
生成器, 迭代器
1. 生成器
function* gen() {
yield Promise.resolve("w1");
yield Promise.resolve("w2");
yield Promise.resolve("w3");
yield Promise.resolve("w4");
yield Promise.resolve("w5");
yield Promise.resolve("w6");
}
const w = gen();
var next = w.next();
while (!next.done) {
console.log(next);
next = w.next();
}
console.log(next);
2. 迭代器
核心是 Symbol.iterator。
for...of 的底层实现。对象不可用,对象是没有 Symbol.iterator 方法的。
const each = (value: any) => {
let It: any = value[Symbol.iterator]();
let next: any = {
done: false,
};
while (!next.done) {
next = It.next();
if (!next.done) {
console.log(next.value);
}
}
};
使对象支持迭代器, [...obj]
let obj = {
name: "w",
age: 18,
max: 3,
current: 0,
[Symbol.iterator]() {
return {
max: this.max,
current: this.current,
next() {
if (this.current === this.max) {
return {
value: undefined,
done: true,
};
} else {
return {
value: this.current++,
done: false,
};
}
},
};
},
};
let x = [...obj];
console.log(x);
{...obj} 的底层实现(浅拷贝)
function fn(v: any): any {
let result = {};
let keys = Reflect.ownKeys(v);
for (const key of keys) {
result[key] = v[key];
}
return result;
}
泛型
方法名后面跟 <T>, T 就是泛型。
function fn<T>(a: T, b: T): T[] {
return [a, b];
}
// <number> 可以不用写, 它自己会进行类型推导
console.log(fn<number>(1, 2));
console.log(fn(1, 2));
type/interface 与泛型一起使用
type A<T> = string | number | T;
let a: A<boolean> = "z";
let b: A<boolean> = 1;
let c: A<boolean> = true;
interface Data<T> {
msg: T;
}
let data1: Data<string> = {
msg: "msg",
};
let data2: Data<number> = {
msg: 1,
};
let data3: Data<boolean> = {
msg: true,
};
泛型的其他用法
多个泛型
function fn1<T, K>(a: T, b: K): Array<T | K> {
return [a, b];
}
fn1(1, false);
fn1(1, "msg");
简单封装一个 axios
const axios = {
get<T>(url: string): Promise<T> {
return new Promise((resolve, reject) => {
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
}
};
xhr.send(null);
});
},
};
interface Data {
compilerOptions: {
[key: string]: string;
};
}
axios.get<Data>("./tsconfig.json").then((res) => {
console.log(res.compilerOptions);
});
泛型约束
<T extends number>
<T extends Data>
function fn<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
type Options<T extends object> = {
[key in keyof T]?: T[key];
};
type B = Options<Data>;
模块扩展语法 (Module Augmentation)
tsconfig.json
生成 tsconfig.json 配置文件
tsc --init
namespace
namespace和interface一样, 同名会进行合并。namespace定义的方法和变量需要导出export
namespace Test {
// 变量, 方法
export let a = 1;
// 嵌套 namespace
export namespace Test2 {
export let a = 121;
}
}
namespace Test {
export let b = 11;
}
模块解析
Commonjs --> Nodejs
require("xxx");
exports.xxx = function () {};
module.exports = xxx;
AMD --> require.js
define("module", ["dep1", "dep2"], function (dep1, dep2) {
// ...
});
require(["module", "../app"], function (module, app) {
// ...
});
CMD --> seajs
define(function (require, exports, module) {
var a = require("./a");
a.doSomething();
var b = require("./b");
b.doSomething();
});
UMD --> AMD 和 CommonJS 的糅合
(function (window, factory) {
// nodejs 环境
if (typeof module === "object" && typeof module.exports === "objects") {
module.exports = factory();
}
// 是不是 amd 规范
else if (typeof define === "function" && define.amd) {
define(factory);
}
// 使用浏览器环境
else {
window.eventUtil = factory();
}
})(this, function () {
// ...
});
ES6 规范, import/export
// import test, { name, hello, cur } from "./test";
// console.log(test);
// console.log(name);
// hello();
// console.log(cur.toString());
// import * as api from "./test";
// console.log(api);
if (true) {
// 动态引入
import("./test").then((res) => {
console.log(res);
});
}
export default {
age: 18,
};
export const name = "wangzhy";
export const hello = () => {
console.log("hello");
};
const cur = Date.now();
export { cur };
声明文件 d.ts (declare.ts)
d.ts 文件的作用就是告诉别人你写的代码应该怎么用(参数类型,可以让编译器给出提示)。
declare 关键字。
使用第三方库(js 写的)时, 需要引入对应的 types 库, npm i --save-dev @types/express
也可以自己手写 d.ts 文件。 如 express.d.ts
自己实现一个 express.d.ts
import express from "express";
const app = express();
const router = express.Router();
app.use("/api", router);
router.get("/api", (req, res) => {
res.json({
code: 200,
msg: "success",
});
});
app.listen(9001, () => {
console.log(":9001");
});
express.d.ts
declare module "express" {
interface Router {
get(path: string, cb: (req: any, res: any) => void): void;
}
interface App {
use(path: string, router: any): void;
listen(port: number, cb?: () => void): void;
}
interface Express {
(): App;
Router(): Router;
}
const express: Express;
export default express;
}
mixin
对象合并
- 浅拷贝
{ ...a, ...b }Object.assign({}, a, b)
- 深拷贝
structuredClone(a)
类的混入
class Logger {
log(msg: string) {
console.log(msg);
}
}
class Html {
render() {
console.log("render");
}
}
class App {
run() {
console.log("run");
}
}
// 定义一个类型别名, 表示一个构造函数
type Custructor<T> = new (...args: any[]) => T;
function pluginMixins<T extends Custructor<App>>(Base: T) {
// 返回一个新的 class
return class extends Base {
private Logger: Logger;
private Html: Html;
constructor(...args: any) {
super(...args);
this.Logger = new Logger();
this.Html = new Html();
}
run() {
this.Logger.log("run");
}
render() {
this.Html.render();
}
};
}
const mixins = pluginMixins(App);
const app = new mixins();
app.run();
infer
用于在条件类型中动态推断类型。