警告
包管理从始至终使用同一个
学习
ES6 入门教程 - ECMAScript 6入门 (ruanyifeng.com)
提示
此文档包含ES6及TS语法
node & npm
安装
Node.js — Download Node.js® (nodejs.org)
验证
node -v
npm -v
配置
首先, 找到node的安装路径
D:\Program Files\nodejs
在此目录下创建两个文件夹
node_global
node_cache
在控制台分别运行以下两条命令(注意改成自己的路径)
# 全局模块插件存放路径 npm config set prefix "D:\Program Files\nodejs\node_global" # 缓存路径 npm config set cache "D:\Program Files\nodejs\node_cache"配置npm安装源
# 淘宝源 npm config set registry "https://registry.npmmirror.com/" # 华为云源 npm config set registry https://mirrors.huaweicloud.com/repository/npm/把
D:\Program Files\nodejs\node_global\node_modules添加到系统环境变变量名: NODE_PATH
变量值: D:\Program Files\nodejs\node_global\node_modules验证
npm config get prefix npm config get cache npm config get registry(可选)
更新到最新版本
npm install npm@latest -g更新到指定版本
npm -g install npm@6.8.0
yarn
安装
npm install -g yarn
验证
yarn config get registry
# 或者
yarn config list
配置
配置镜像源
# 淘宝源 yarn config set registry "https://registry.npmmirror.com/" # 华为云源 yarn config set registry https://mirrors.huaweicloud.com/repository/npm/修改全局安装目录
yarn config set global-folder "D:\Program Files\nodejs\node_global"修改bin目录
# 在全局安装目录下新建./bin目录 yarn config set prefix "D:\Program Files\nodejs\node_global" # 会自动设置成./bin修改缓存目录
yarn config set cache-folder "D:\Program Files\nodejs\node_cache"验证所有配置
yarn config list查看当前yarn的bin的位置
yarn global bin查看当前yarn的全局安装位置
yarn global dir
常用命令
npm init === yarn init
npm install === yarn 或者 yarn install
npm install taco --save === yarn add taco
npm uninstall taco --save === yarn remove taco
npm install taco --save-dev === yarn add taco --dev
npm update --save === yarn upgrade
npm install taco@latest --save === yarn add taco
npm install taco --global === yarn global add taco
npm init --yes/-y === yarn init --yes/-y
npm link === yarn link
npm outdated === yarn outdated
npm publish === yarn publish
npm run === yarn run
npm cache clean === yarn cache clean
npm login === yarn login
npm test === yarn test
初始化项目:
yarn init // 同npm init,执行输入信息后,会生成package.json文件
yarn的配置项:
yarn config list // 显示所有配置项
yarn config get <key> //显示某配置项
yarn config delete <key> //删除某配置项
yarn config set <key> <value> [-g|--global] //设置配置项
安装包:
yarn install //安装package.json里所有包,并将包及它的所有依赖项保存进yarn.lock
yarn install --flat //安装一个包的单一版本
yarn install --force //强制重新下载所有包
yarn install --production //只安装dependencies里的包
yarn install --no-lockfile //不读取或生成yarn.lock
yarn install --pure-lockfile //不生成yarn.lock
添加包(会更新package.json和yarn.lock):
yarn add [package] // 在当前的项目中添加一个依赖包,会自动更新到package.json和yarn.lock文件中
yarn add [package]@[version] // 安装指定版本,这里指的是主要版本,如果需要精确到小版本,使用-E参数
yarn add [package]@[tag] // 安装某个tag(比如beta,next或者latest)
//不指定依赖类型默认安装到dependencies里,你也可以指定依赖类型:
yarn add --dev/-D // 加到 devDependencies
yarn add --peer/-P // 加到 peerDependencies
yarn add --optional/-O // 加到 optionalDependencies
//默认安装包的主要版本里的最新版本,下面两个命令可以指定版本:
yarn add --exact/-E // 安装包的精确版本。例如yarn add foo@1.2.3会接受1.9.1版,但是yarn add foo@1.2.3 --exact只会接受1.2.3版
yarn add --tilde/-T // 安装包的次要版本里的最新版。例如yarn add foo@1.2.3 --tilde会接受1.2.9,但不接受1.3.0
发布包
yarn publish
移除一个包
yarn remove <packageName>:移除一个包,会自动更新package.json和yarn.lock
更新一个依赖
yarn upgrade 用于更新包到基于规范范围的最新版本
运行脚本
yarn run 用来执行在 package.json 中 scripts 属性下定义的脚本
显示某个包的信息
yarn info <packageName> 可以用来查看某个模块的最新版本信息
缓存
yarn cache
yarn cache list # 列出已缓存的每个包 yarn cache dir # 返回 全局缓存位置 yarn cache clean # 清除缓存
pnpm
内容可寻址存储 + 硬链接:
- 全局存储: PNPM 在本地磁盘上维护一个内容可寻址的存储库。所有从网络下载的包都会被整齐地存储在这个全局目录中(通常在
~/.pnpm-store)。 - 硬链接: 当你为项目安装依赖时,PNPM 并不会复制文件,而是在项目的
node_modules目录中创建指向全局存储中文件的硬链接。
安装
npm install -g pnpm
配置
其他参考上方npm,配置方式都一样;
全局存储位置
方法一:使用环境变量
设置
PNPM_STORE_DIR环境变量:# 临时设置(当前终端会话有效) set PNPM_STORE_DIR=E:\new\path\to\pnpm-store # 或者在 Windows 系统中永久设置: # 1. 右键"此电脑" → 属性 → 高级系统设置 # 2. 环境变量 → 新建系统变量 # 3. 变量名: PNPM_STORE_DIR # 4. 变量值: E:\new\path\to\pnpm-store方法二:使用 pnpm config 命令
# 设置全局 store 路径 pnpm config set store-dir E:\new\path\to\pnpm-store # 或者针对当前项目设置 pnpm config set store-dir E:\new\path\to\pnpm-store --location project方法三:在 .npmrc 文件中配置
在项目根目录的.npmrc文件或用户目录的.npmrc文件中添加:store-dir=E:\new\path\to\pnpm-store
提示
迁移现有store
停止所有正在运行的 Node.js 应用
复制现有 store 到新位置
设置新的 store 路径(使用上述方法之一)
删除旧的 store 目录(可选)
验证配置是否生效
# 检查当前 store 配置 pnpm config get store-dir # 或者查看所有配置 pnpm config list
常用命令
# 安装生产依赖(dependencies)
pnpm add <package-name>
# 安装开发依赖(devDependencies)
pnpm add -D <package-name>
# 全局安装
pnpm add -g <package-name>
# 移除开发依赖
pnpm remove <package-name>
## 如果包同时存在于多个依赖类型中
## 明确指定从开发依赖中移除
pnpm remove -D <package-name>
## 从生产依赖中移除
pnpm remove -S <package-name>
# 更改依赖类型
## 从开发依赖改为生产依赖
pnpm remove -D <package-name>
pnpm add -S <package-name>
## 从生产依赖改为开发依赖
pnpm remove -S <package-name>
pnpm add -D <package-name>
pnpm install
# 根据 package.json 中的版本范围更新包
pnpm update`
# 忽略版本范围,直接更新到最新版本
pnpm update --latest
# 更新指定包
pnpm update <package>
# 在工作区中递归更新所有包
pnpm update -r
nvm🥳
链接:https://github.com/coreybutler/nvm-windows/releases
提示
原理:nvm创建一个固定名称的文件夹软链接动态指向某个版本的node文件夹。
将压缩包解压到应用安装位置,命名
nvm管理员运行
install.bat或者install.cmd- 这一步应该会提示输入
nvm解压之后的路径,例如D:\Program Files\nvm
- 这一步应该会提示输入
之后会在上方文件夹内创建一个
settings.txt,修改其中的配置root:nvm安装路径(就算路径内包含空格也不需要使用""包裹,下同。)path:nodejs的软链接路径(不需要手动创建这个文件夹,在使用nvm use之后会自动创建)
新增环境变量
NVM_HOME: 对应上方root路径NVM_SYMLINK: 对应上方pathPath里加入:%NVM_HOME%,%NVM_SYMLINK%
# 华为云源 npm config set registry https://mirrors.huaweicloud.com/repository/npm/
使用
验证安装
nvm version查看可安装的 Node.js 版本
nvm list available安装指定版本
nvm install 18.18.0使用已安装的版本
nvm use 18.18.0查看已安装的版本
nvm list
库
| 库名 | 安装 | 启动命令 | 备注 |
|---|---|---|---|
| json-server | npm install -g json-server | json-server data.json # 配置参数(实时监测 + 自定义端口) json-server data.json --watch --port 3000 | 1. GitHub - typicode/json-server 2. json-server 详解(cnblogs.com) |
| mockjs | npm install -g mockjs | D:\WorkSpace\TypeScript\mock | 1. 修改server.js 2. node server.js 3. 保存为json |
| http-server | npm install -g http-server | http-server -p 8899 -o | 可以将当前文件夹当成服务器根目录,可以访问静态html页面、播放本地视频 |
| express | npm install express | node xxx.js | 直接运行js文件(将用js来处理http请求) |
| sort-package-json | npm install -g sort-package-json | sort-package-json package.json | 排序当前项目中的package.json可以省略参数,直接执行: sort-package-json |
http-server
-p 端口号 (默认 8080) -a IP 地址 (默认 0.0.0.0) -d 显示目录列表 (默认 'True') -i 显示 autoIndex (默认 'True') -e or --ext 如果没有提供默认的文件扩展名(默认 'html') -s or --silent 禁止日志信息输出 --cors 启用 CORS via the Access-Control-Allow-Origin header -o 在开始服务后打开浏览器 -c 为 cache-control max-age header 设置Cache time(秒) , e.g. -c10 for 10 seconds (defaults to '3600'). 禁用 caching, 则使用 -c-1. -U 或 --utc 使用UTC time 格式化log消息 -P or --proxy Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com -S or --ssl 启用 https -C or --cert ssl cert 文件路径 (default: cert.pem) -K or --key Path to ssl key file (default: key.pem). -r or --robots Provide a /robots.txt (whose content defaults to 'User-agent: *\nDisallow: /')express(模仿下载clash的配置文件,真是的使用了gzip压缩了文件,示例代码里注释掉了,因为浏览器会自动解压,用其他应用来请求则需要自己处理解压数据)
const express = require('express'); const fs = require('fs'); const path = require('path'); // const zlib = require('zlib'); const app = express(); const port = 2333; // 文件路径 const filePath = path.join(__dirname, 'iggfeed.yaml'); // 确保文件a.yaml在同一目录下 // http://192.168.3.33:2333/file/clash-config app.get('/file/clash-config', (req, res) => { // 检查文件是否存在 fs.stat(filePath, (err, stats) => { if (err) { return res.status(404).send('File not found'); } // 设置响应头 res.setHeader('Content-Disposition', 'attachment; filename=iggfeed.yaml'); // 要求客户端使用gzip解压缩响应 // res.setHeader('Content-Encoding', 'gzip'); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Length', stats.size); // 创建文件读取流 const readStream = fs.createReadStream(filePath); // 创建gzip压缩流 // const gzipStream = zlib.createGzip(); // 管道传输:读取文件 -> 压缩 -> 返回响应 // readStream.pipe(gzipStream).pipe(res); readStream.pipe(res); }); }); // 启动服务器 app.listen(port, () => { console.log(`Success: File server is running at http://192.168.3.33:${port}/file/clash-config`); });
版本符号释义
文档
箭头函数
限制返回值类型
在 TypeScript 中,箭头函数的参数冒号右边的类型表示对该参数的类型进行限制。这是 TypeScript 中的函数参数类型注解,用于指定参数的预期类型。
例如,在以下箭头函数中:
codeconst exampleFunction = (x: number, y: string): void => {
// 函数体
};
这里的 (x: number, y: string) 表示函数 exampleFunction 接受两个参数,其中 x 应该是一个 number 类型,y 应该是一个 string 类型。冒号右边的 void 则表示该函数没有返回值。
对于箭头函数的返回值类型,可以通过冒号及其右边的类型注解来指定。例如:
codeconst exampleFunction = (): number => {
return 42;
};
这里的 (): number 表示该箭头函数不接受任何参数,而返回值应该是一个 number 类型。箭头函数的返回值类型注解是可选的,TypeScript 通常可以通过类型推断来自动推断函数的返回值类型。
在 Vue 3 中,使用 data 函数的方式和 Vue 2 中有一些不同。Vue 3 引入了 Composition API,其中的 setup 函数取代了 Vue 2 中的 data 函数。在 setup 函数中,你可以使用 ref、reactive 等 Composition API 提供的函数来创建响应式数据。
以下是一个简单的对比:
——————————
JS -> TS
从 JavaScript 转到 TypeScript 时,有一些语法和概念上的变化需要注意。以下是一些关键点:
1. 类型注解
TypeScript 的核心特性之一是类型注解,它允许你明确地指定变量、函数参数和返回值的类型。
let name: string = "John";
let age: number = 25;
let isStudent: boolean = true;
function greet(name: string): string {
return `Hello, ${name}`;
}
2. 接口(Interfaces)
接口用于定义对象的结构。它们可以确保对象符合特定的形状。
interface Person {
name: string;
age: number;
}
let john: Person = {
name: "John",
age: 25
};
3. 类型别名(Type Aliases)
类型别名可以用于给复杂的类型起一个简单的名字。
type Point = {
x: number;
y: number;
};
let point: Point = {
x: 10,
y: 20
};
4. 枚举(Enums)
枚举用于定义一组命名的常量。
enum Direction {
Up,
Down,
Left,
Right
}
let dir: Direction = Direction.Up;
// 在上述实例中,UP为number类型数据:0,以此类推,也可以直接给值,例如
enum Direction {
Up = 'UP',
Down = 'DOWN',
// ... 其他属性
}
// 这样 Direction.Up 就相当于输入了字符串'UP'
5. 元组(Tuples)
元组允许你定义一个已知元素数量和类型的数组。
let tuple: [string, number];
tuple = ["hello", 10];
可选元素
type OptionalTuple = [string, number?];
const a: OptionalTuple = ["Hello"]; // 合法(第二个元素可选)
const b: OptionalTuple = ["Hello", 42]; // 合法
剩余元素:类似数组展开语法,允许可变长度:
type StringNumberBooleans = [string, ...number[], boolean];
const c: StringNumberBooleans = ["TS", 1, 2, 3, true]; // 合法
解构赋值与函数返回值
元组常用于解构或返回多个值:
// 函数返回元组
function getUser(): [string, number] {
return ["Bob", 25];
}
// 解构赋值
const [name, age] = getUser();
console.log(name); // "Bob"
console.log(age); // 25
只读元组
通过 readonly 或 as const 创建不可变元组:
const readOnlyTuple: readonly [string, number] = ["Alice", 30];
readOnlyTuple = "Bob"; // 报错:只读属性不可修改
// 使用 as const 断言
const tupleConst = ["Alice", 30] as const; // 类型为 readonly ["Alice", 30]
6. 类型推断
TypeScript 能够自动推断变量的类型,因此在某些情况下,你不需要显式地声明类型。
let message = "Hello, world!"; // TypeScript 自动推断 message 的类型为 string
7. 联合类型(Union Types)
联合类型允许一个变量可以是多种类型之一。
let id: number | string;
id = 101;
id = "202";
8. 类型守卫(Type Guards)
类型守卫用于在代码中进行类型检查。
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
举个例子
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined// 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log// 'function'
从上面例子,前6个都是基础数据类型。虽然 typeof null 为 object ,但这只是 JavaScript 存在的一个悠久 Bug,不代表 null 就是引用数据类型,并且 null 本身也不是对象
所以, null 在 typeof 之后返回的是有问题的结果,不能作为判断 null 的方法。如果你需要在 if 语句中判断是否为 null,直接通过 ===null 来判断就好
同时,可以发现引用类型数据,用 typeof 来判断的话,除了 function 会被识别出来之外,其余的都输出 object。
判断一个变量是否存在,可以使用 typeof :(不能使用 if(a) , 若 a 未声明,则报错)
if(typeof a != 'undefined'){
//变量存在
}
和 instanceOf 区别
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
使用如下:
object 为实例对象,constructor 为构造函数
构造函数通过 new 可以实例对象,instanceof 能判断这个对象是否是之前那个构造函数生成的对象
// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false
关于instanceof的实现原理,可以参考下面:
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if(typeof left !== 'object' || left === null) return false;
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto === null) return false;
if(proto === right.prototype) return true;//找到相同原型对象,返回true
proto = Object.getPrototypeof(proto);
}
}
也就是顺着原型链去找,直到找到相同的原型对象,返回true,否则为false
总结:
typeof与instanceof都是判断数据类型的方法,区别如下:
typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值instanceof可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型- 而
typeof也存在弊端,它虽然可以判断基础数据类型(null除外),但是引用数据类型中,除了function类型以外,其他的也无法判断
可以看到,上述两种方法都有弊端,并不能满足所有场景的需求
如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串
如下
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
了解了toString的基本用法,下面就实现一个全局通用的数据类型判断方法
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
使用如下
getType([]) // "Array" typeof []是object,因此toString返回
getType('123') // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined) // "undefined" typeof 直接返回
getType() // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g) //"RegExp" toString返回
9. 类(Classes)
TypeScript 扩展了 JavaScript 的类,使其更强大和类型安全。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance} meters.`);
}
}
let dog = new Animal("Dog");
dog.move(10);
10. 模块(Modules)
TypeScript 使用 ES6 的模块系统,可以使用 import 和 export 关键字。
每个模块可以有一个默认导出(default export)和多个命名导出(named exports)。
静态导入
// math.ts
export function add(x: number, y: number): number {
return x + y;
}
// 默认导出
export default sub(x: number, y: number): number {
return x - y;
}
// main.ts
// 命名导入(命名导出在导入时必须使用导出时的名字,或者使用as关键字来重命名)
import { add } from './math';
// 默认导入(默认导出在导入时可以使用任何名字)
import sub from './math';
// 可以同时使用两种导入模式,中间使用逗号连接
import sub, { add } from './math';
console.log(add(5, 3));
console.log(sub(5, 3));
注意点
exports字段的:- 如果
package.json中没有定义exports属性,则Node.js会默认加载main属性指定的文件。 - 如果
package.json中定义了exports,以精确控制模块的对外暴露路径,则所有未在exports中声明的子路径将无法访问(即使文件物理存在)。 - 这是 Node.js 的强制规则,用于防止意外访问内部文件。
场景 无 exports字段有 exports字段入口文件优先级 main字段生效exports覆盖main子路径访问 允许直接访问任意子路径文件 仅允许访问 exports声明的路径模块封装性 低(易导致依赖内部实现) 高(强制接口隔离) 兼容性 兼容旧版本 Node.js 和打包工具 需较新 Node.js(≥12.0.0)支持 - 如果
类型声明文件(.d.ts):
- 对于 TypeScript 项目,包需要提供类型声明(如
index.d.ts),否则即使代码存在,类型检查也会失败。
- 对于 TypeScript 项目,包需要提供类型声明(如
模块打包工具的影响:
- Webpack/Rollup 等工具可能默认启用
exports字段的严格解析,导致未声明的子路径导入失败。
- Webpack/Rollup 等工具可能默认启用
| 场景 | 能否访问其他文件导出? | 条件 |
|---|---|---|
| 直接导入包名 | 仅主入口导出的内容 | 主入口需显式导出 |
| 通过子路径导入 | ✅ 可以 | 包未限制 exports 或子路径在 exports 中声明 |
包使用严格 exports | 仅允许声明的路径 | 未声明的子路径即使文件存在也会报错 |
| 主入口聚合所有导出 | ✅ 可以 | 主入口文件通过 export * 统一导出 |
动态导入
import() 导入规则和和静态导入的导入规则一致,其他区别在于:
运行时解析与条件加载
动态导入是运行时执行的,因此路径可以是动态生成的(如基于用户输入或环境变量):const moduleName = someCondition ? 'utils' : 'other'; import(`package-name/$${moduleName}`).then(...);- 优势:支持按需加载和代码分割,减少初始加载体积。
- 限制:若动态路径未在
exports中声明,即使文件存在也会报错。
错误处理机制
静态导入在编译阶段会检查路径有效性,而动态导入的错误在运行时通过 Promise 捕获:// 静态导入:编译时报错 import { missing } from 'package-name'; // 直接报错 // 动态导入:运行时捕获 import('package-name/missing') .then(...) .catch(err => console.error('加载失败', err));打包优化差异
- 静态导入:打包工具(如 Webpack、Rollup)可静态分析依赖关系,实现 Tree Shaking(删除未使用代码)。
- 动态导入:由于路径可能动态生成,打包工具难以提前分析,可能导致未使用的代码仍被包含在产物中。
11. 类型断言(Type Assertions)
类型断言用于告诉编译器变量的具体类型。在这两种语法中,编译器将 someValue 视为 string 类型,因此可以访问 string 类型的方法和属性,例如 length。
// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
类型断言的应用场景
从
any类型中恢复类型信息,如上。操作 DOM 元素时,类型断言可以告诉编译器元素的具体类型,从而访问特定类型的方法和属性。
let inputElement = document.getElementById("myInput") as HTMLInputElement; inputElement.value = "Hello, World!";在处理联合类型时,类型断言可以帮助编译器确定变量的具体类型。
function getLength(value: string | number): number { if ((value as string).length !== undefined) { return (value as string).length; } else { return value.toString().length; } }注意事项
- 类型断言不会改变运行时的类型检查,它只是影响编译时的类型检查。
- 在使用类型断言时需要谨慎,确保你对类型的判断是正确的,否则可能会导致运行时错误。
TypeScript 在其基础上支持并扩展了很多 ES6(ES2015) 的特性。以下是一些在 TypeScript 中常用的 ES6 语法,以及它们在 TypeScript 中的用法和示例:
——————————
TS & ES6
1. let 和 const
let 和 const 用于声明变量,区别在于 let 声明的变量可以重新赋值,而 const 声明的变量不能重新赋值。
let mutableVariable = "I can change";
const immutableVariable = "I cannot change";
mutableVariable = "New value"; // 正常
// immutableVariable = "New value"; // 错误,const 变量不能重新赋值
2. 箭头函数
箭头函数提供了一种更简洁的函数语法,并且不会绑定自己的 this。
const add = (x: number, y: number): number => x + y;
console.log(add(5, 3)); // 输出: 8
3. 模板字符串
模板字符串使用反引号(`)包围,可以包含嵌入变量和表达式的模板。
let name = "John";
let message = `Hello, ${name}!`;
console.log(message); // 输出: Hello, John!
4. 解构赋值
解构赋值允许从数组或对象中提取值,并赋值给变量。
// 数组解构
let [first, second] = [1, 2, 3];
console.log(first, second); // 输出: 1 2
// 对象解构
let person = { name: "Jane", age: 25 };
let { name, age } = person;
console.log(name, age); // 输出: Jane 25
5. 默认参数
函数参数可以有默认值,当调用函数时未提供对应参数时使用默认值。
function greet(name: string = "Guest"): string {
return `Hello, ${name}!`;
}
console.log(greet()); // 输出: Hello, Guest!
console.log(greet("John")); // 输出: Hello, John!
6. 展开操作符(Spread Operator)
展开操作符用于展开数组或对象。
// 数组展开
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5];
console.log(arr2); // 输出: [1, 2, 3, 4, 5]
// 对象展开
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出: { a: 1, b: 2, c: 3 }
7. 类(Classes)
ES6 类提供了一种更简单、更清晰的方式来创建对象和处理继承。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance} meters.`);
}
}
let dog = new Animal("Dog");
dog.move(10); // 输出: Dog moved 10 meters.
8. 模块(Modules)
ES6 模块系统允许你通过 import 和 export 关键字导入和导出代码。
// math.ts
export function add(x: number, y: number): number {
return x + y;
}
// main.ts
import { add } from './math';
console.log(add(5, 3)); // 输出: 8
9. Promise
Promise 提供了一种处理异步操作的方式。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 一些业务代码
// ...
if (一些业务逻辑) {
resolve("Success!");
} else {
// 手动包装异常,因为直接抛出业务自定义异常throw new XxxException()是同步异常,并不会被Promise调用链上的`catch`捕获到,需要包装成Promise异常,这里调用reject方法
reject(new Error(""))
}
} catch (err) {
console.error("Error in custom promise:", err);
reject(new Error(""))
// return Promise.reject(err); // 把错误传递给 catch
}
}, 1000);
});
promise
.then(result => {
console.log(result); // 一秒后输出: Success!
})
.catch(error => {
console.log("ERROR: ", error);
});
调用链
调用链有三种需求:
- 期中有异常,统一处理异常并且中断调用链
- 期中有异常,分别处理每个
then中的异常,并且中断调用链(和1的区别就是分别处理异常) - 期中有异常,分别处理每个
then中的异常,但是不中断调用链
异常时机:
- 同步异常:代码中运行时错误,或手动直接抛出的
throw new Error(),不会被链式的catch捕获,会直接在控制台抛出这个异常。如果是在普通方法中发生了异常,那么这个方法会直接停止执行,这个方法中调用的未完成的Promise调用链也会停止执行。 - 异步异常:被包装成
Promise.reject(new XxxException("xxx"))的错误,或是自定义Promise对象调用了``reject方法,这种异常才可以被调用链中的catch`捕获到。
示例代码:
定义调用链
function method1(): Promise<number> {
return new Promise<number>((resolve, reject) => {
setTimeout(() => {
console.log("Method 1 executed");
// 返回数字 10
resolve(10);
}, 200);
});
}
function method2(value: number): Promise<string> {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
console.log(`Method 2 executed with value: ${value}`);
// 返回字符串
resolve(`Result from method 2: ${value * 2}`);
}, 200);
});
}
function method3(value: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
setTimeout(() => {
try {
/**
* 模拟程序可能会发生异常
* (这里没有使用直接throw错误,因为直接throw错误是同步错误,不会被调用链中的catch捕获到,会直接终止程序的运行并抛出该错误)
* 如果需要让Promise调用链调用到就要使用`try-catch`包裹该代码,并在`catch`中调用`reject()`方法
*/
Math.random() > 0.5 ? JSON.parse(`abcdefg`) : JSON.parse(`{"name": "enlin"}`);
// throw new Error("Error occurred in method 3");
console.log(`Method 3 executed with value: ${value}`);
// 返回布尔值,字符串长度大于 10 时返回 true
resolve(value.length > 10);
} catch (err) {
reject(err);
}
}, 200);
});
}
function method4(value: boolean): Promise<void> {
return new Promise<void>((resolve, reject) => {
setTimeout(() => {
console.log(`Method 4 executed with value: ${value}`);
// 不返回值
resolve();
}, 200);
});
}
function method5(): Promise<string> {
return new Promise<string>((resolve, reject) => {
setTimeout(() => {
console.log("Method 5 executed");
resolve("Final result from method 5");
}, 200);
});
}
- :期中有异常,统一处理异常,并且中断调用链
method1() // 隐式 return,因为method2方法本身就返回了一个Promise对象 .then((result1) => method2(result1)) .then((result2) => method3(result2)) .then((result3) => method4(result3)) .then(() => method5()) .then((finalResult) => { // 输出最终结果 console.log(finalResult); }) .catch((err) => { // 任何步骤出现错误,都会跳到这里,后续的步骤都不会继续执行 console.error("#Error: occurred:", err); });运行结果
// 无异常 Method 1 executed Method 2 executed with value: 10 Method 3 executed with value: Result from method 2: 20 Method 4 executed with value: true Method 5 executed Final result from method 5 // 有异常 Method 1 executed Method 2 executed with value: 10 Method 3 executed with value: Result from method 2: 20 #Error in method2 or method3: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7)
- :期中有异常,处理每个`then`中的异常,并且调用链:在每一个
catch中重新抛出该异常,变相避免了调用链的执行method1() .then((result1) => method2(result1)) .catch((err) => { console.error("#Error in method1 or method2:", err); // 重新抛出错误,将直接传递给下一个链条中的catch(不执行下一个then) throw err; // ArkTS 中不支持直接throw err,可以return一个Promise包装对象,状态为rejected // return Promise.reject(err) }) .then((result2) => method3(result2)) .catch((err) => { console.error("#Error in method2 or method3:", err); throw err; }) .then((result3) => method4(result3)) .catch((err) => { console.error("#Error in method3 or method4:", err); throw err; }) .then(() => method5()) .catch((err) => { console.error("#Error in method4 or method5:", err); throw err; }) .then((finalResult) => { console.log(finalResult); // 输出最终结果 }) .catch((err) => { // 任何步骤出现错误,都会跳到这里,后续的步骤都不会继续执行 console.error("#Error: occurred:", err); });运行结果
Method 1 executed Method 2 executed with value: 10 Method 3 executed with value: Result from method 2: 20 #Error in method2 or method3: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7) #Error in method3 or method4: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7) #Error in method4 or method5: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7) #Error: occurred: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7)
- :期中有异常,处理每个`then`中的异常,但是调用链
method1() .then((result1) => method2(result1)) .catch((err) => { console.error("#Error in method1 or method2:", err); // 隐式返回一个Promise.resolve(undefined),后一个then方法会接收到这个Promise对象 }) .then((result2) => method3(result2)) .catch((err) => { console.error("#Error in method2 or method3:", err); }) .then((result3) => method4(result3)) .catch((err) => { console.error("#Error in method3 or method4:", err); }) .then(() => method5()) .catch((err) => { console.error("#Error in method4 or method5:", err); }) .then((finalResult) => { // 输出最终结果 console.log(finalResult); }) .catch((err) => { // 任何步骤出现错误,都会跳到这里,后续的步骤都不会继续执行 console.error("#Error: occurred:", err); });运行结果
Method 1 executed Method 2 executed with value: 10 Method 3 executed with value: Result from method 2: 20 #Error in method2 or method3: SyntaxError: Unexpected token a in JSON at position 0 at JSON.parse (<anonymous>) at Timeout._onTimeout (D:\WorkSpace\TypeScript\study\promise-study.js:39:44) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7) Method 4 executed with value: undefined Method 5 executed
总结
- 同步异常不会被调用链中的
catch捕获到进而导致这个应用程序崩溃。需要手动使用try-catch处理成Promise异常
- 同步异常不会被调用链中的
全部代码,可以用
tsc编译(编译报错不影响生成js以及js的运行),node运行测试:/** * Description: Promise 链式调用示例 * * Promise 是一种异步编程的解决方案,它是一个对象,可以获取异步操作的消息。 * Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。 * Promise 对象的状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。 * Promise 对象的状态一旦改变,就永久保持该状态,不会再变。 */ // 定义几个异步方法 function method1(): Promise<number> { return new Promise<number>((resolve, reject) => { setTimeout(() => { console.log("Method 1 executed"); // 返回数字 10 resolve(10); }, 200); }); } function method2(value: number): Promise<string> { return new Promise<string>((resolve, reject) => { setTimeout(() => { console.log(`Method 2 executed with value: ${value}`); // 返回字符串 resolve(`Result from method 2: ${value * 2}`); }, 200); }); } function method3(value: string): Promise<boolean> { return new Promise<boolean>((resolve, reject) => { setTimeout(() => { try { console.log(`Method 3 executed with value: ${value}`); /** * 模拟程序可能会发生异常 * (这里没有使用直接throw错误,因为直接throw错误是同步错误,不会被调用链中的catch捕获到,会直接终止程序的运行并抛出该错误) * 如果需要让Promise调用链调用到就要使用`try-catch`包裹该代码,并在`catch`中调用`reject()`方法 */ Math.random() > 0.5 ? JSON.parse(`abcdefg`) : JSON.parse(`{"name": "enlin"}`); // throw new Error("Error occurred in method 3"); // 返回布尔值,字符串长度大于 10 时返回 true resolve(value.length > 10); } catch (err) { reject(err); } }, 200); }); } function method4(value: boolean): Promise<void> { return new Promise<void>((resolve, reject) => { setTimeout(() => { console.log(`Method 4 executed with value: ${value}`); // 不返回值 resolve(); }, 200); }); } function method5(): Promise<string> { return new Promise<string>((resolve, reject) => { setTimeout(() => { console.log("Method 5 executed"); resolve("Final result from method 5"); }, 200); }); } // 定义链式调用 function executedMethod() { console.log("#Start executing methods"); console.log("#Before promise definition: something...\n") method1() // 隐式 return,因为method2方法本身就返回了一个Promise对象 .then((result1) => method2(result1)) .then((result2) => method3(result2)) .then((result3) => method4(result3)) .then(() => method5()) .then((finalResult) => { // 输出最终结果 console.log(finalResult); }) .catch((err) => { // 任何步骤出现错误,都会跳到这里,后续的步骤都不会继续执行 console.error("#Error: occurred:", err); }); console.log("\n#After promise definition: something...") console.log("#End executing methods\n"); setTimeout(() => { console.log("#After 2 seconds, the final result will be printed.") }, 2000) // 发生同步异常之后,代码会直接停止执行,未执行完的Promise链式调用也不会继续执行 // JSON.parse(`enlin`) } // 执行方法 executedMethod();
异常传播机制
Promise.resolve()
.then(() => {
console.log('then1');
return 'value1';
})
.then(() => {
console.log('then2');
throw new Error('error in then2'); // 假设代码运行时发生异常
})
.then(() => {
console.log('then3'); // ❌ 被跳过
})
.then(() => {
console.log('then4'); // ❌ 被跳过
})
.catch((err) => {
console.log('catch1:', err.message); // ✅ 执行,接收到error in then2
})
.then(() => {
console.log('then5'); // ✅ 执行,因为错误已被catch处理
})
.catch((err) => {
console.log('catch2:', err); // ❌ 不执行,因为前面没有错误
});
- 一旦发生异常(
throw、Promise.reject等),就会跳过后续所有的then; - 直到遇到第一个
catch来处理错误; catch处理完后,后续的链式调用恢复正常(除非catch本身也出错);- 如果没有
catch,异常会一直传播到最末端;
与同步代码执行优先级
- 主线程同步代码:立即执行(包括Promise构造函数)
- 微任务队列:Promise的then/catch/finally回调
- 事件循环机制:
- 执行所有同步代码
- 检查微任务队列并执行所有微任务
- 执行渲染(浏览器)
- 执行宏任务(setTimeout等)
console.log('同步代码 1');
setTimeout(() => {
console.log('宏任务 - setTimeout');
}, 0);
let promise = new Promise((resolve) => {
console.log('同步代码 2 - Promise构造器');
resolve('成功');
});
promise.then(() => {
console.log('微任务 1 - then回调');
}).then(() => {
console.log('微任务 2 - 链式then');
});
console.log('同步代码 3');
/* 输出顺序*/
// 同步代码 1
// 同步代码 2 - Promise构造器
// 同步代码 3
// 微任务 1 - then回调
// 微任务 2 - 链式then
// 宏任务 - setTimeout
执行优先级:
同步代码 > 微任务(Promise) > 宏任务(setTimeout)
注意
特别注意:使用 new Promise() 语句的时候,构造器中的方法会立即执行,但它对应的then 回调依然会等待主线程同步代码执行完毕之后。 而且 Promise.then 的回调不是简单排队,而是有优先级的微任务。
10. 迭代器(Iterators) 和 生成器(Generators)
迭代器和生成器提供了遍历集合的功能。
// 生成器函数
function* generator() {
yield 1;
yield 2;
yield 3;
}
let iter = generator();
console.log(iter.next().value); // 输出: 1
console.log(iter.next().value); // 输出: 2
console.log(iter.next().value); // 输出: 3
11. Symbol
Symbol 是一种基本数据类型,表示唯一的标识符。
let sym1 = Symbol();
let sym2 = Symbol("description");
console.log(sym1 === sym2); // 输出: false
总结
TypeScript 扩展和增强了 ES6 的语法,并引入了类型系统,使得代码更健壮和可维护。熟悉这些 ES6 特性,并了解它们在 TypeScript 中的用法,可以帮助你更好地利用 TypeScript 的强大功能。
语法
索引签名
假如有一个函数需要的参数类型为:
export type MultipartFormFields = {
[k: string]: MultipartFormFieldValue | MultipartFormFieldValue[];
};
- 键(key): 索引签名,表示对象可以有任意数量的字符串类型的键
- 值(value):
MultipartFormFieldValue类型或MultipartFormFieldValue[]数组。
使用方式:
functionName({
// 这个类型可以有任意数量的字符串键
'param1': { ... }, // MultipartFormFieldValue 类型
'param2': [ {...},{...},... ], // MultipartFormFieldValue 数组类型
})
- 传入任意数量的参数(1个、2个、3个...甚至0个)
这种设计很常见于需要处理动态字段的场景,比如表单数据、配置对象等。
——————————
import & export
在JavaScript和TypeScript中,import语句确实可以使用别名,但它的使用方式取决于导出方式。以下是导入时使用别名的几种常见情况:
1. 默认导出(Default Export)
当一个模块默认导出一个值或函数时,导入时可以使用任何名称:
// module.ts
export default function() {
console.log("Hello from default export!");
}
// 使用默认导出的函数
import myFunction from './module';
myFunction(); // 输出:Hello from default export!
在这种情况下,myFunction 是一个你自己选择的名称,可以是任何名称。
2. 命名导出(Named Export)
当一个模块导出多个命名导出时,你可以选择导入哪些具体的导出,并且可以使用别名:
// module.ts
export const foo = 42;
export const bar = () => console.log("Hello from bar");
// 导入命名导出的值和函数
import { foo, bar } from './module';
console.log(foo); // 输出:42
bar(); // 输出:Hello from bar
// 使用别名导入
import { foo as myFoo, bar as myBar } from './module';
console.log(myFoo); // 输出:42
myBar(); // 输出:Hello from bar
在这种情况下,foo 和 bar 可以使用别名 myFoo 和 myBar 来导入。
3. 导入整个模块作为一个对象
你还可以导入整个模块作为一个对象,并使用这个对象来访问导出的成员:
// module.ts
export const foo = 42;
export const bar = () => console.log("Hello from bar");
// 导入整个模块作为一个对象
import * as myModule from './module';
console.log(myModule.foo); // 输出:42
myModule.bar(); // 输出:Hello from bar
在这种情况下,myModule 是整个模块的别名,可以用来访问模块中的所有导出成员。
扩展
当你使用 export default 导出一个没有命名的函数时,在使用 import * as myModule from './module'; 这种方式导入时,可以通过 myModule.default 来访问和调用这个函数。
举例来说,假设你有一个模块 module.ts,里面导出了一个没有命名的默认函数:
// module.ts
export default function() {
console.log("Hello from default export!");
}
然后在另一个文件中,比如 main.ts 中,你可以这样导入和调用该函数:
// main.ts
import * as myModule from './module';
// 调用默认导出的函数
myModule.default(); // 输出:Hello from default export!
在这个例子中,myModule.default 就是对默认导出的函数的引用,通过它可以调用导出的函数。
这种方式是因为 import * as myModule from './module'; 将整个模块作为一个对象导入,而默认导出的内容会被放在 default 属性中。
4. 混合使用默认导出和命名导出
你可以同时导入默认导出和命名导出:
// module.ts
export default function() {
console.log("Hello from default export!");
}
export const foo = 42;
// 同时导入默认导出和命名导出
import myFunction, { foo } from './module';
myFunction(); // 输出:Hello from default export!
console.log(foo); // 输出:42
总结:
- 默认导出:可以使用任何名称来导入。
- 命名导出:可以使用别名来导入。
- 整个模块:可以使用别名来导入整个模块作为一个对象。
扩展
import * from ./xxx.js为静态加载,无法在运行时动态加载;
ES6(2020)时提出了import():
import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载(会缓存已经require过的模块)。
import()
按需加载
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) });条件加载
if (condition) { import('moduleA').then(...); } else { // 允许方法返回动态路径 import(f()).then(...); }
import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
import('./myModule.js')
.then(({export1, export2}) => {
// ...·
});
上面代码中,export1和export2都是myModule.js的输出接口,可以解构获得。
如果模块有default输出接口,可以用参数直接获得。
import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});
上面的代码也可以使用具名[1]输入的形式。
import('./myModule.js')
.then(({default: theDefault}) => {
console.log(theDefault);
});
如果想同时加载多个模块,可以采用下面的写法。
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
import()也可以用在 async 函数之中。
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
注意
在Vue3项目main.js中这两种导入方式的差异
import aaa from '@/Aaa.js' 和 import './bbb.js'
| 特性 | import aaa from '@/Aaa' | import './bbb' |
|---|---|---|
| 目的 | 导入模块/组件 | 导入文件(通常用于副作用) |
| 用法 | 获取模块的默认 | ,不获取导出 |
| 常见用途 | 导入Vue组件、工具函数等 | 导入样式文件、初始化脚本 |
| 示例 | import App from './App.vue' | import './style.css' |
require()
// 导入内置模块
const fs = require('fs');
// 导入自定义模块
const myModule = require('./myModule');
// 导入JSON文件
const data = require('./data.json');
re-erxport
在 ES6 和 TypeScript 中,re-export 是指从一个模块中导入某些东西,并将它们再次导出。这种方式可以用来组织和组合模块,使得代码结构更加清晰和模块化。Re-export 主要有两种形式:导入并导出整个模块或特定的导出。
1. 导入并导出整个模块
这种方式将整个模块导入并再次导出。它可以用于创建一个“聚合模块”,将多个模块的内容组合在一起,从而简化导入路径。
// module1.ts
export const a = 1;
export const b = 2;
// module2.ts
export const c = 3;
export const d = 4;
// index.ts (聚合模块)
export * from './module1';
export * from './module2';
在这种情况下,index.ts 文件将 module1 和 module2 中的所有导出重新导出。这意味着你可以从 index.ts 文件中导入所有这些内容:
// main.ts
import { a, b, c, d } from './index';
console.log(a, b, c, d); // 输出: 1 2 3 4
2. 导入并导出特定的导出
有时你可能只想导入并重新导出某些特定的导出,而不是整个模块。这可以通过以下语法实现:
// module1.ts
export const a = 1;
export const b = 2;
// module2.ts
export const c = 3;
export const d = 4;
// index.ts (聚合模块)
export { a, b } from './module1';
export { c } from './module2';
在这种情况下,index.ts 文件只会重新导出 module1 中的 a 和 b 以及 module2 中的 c。这使得导出的内容更加具体和控制:
// main.ts
import { a, b, c } from './index';
console.log(a, b, c); // 输出: 1 2 3
// console.log(d); // 错误:d 未导出
3. 重新导出默认导出
默认导出也可以重新导出,但语法稍有不同:
// module1.ts
const defaultExport = 1;
export default defaultExport;
// module2.ts
const anotherDefaultExport = 2;
export default anotherDefaultExport;
// index.ts (聚合模块)
export { default as module1Default } from './module1';
export { default as module2Default } from './module2';
这种情况下,index.ts 文件重新导出 module1 和 module2 的默认导出,并给它们指定了新的名称:
// main.ts
import { module1Default, module2Default } from './index';
console.log(module1Default); // 输出: 1
console.log(module2Default); // 输出: 2
总结
Re-export 是一种非常强大的工具,可以帮助你组织代码,创建更清晰的模块结构。无论是导入并导出整个模块,还是只导出特定的部分,亦或是处理默认导出,这些技术都能让你的代码更加模块化和可维护。通过巧妙地使用 Re-export,你可以减少导入路径的混乱,并使代码的依赖关系更加透明。
——————————
坑
forEach
forEach 的设计是同步的,它会遍历数组中的每个元素,并立即调用回调函数。如果回调函数是 async 的,forEach 会将其视为一个普通的函数调用,不会等待 await 的完成。因此,forEach 会继续执行下一个回调,而不会等待前一个回调中的异步操作完成。
示例代码
const array = [1, 2, 3];
array.forEach(async (item) => {
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟异步操作
console.log(item);
});
console.log("forEach finished");
输出结果:
forEach finished
1
2
3
Map
正确的写法
new Map([
["key1", "value1"], // 第一个元素:数组包含键和值
["key2", "value2"], // 第二个元素:数组包含键和值
// ...
])
提示
嵌套两层 [] 的原因:
- 外层
[]:表示整个参数是一个数组(可迭代对象) - 内层
[]:表示每个键值对是一个数组[key, value]
——————————
名词释义
具体的名字,结合匿名可以更好的理解 ↩︎
