拾忆🍂拾忆🍂
  • cpp
  • MySQL
  • Oracle
  • PostgreSQL
  • MyBatis
  • API升级
  • HMOS
  • 百变小组件
  • hdc
  • 元服务
  • Java
  • MinIO
  • Stream
  • JSP & Struts2
  • Spring
  • FFmpeg
  • Linux
  • Git
  • Nginx
  • Ollama
  • Adobe Audition
  • Aseprite
  • Excel
  • Markdown基本用法
  • MuseScore 4.x
  • UVR
  • Windows
  • emoji-cheat-sheet
  • IDE快捷键
  • obs-studio
  • YOLO
  • Python
  • VuePress 2.x
  • 内置组件
  • markdown-container
  • markdown-ext
  • markdown-hint
  • markdown-preview
  • markdown-tab
  • Markdown扩展语法
  • 插件配置
  • prismjs
  • 样式
  • CSS
  • JS
  • TS
  • Vue3
主页
梦的开始🌅
  • cpp
  • MySQL
  • Oracle
  • PostgreSQL
  • MyBatis
  • API升级
  • HMOS
  • 百变小组件
  • hdc
  • 元服务
  • Java
  • MinIO
  • Stream
  • JSP & Struts2
  • Spring
  • FFmpeg
  • Linux
  • Git
  • Nginx
  • Ollama
  • Adobe Audition
  • Aseprite
  • Excel
  • Markdown基本用法
  • MuseScore 4.x
  • UVR
  • Windows
  • emoji-cheat-sheet
  • IDE快捷键
  • obs-studio
  • YOLO
  • Python
  • VuePress 2.x
  • 内置组件
  • markdown-container
  • markdown-ext
  • markdown-hint
  • markdown-preview
  • markdown-tab
  • Markdown扩展语法
  • 插件配置
  • prismjs
  • 样式
  • CSS
  • JS
  • TS
  • Vue3
主页
梦的开始🌅
  • 「从开始,到永久」
  • C艹

    • cpp
  • Database

    • MySQL
    • Oracle
    • PostgreSQL
    • MyBatis
  • HarmonyOS

    • API升级
    • HMOS
    • 百变小组件
    • hdc
    • 元服务
  • Java

    • Java
    • MinIO
    • Stream
    • JSP & Struts2
    • Spring
  • Linux

    • FFmpeg
    • Linux
    • Git
    • Nginx
  • LLM

    • Ollama
  • Others

    • Adobe Audition
    • Aseprite
    • Excel
    • Markdown基本用法
    • MuseScore 4.x
    • UVR
    • Windows
    • emoji-cheat-sheet
    • IDE快捷键
    • obs-studio
    • YOLO
  • Python

    • Python
  • VuePress

    • VuePress 2.x
    • 内置组件
    • markdown-container
    • markdown-ext
    • markdown-hint
    • markdown-preview
    • markdown-tab
    • Markdown扩展语法
    • 插件配置
    • prismjs
    • 样式
  • Web

    • CSS
    • JS
    • TS
    • Vue3
  • 主页

警告

包管理从始至终使用同一个

学习

ES6 入门教程 - ECMAScript 6入门 (ruanyifeng.com)

提示

此文档包含ES6及TS语法

node & npm

安装

Node.js — Download Node.js® (nodejs.org)

验证

node -v
npm -v

配置

  1. 首先, 找到node的安装路径

    D:\Program Files\nodejs

  2. 在此目录下创建两个文件夹

    • node_global

    • node_cache

  3. 在控制台分别运行以下两条命令(注意改成自己的路径)

    # 全局模块插件存放路径
    npm config set prefix "D:\Program Files\nodejs\node_global"
    # 缓存路径
    npm config set cache "D:\Program Files\nodejs\node_cache"
    
  4. 配置npm安装源

    # 淘宝源
    npm config set registry "https://registry.npmmirror.com/"
    
    # 华为云源
    npm config set registry https://mirrors.huaweicloud.com/repository/npm/
    
  5. 把 D:\Program Files\nodejs\node_global\node_modules 添加到系统环境变

    变量名: NODE_PATH
    变量值: D:\Program Files\nodejs\node_global\node_modules

  6. 验证

    npm config get prefix
    npm config get cache
    npm config get registry
    
  7. (可选)

    更新到最新版本 npm install npm@latest -g

    更新到指定版本 npm -g install npm@6.8.0

yarn

安装

npm install -g yarn

验证

yarn config get registry
# 或者
yarn config list

配置

  1. 配置镜像源

    # 淘宝源
    yarn config set registry "https://registry.npmmirror.com/"
    
    # 华为云源
    yarn config set registry https://mirrors.huaweicloud.com/repository/npm/
    
  2. 修改全局安装目录

    yarn config set global-folder "D:\Program Files\nodejs\node_global"
    
  3. 修改bin目录

    # 在全局安装目录下新建./bin目录
    yarn config set prefix "D:\Program Files\nodejs\node_global"
    # 会自动设置成./bin 
    
  4. 修改缓存目录

    yarn config set cache-folder "D:\Program Files\nodejs\node_cache"
    
  5. 验证所有配置

    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

  1. 停止所有正在运行的 Node.js 应用

  2. 复制现有 store 到新位置

  3. 设置新的 store 路径(使用上述方法之一)

  4. 删除旧的 store 目录(可选)

  5. 验证配置是否生效

    # 检查当前 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: 对应上方 path
    • Path里加入:%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-servernpm install -g json-serverjson-server data.json
