乐趣区

关于typescript:表弟说看了这本书后他的TypeScript技术已经登峰造极了

在看这篇文章之前,我是强烈推荐 TypeScript 入门教程这本书的。因为这本书它是:从 JavaScript 程序员的角度总结思考,循序渐进的了解 TypeScript。文章起源也是该书,但听我一句话:踩过坑才代表会了。【倡议珍藏】

心愿你看这本书之前曾经具备了以下技能:

  • 纯熟应用 JavaScript 开发日常我的项目
  • 曾经据说过 TypeScript

一、什么是 TypeScript?

1、TypeScript 词解

Typed JavaScript at Any Scale。

增加了类型零碎的 JavaScript,实用于任何规模的我的项目。

以上是官网 [[1]](https://www.tslang.cn/ “[1]”) 对于 TypeScript 的定义。强调了两个重要个性:

  1. 类型零碎
  2. 实用于任何规模
    • *

2、TypeScript 的个性 

1、类型零碎

从 TypeScript 的名字就可以看进去,「类型」是其最外围的个性。

为什么要给 JavaScript 加上类型呢?

咱们晓得,JavaScript 是一种轻量级的解释性脚本语言。也是弱类型、动静类型语言,容许隐式转换,只有运行时能力确定变量的类型。正是因为在运行时能力确定变量的类型,JavaScript 代码很多谬误在运行时能力发现。TypeScript 在 JavaScript 的根底上,包装了类型机制,使其变身成为 动态类型 语言。在 TypeScript 中,不仅能够轻易复用 JavaScript 的代码、最新个性,还能应用可选的动态类型进行查看报错,使得编写的代码更强壮、更易于保护。

上面是 JavaScript 我的项目中最常见的十大谬误,如果应用 TypeScript,那么在 编写阶段 就能够发现并解决很多 JavaScript 谬误了:

类型零碎可能进步代码的品质和可维护性,通过一直的实际,以下两点尤其须要留神:

  • 类型有利于代码的重构,它有利于编译器在编译时而不是运行时捕捉谬误;
  • 类型是杰出的文档模式之一,函数签名是一个定理,而函数体是具体的实现。

TypeScript 是动态类型

类型零碎依照「类型查看的机会」来分类,能够 分为动静类型和动态类型

动静类型是指在运行时才会进行类型查看,这种语言的类型谬误往往会导致运行时谬误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动静类型,以下这段代码在运行时才会报错:

let zhifeiji = 110;
zhifeiji.split(' ');
// Uncaught TypeError: zhifeiji.split is not a function
// 运行时会报错(zhifeiji.split 不是一个函数),造成线上 bug

动态类型是指编译阶段就能确定每个变量的类型(编译时类型查看),这种语言的类型谬误往往会导致语法错误。TypeScript 在运行前须要先编译为 JavaScript,而在编译阶段就会进行类型查看,所以 TypeScript 是动态类型,这段 TypeScript 代码在编译阶段就会报错了:

let zhifeiji = 1;
zhifeiji.split(' ');
// Property 'split' does not exist on type 'number'.
// 编译时会报错(数字没有 split 办法),无奈通过编译

这段 TypeScript 代码貌似和 JavaScript 没有什么区别。

实际上因为 TypeScript 弱小的「类型推论 」能力使得大部分 JavaScript 代码都只须要通过大量的批改(或者齐全不必批改)就能变成 TypeScript 代码,即便不申明变量 zhifeiji 的类型,也能在变量初始化时主动推论出它是一个 number 类型。如上图没有申明为 number 却报错了 number,足够证实了 类型推论。

残缺的 TypeScript 代码是这样的:

let zhifeiji : number = 1;
zhifeiji.split(' ');
// Property 'split' does not exist on type 'number'.
// 编译时会报错(数字没有 split 办法),无奈通过编译

TypeScript 是弱类型

类型零碎依照「是否容许隐式类型转换」来分类,能够分为强类型和弱类型。

以下这段代码不论是在 JavaScript 中还是在 TypeScript 中都是能够失常运行的,运行时数字 1 会被隐式类型转换为字符串 '1',加号 + 被辨认为字符串拼接,所以打印出后果是字符串 '11'

console.log(1 + '1');
// 打印出字符串 '11'

TypeScript 是齐全兼容 JavaScript 的,它不会批改 JavaScript 运行时的个性,所以 它们都是弱类型

作为比照,Python 是强类型,以下代码会在运行时报错:

print(1 + '1')
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

若要修复该谬误,须要进行强制类型转换:

print(str(1) + '1')
# 打印出字符串 '11'

JavaScript 和 TypeScript 中不论加号两侧是什么类型,都能够通过隐式类型转换计算出一个后果——而不是报错——所以 JavaScript 和 TypeScript 都是弱类型。

尽管 TypeScript 是弱类型,但咱们能够通过 TypeScript 提供的类型零碎,以及 ESLint 提供的代码查看性能。使得 TypeScript 更像是一个「强类型」。

这样的类型零碎体现了 TypeScript 的外围设计理念:在残缺保留 JavaScript 运行时行为的根底上,通过引入动态类型零碎来进步代码的可维护性,缩小可能呈现的 bug。

2、实用于任何规模 

TypeScript 十分实用于大型项目,类型零碎能够为大型项目带来更高的可维护性,以及更少的 bug。

在中小型我的项目中因为有[类型推论],大部分类型都不须要手动申明了,所以不会很大水平的升高开发效率,故局部中小企业也会抉择它。

TypeScript 还能够和 JavaScript 共存。如果你是老的 JavaScript 我的项目,你想革新成 TypeScript 的我的项目,但又代码量太大并且我的项目要持续更新。那么你能够新的内容和文件用 TypeScript,老的不变。当闲下来时,能够逐渐将老的一个一个文件的降级为 TypeScript。

事实上,就算你素来没学习过 TypeScript,你也可能曾经在人不知; 鬼不觉中应用到了 TypeScript——在 VSCode 编辑器中编写 JavaScript 时,代码补全和接口提醒等性能就是通过 TypeScript Language Service 实现的:

一些第三方库原生反对了 TypeScript,在应用时就能取得代码补全了,比方 Vue 3.0[[8]](https://ts.xcatliu.com/introd… “[8]”):

[图片上传失败 …(image-1de711-1632538686036)]

有一些第三方库原生不反对 TypeScript,然而能够通过装置社区保护的类型申明库[[9]](https://ts.xcatliu.com/introd… “[9]”)(比方通过运行 npm install --save-dev @types/react 来装置 React 的类型申明库)来取得代码补全能力——不论是在 JavaScript 我的项目中还是在 TypeScript 中我的项目中都是反对的:

由此可见,TypeScript 的倒退曾经深刻到前端社区的方方面面了,任何规模的我的项目都或多或少失去了 TypeScript 的反对。

3、与规范同步倒退

TypeScript 的另一个重要的个性就是保持与 ECMAScript 规范同步倒退。

ECMAScript 是 JavaScript 外围语法的规范,自 2015 年起,每年都会公布一个新版本,蕴含一些新的语法。

一个新的语法从提案到变成正式规范,须要经验以下几个阶段:

  • Stage 0:展现阶段,仅仅是提出了探讨、想法,尚未正式提案。
  • Stage 1:征求意见阶段,提供形象的 API 形容,探讨可行性,要害算法等。
  • Stage 2:草案阶段,应用正式的标准语言准确形容其语法和语义。
  • Stage 3:候选人阶段,语法的设计工作已实现,须要浏览器、Node.js 等环境反对,收集用户的反馈。
  • Stage 4:定案阶段,已筹备好将其增加到正式的 ECMAScript 规范中。

一个语法进入到 Stage 3 阶段后,TypeScript 就会实现它。一方面,让咱们能够尽早的应用到最新的语法,帮忙它进入到下一个阶段;另一方面,处于 Stage 3 阶段的语法曾经比较稳定了,根本不会有语法的变更,这使得咱们可能释怀的应用它。

除了实现 ECMAScript 规范之外,TypeScript 团队也推动了诸多语法提案,比方可选链操作符(?.)、空值合并操作符(??)、Throw 表达式、正则匹配索引等。

3、TypeScript 和 JavaScript 的区别

TypeScript JavaScript
JavaScript 的超集用于解决大型项目的代码复杂性 一种脚本语言,用于创立动静网页。
能够在编译期间发现并纠正错误 作为一种解释型语言,只能在运行时发现错误
强类型,反对动态和动静类型 弱类型,没有动态类型选项
最终被编译成 JavaScript 代码,使浏览器能够了解 能够间接在浏览器中应用
反对模块、泛型和接口 不反对模块,泛型或接口
反对 ES3,ES4,ES5 和 ES6 等 不反对编译其余 ES3,ES4,ES5 或 ES6 性能
社区的反对仍在增长,而且还不是很大 大量的社区反对以及大量文档和解决问题的反对

4、怎么装置 TypeScript 和编辑器举荐

1、装置 TypeScript

TypeScript 的命令行工具装置办法如下:

npm install -g typescript
# 或者用 yarn

以上命令会在全局环境下装置 tsc 命令,装置实现之后,咱们就能够在任何中央执行 tsc 命令了。

编译一个 TypeScript 文件很简略:

tsc hello.ts
# hello.ts => hello.js

咱们约定应用 TypeScript 编写的文件以 .ts 为后缀,用 TypeScript 编写 React 时,以 .tsx 为后缀。

当然,对于刚入门 TypeScript 的小伙伴,也能够不必装置 typescript,而是间接应用线上的 TypeScript Playground 来学习新的语法或新个性。

TypeScript Playground:www.typescriptlang.org/play/

2、编辑器举荐

TypeScript 最大的劣势之一便是加强了编辑器和 IDE 的性能,包含代码补全、接口提醒、跳转到定义、重构等。

支流的编辑器都反对 TypeScript,这里我举荐应用 Visual Studio Code(前端开发神器)。

它是一款开源,跨终端的轻量级编辑器,内置了对 TypeScript 的反对。无须要编译阶段,写完就能看到对不对。

上图写完就报了谬误,因为 number 没有 length 属性。

另外 vscode 自身也是用 TypeScript 编写的。

下载安装:Visual Studio Code – Code Editing. Redefined

 5、第一个案例 Hello TS

咱们从一个简略的例子开始。

将以下代码复制到 hello.ts 中:

function sayHello(person: string) {return 'Hello,' + person;}

let user = 'TS';
console.log(sayHello(user));

而后执行

tsc hello.ts

 这时候会生成一个编译好的文件 hello.js

function sayHello(person) {return 'Hello,' + person;}
var user = 'TS';
console.log(sayHello(user));

在 TypeScript 中,咱们应用 : 指定变量的类型,: 的前后有没有空格都能够。

上述例子中,咱们用 : 指定 person 参数类型为 string。然而编译为 js 之后,并没有什么查看的代码被插入进来。

这是因为 TypeScript 只会在编译时对类型进行动态查看,如果发现有谬误,编译(ts 文件转换成 js 的过程)的时候就会报错。而在运行时,与一般的 JavaScript 文件一样,不会对类型进行查看。

如果咱们须要保障运行时的参数类型,还是得手动对类型进行判断:(这也是最优的写法,动静联合)

function sayHello(person: string) {if (typeof person === 'string') {return 'Hello,' + person;} else {throw new Error('person is not a string');
    }
}

let user = 'TS';
console.log(sayHello(user));

上面尝试把这段代码编译一下:

function sayHello(person: string) {return 'Hello,' + person;}

let user = [0, 1, 2];
console.log(sayHello(user));

编辑器中会提醒谬误,编译的时候也会出错:

然而还是生成了 js 文件:

function sayHello(person) {return 'Hello,' + person;}
var user = [0, 1, 2];
console.log(sayHello(user));

这是因为 TypeScript 编译的时候即便报错了,还是会生成编译后果,咱们依然能够应用这个编译之后的文件。

如果要在报错的时候终止 js 文件的生成,能够在 tsconfig.json 中配置 noEmitOnError 即可。对于 tsconfig.json,请参阅官网手册(中文版)。前面会阐明 tsconfig.json 怎么来的!

6、tsconfig.json 生成和配置解说

1、生成

尽管不生成 tsconfig.json 你也能够 tsc 编译,然而有些人的 vscode 会提醒 ts 文件的变量名和 js 文件的变量名反复谬误!如下图:

[图片上传失败 …(image-c0b8dc-1632538686036)]

上图在 tsc 编译后,就忽然提醒和编译进去的 js 文件变量反复!生成 tsconfig.json 能够解决该问题!

在跟目录下执行以下命令:

tsc --init 

执行后 文件夹下会生成一个 tsconfig.json 文件,最初你神奇的发现,不提醒反复谬误了!(仍有效的须要再重启一下编辑器)

如图已解决。

2、配置解说

2.1 tsconfig.json 的性能

  • 用于标识 TypeScript 我的项目的根门路;
  • 用于配置 TypeScript 编译器;
  • 用于指定编译的文件。

2.2 tsconfig.json 重要字段

  • files – 设置要编译的文件的名称;
  • include – 设置须要进行编译的文件,反对门路模式匹配;
  • exclude – 设置无需进行编译的文件,反对门路模式匹配;
  • compilerOptions – 设置与编译流程相干的选项。

2.3 compilerOptions 选项

compilerOptions 反对很多选项,常见的有 baseUrltarget、`baseUrlmoduleResolution 和 lib` 等。

compilerOptions 每个选项的具体阐明如下:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5" /* target 用于指定编译之后的版本指标: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* 用来指定要应用的模块规范: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "lib": ["es6", "dom"] /* lib 用于指定要蕴含在编译中的库文件 */,
    "allowJs": true,                       /* allowJs 设置的值为 true 或 false,用来指定是否容许编译 js 文件,默认是 false,即不编译 js 文件 */
    "checkJs": true,                       /* checkJs 的值为 true 或 false,用来指定是否检查和报告 js 文件中的谬误,默认是 false */
    "jsx": "preserve",                     /* 指定 jsx 代码用于的开发环境: 'preserve', 'react-native', or 'react'. */
    "declaration": true,                   /* declaration 的值为 true 或 false,用来指定是否在编译的时候生成相应的 ".d.ts" 申明文件。如果设为 true,编译每个 ts 文件之后会生成一个 js 文件和一个申明文件。然而 declaration 和 allowJs 不能同时设为 true */
    "declarationMap": true,                /* 值为 true 或 false,指定是否为申明文件.d.ts 生成 map 文件 */
    "sourceMap": true,                     /* sourceMap 的值为 true 或 false,用来指定编译时是否生成.map 文件 */
    "outFile": "./",                       /* outFile 用于指定将输入文件合并为一个文件,它的值为一个文件路径名。比方设置为 "./dist/main.js",则输入的文件为一个 main.js 文件。然而要留神,只有设置 module 的值为 amd 和 system 模块时才反对这个配置 */
    "outDir": "./",                        /* outDir 用来指定输入文件夹,值为一个文件夹门路字符串,输入的文件都将搁置在这个文件夹 */
    "rootDir": "./",                       /* 用来指定编译文件的根目录,编译器会在根目录查找入口文件,如果编译器发现以 rootDir 的值作为根目录查找入口文件并不会把所有文件加载进去的话会报错,然而不会进行编译 */
    "composite": true,                     /* 是否编译构建援用我的项目  */
    "incremental": true,                   /* 是否启用增量编译 */
    "tsBuildInfoFile": "./",               /* 指定文件用来存储增量编译信息 */
    "removeComments": true,                /* removeComments 的值为 true 或 false,用于指定是否将编译后的文件中的正文删掉,设为 true 的话即删掉正文,默认为 false */
    "noEmit": true,                        /* 不生成编译文件,这个个别比拟少用 */
    "importHelpers": true,                 /* importHelpers 的值为 true 或 false,指定是否引入 tslib 里的辅助工具函数,默认为 false */
    "downlevelIteration": true,            /* 当 target 为 'ES5' or 'ES3' 时,为 'for-of', spread, and destructuring'中的迭代器提供齐全反对 */"isolatedModules": true,               /* isolatedModules 的值为 true 或 false,指定是否将每个文件作为独自的模块,默认为 true,它不能够和 declaration 同时设定 */

    /* Strict Type-Checking Options */
    "strict": true /* strict 的值为 true 或 false,用于指定是否启动所有类型查看,如果设为 true 则会同时开启上面这几个严格类型查看,默认为 false */,
    "noImplicitAny": true,                 /* noImplicitAny 的值为 true 或 false,如果咱们没有为一些值设置明确的类型,编译器会默认认为这个值为 any,如果 noImplicitAny 的值为 true 的话。则没有明确的类型会报错。默认值为 false */
    "strictNullChecks": true,              /* strictNullChecks 为 true 时,null 和 undefined 值不能赋给非这两种类型的值,别的类型也不能赋给他们,除了 any 类型。还有个例外就是 undefined 能够赋值给 void 类型 */
    "strictFunctionTypes": true,           /* strictFunctionTypes 的值为 true 或 false,用于指定是否应用函数参数双向协变查看 */
    "strictBindCallApply": true,           /* 设为 true 后会对 bind、call 和 apply 绑定的办法的参数的检测是严格检测的 */
    "strictPropertyInitialization": true,  /* 设为 true 后会查看类的非 undefined 属性是否曾经在构造函数里初始化,如果要开启这项,须要同时开启 strictNullChecks,默认为 false */
   "noImplicitThis": true,                /* 当 this 表达式的值为 any 类型的时候,生成一个谬误 */
    "alwaysStrict": true,                  /* alwaysStrict 的值为 true 或 false,指定始终以严格模式查看每个模块,并且在编译之后的 js 文件中退出 "use strict" 字符串,用来通知浏览器该 js 为严格模式 */

    /* Additional Checks */
    "noUnusedLocals": true,                /* 用于查看是否有定义了然而没有应用的变量,对于这一点的检测,应用 eslint 能够在你书写代码的时候做提醒,你能够配合应用。它的默认值为 false */
    "noUnusedParameters": true,            /* 用于查看是否有在函数体中没有应用的参数,这个也能够配合 eslint 来做查看,默认为 false */
    "noImplicitReturns": true,             /* 用于查看函数是否有返回值,设为 true 后,如果函数没有返回值则会提醒,默认为 false */
    "noFallthroughCasesInSwitch": true,    /* 用于查看 switch 中是否有 case 没有应用 break 跳出 switch,默认为 false */

    /* Module Resolution Options */
    "moduleResolution": "node",            /* 用于抉择模块解析策略,有 'node' 和 'classic' 两种类型 '*/"baseUrl":"./",                       /* baseUrl 用于设置解析非绝对模块名称的根本目录,绝对模块不会受 baseUrl 的影响 */"paths": {},                           /* 用于设置模块名称到基于 baseUrl 的门路映射 */"rootDirs": [],                        /* rootDirs 能够指定一个门路列表,在构建时编译器会将这个门路列表中的门路的内容都放到一个文件夹中 */"typeRoots": [],                       /* typeRoots 用来指定申明文件或文件夹的门路列表,如果指定了此项,则只有在这里列出的申明文件才会被加载 */"types": [],                           /* types 用来指定须要蕴含的模块,只有在这里列出的模块的申明文件才会被加载进来 */"allowSyntheticDefaultImports": true,  /* 用来指定容许从没有默认导出的模块中默认导入 */"esModuleInterop": true /* 通过为导入内容创立命名空间,实现 CommonJS 和 ES 模块之间的互操作性 */,"preserveSymlinks": true,              /* 不把符号链接解析为其实在门路,具体能够理解下 webpack 和 nodejs 的 symlink 相干常识 */

    /* Source Map Options */
    "sourceRoot": "",                      /* sourceRoot 用于指定调试器应该找到 TypeScript 文件而不是源文件地位,这个值会被写进.map 文件里 */"mapRoot":"",                         /* mapRoot 用于指定调试器找到映射文件而非生成文件的地位,指定 map 文件的根门路,该选项会影响.map 文件中的 sources 属性 */
    "inlineSourceMap": true,               /* 指定是否将 map 文件的内容和 js 文件编译在同一个 js 文件中,如果设为 true,则 map 的内容会以 //# sourceMappingURL= 而后拼接 base64 字符串的模式插入在 js 文件底部 */
    "inlineSources": true,                 /* 用于指定是否进一步将.ts 文件的内容也蕴含到输出文件中 */

    /* Experimental Options */
    "experimentalDecorators": true /* 用于指定是否启用实验性的装璜器个性 */
    "emitDecoratorMetadata": true,         /* 用于指定是否为装璜器提供元数据反对,对于元数据,也是 ES6 的新规范,能够通过 Reflect 提供的静态方法获取元数据,如果须要应用 Reflect 的一些办法,须要引入 ES2015.Reflect 这个库 */
  }
  "files": [], // files 能够配置一个数组列表,外面蕴含指定文件的绝对或绝对路径,编译器在编译的时候只会编译蕴含在 files 中列出的文件,如果不指定,则取决于有没有设置 include 选项,如果没有 include 选项,则默认会编译根目录以及所有子目录中的文件。这里列出的门路必须是指定文件,而不是某个文件夹,而且不能应用 * ? **/ 等通配符
  "include": [],  // include 也能够指定要编译的门路列表,然而和 files 的区别在于,这里的门路能够是文件夹,也能够是文件,能够应用绝对和绝对路径,而且能够应用通配符,比方 "./src" 即示意要编译 src 文件夹下的所有文件以及子文件夹的文件
  "exclude": [],  // exclude 示意要排除的、不编译的文件,它也能够指定一个列表,规定和 include 一样,能够是文件或文件夹,能够是相对路径或绝对路径,能够应用通配符
  "extends": "",   // extends 能够通过指定一个其余的 tsconfig.json 文件门路,来继承这个配置文件里的配置,继承来的文件的配置会笼罩以后文件定义的配置。TS 在 3.2 版本开始,反对继承一个来自 Node.js 包的 tsconfig.json 配置文件"compileOnSave": true,  // compileOnSave 的值是 true 或 false,如果设为 true,在咱们编辑了我的项目中的文件保留的时候,编辑器会依据 tsconfig.json 中的配置从新生成文件,不过这个要编辑器反对"references": [],  // 一个对象数组,指定要援用的我的项目}

[图片上传失败 …(image-d9b146-1632538686036)]

7、小记

  • TypeScript 是增加了类型零碎的 JavaScript,实用于任何规模的我的项目。
  • TypeScript 是一门动态类型、弱类型的语言,最优写法须要动静联合。
  • TypeScript 是 JavaScript 的超集,能够运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
  • TypeScript 能够和 JavaScript 共存,这意味着 JavaScript 我的项目可能渐进式的迁徙到 TypeScript。
  • TypeScript 与规范同步倒退,合乎最新的 ECMAScript 规范(stage 3)。
  • TypeScript 中tsconfig.json 的生成和配置。

二、TypeScript 根底

1、原始数据类型

原始数据类型 · TypeScript

PS:空值中:

与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,能够赋值给 number 类型的变量:(须要 tsconfig.json 中 strict 设置为 false)

2、任意值 

任意值 · TypeScript

3、类型推论

类型推论 · TypeScript

4、联结类型

联结类型 · TypeScript

PS:须要留神的是,一旦定义了可选属性,那么同一个接口内其余所有的属性,都只能是可选属性的子集,否则将会报错!汇合概念:string | number 为例,number 为它的子集,string 也为它的子集。

另一个版本问题:

5、对象的类型—接口 

对象的类型——接口 · TypeScript

6、数组的类型

数组的类型 · TypeScript

PS:纳闷?{
    [index: number]: number;
    length: number;
    callee: Function;
}

中任意类型为 number 为什么 callee 能够是 Function?

7、函数的类型

函数的类型 · TypeScript

8、类型断言

类型断言 · TypeScript

9、申明文件

申明文件 · TypeScript

10、内置对象

内置对象 · TypeScript

三、TypeScript 进阶

进阶 · TypeScript

四、总结

咳,偷个懒,不一一列举。还是那句话,对于 TypeScript 入门教程这本书我是极其举荐的,学 TypeScript 还是得看它。除了以上的内容外,它还囊括了工程化配置阐明。我以我表弟的人格担保,看它必会!冲啊 xdm!

🔥往期文章举荐:

手把手教你在 Vue 中应用 JSX,不怕学不会!【倡议珍藏】_纸飞机博客 -CSDN 博客

2021 年的几次面试让我死磕了 17 道 JS 手写题!_纸飞机博客 -CSDN 博客

大前端根本知识点及面试重灾区学习目录_纸飞机博客 -CSDN 博客

❤️一起谈一谈 js 中的宏工作和微工作!_纸飞机博客 -CSDN 博客

前端高效开发不得不晓得的一些 JavaScript 库!_纸飞机博客 -CSDN 博客

退出移动版