# 配置参数(实时监测 + 自定义端口)
json-server data.json --watch --port 3000
1. GitHub - typicode/json-server
2. json-server 详解(cnblogs.com)
mockjsnpm install -g mockjsD:\WorkSpace\TypeScript\mock1. 修改server.js
2. node server.js
3. 保存为json
http-servernpm install -g http-serverhttp-server -p 8899 -o可以将当前文件夹当成服务器根目录,可以访问静态html页面、播放本地视频
expressnpm install expressnode xxx.js直接运行js文件(将用js来处理http请求)
sort-package-jsonnpm install -g sort-package-jsonsort-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`);
    });
    
    

版本符号释义

文档

  • ZhangEnlin/es6tutorial: 《ECMAScript 6入门》是一本开源的 JavaScript 语言教程 (github.com)

  • ZhangEnlin/typescript-tutorial: TypeScript 教程 (github.com)

箭头函数

限制返回值类型

在 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));

注意点

  1. 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)支持
  2. 类型声明文件(.d.ts):

    • 对于 TypeScript 项目,包需要提供类型声明(如 index.d.ts),否则即使代码存在,类型检查也会失败。
  3. 模块打包工具的影响:

    • Webpack/Rollup 等工具可能默认启用 exports 字段的严格解析,导致未声明的子路径导入失败。
场景能否访问其他文件导出?条件
直接导入包名仅主入口导出的内容主入口需显式导出
通过子路径导入✅ 可以包未限制 exports 或子路径在 exports 中声明
包使用严格 exports仅允许声明的路径未声明的子路径即使文件存在也会报错
主入口聚合所有导出✅ 可以主入口文件通过 export * 统一导出

动态导入

import() 导入规则和和静态导入的导入规则一致,其他区别在于:

  1. 运行时解析与条件加载
    动态导入是运行时执行的,因此路径可以是动态生成的(如基于用户输入或环境变量):

    const moduleName = someCondition ? 'utils' : 'other';
    import(`package-name/$${moduleName}`).then(...);
    
    • 优势:支持按需加载和代码分割,减少初始加载体积。
    • 限制:若动态路径未在 exports 中声明,即使文件存在也会报错。
  2. 错误处理机制
    静态导入在编译阶段会检查路径有效性,而动态导入的错误在运行时通过 Promise 捕获:

    // 静态导入:编译时报错
    import { missing } from 'package-name'; // 直接报错
    
    // 动态导入:运行时捕获
    import('package-name/missing')
      .then(...)
      .catch(err => console.error('加载失败', err)); 
    
  3. 打包优化差异

    • 静态导入:打包工具(如 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;

类型断言的应用场景

  1. 从 any 类型中恢复类型信息,如上。

  2. 操作 DOM 元素时,类型断言可以告诉编译器元素的具体类型,从而访问特定类型的方法和属性。

    let inputElement = document.getElementById("myInput") as HTMLInputElement;
    inputElement.value = "Hello, World!";
    
  3. 在处理联合类型时,类型断言可以帮助编译器确定变量的具体类型。

    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);
	});

调用链

调用链有三种需求:

  1. 期中有异常,统一处理异常并且中断调用链
  2. 期中有异常,分别处理每个then中的异常,并且中断调用链(和1的区别就是分别处理异常)
  3. 期中有异常,分别处理每个then中的异常,但是不中断调用链

异常时机:

  1. 同步异常:代码中运行时错误,或手动直接抛出的throw new Error(),不会被链式的catch捕获,会直接在控制台抛出这个异常。如果是在普通方法中发生了异常,那么这个方法会直接停止执行,这个方法中调用的未完成的Promise调用链也会停止执行。
  2. 异步异常:被包装成 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
      
  • 总结

    1. 同步异常不会被调用链中的 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); // ❌ 不执行,因为前面没有错误
  });
  1. 一旦发生异常( throw 、Promise.reject 等),就会跳过后续所有的 then;
  2. 直到遇到第一个 catch 来处理错误;
  3. catch 处理完后,后续的链式调用恢复正常(除非 catch 本身也出错);
  4. 如果没有 catch ,异常会一直传播到最末端;

与同步代码执行优先级

  1. 主线程同步代码:立即执行(包括Promise构造函数)
  2. 微任务队列:Promise的then/catch/finally回调
  3. 事件循环机制:
    • 执行所有同步代码
    • 检查微任务队列并执行所有微任务
    • 执行渲染(浏览器)
    • 执行宏任务(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()

  1. 按需加载

    button.addEventListener('click', event => {
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });
    
  2. 条件加载

    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]

——————————

名词释义


  1. 具体的名字,结合匿名可以更好的理解 ↩︎

最近更新: 2025/12/22 20:31
Contributors: Enlin
Prev
JS
Next
Vue3