现代JavaScript开发
268-280
268 Section Intro(章节介绍)
核心目标
本章节聚焦“现代JavaScript开发实践”,衔接前文的异步编程(Promise、Async/Await)与ES6模块,后续将系统讲解模块规范、工程化工具(如NPM、Parcel)、代码优化及开发最佳实践,最终帮助学习者从“基础语法掌握”过渡到“企业级项目开发能力”。
270 An Overview of Modern JavaScript Development(现代JavaScript开发概述)
270.1 现代JS开发的核心痛点
传统JS开发(如直接引入多个<script>标签)存在以下问题:
- 作用域污染:全局作用域下变量/函数冲突(如
var声明的变量全局共享);
- 依赖管理混乱:脚本加载顺序依赖
<script>标签顺序,大型项目中易出现“依赖缺失”或“重复引入”;
- 代码复用性低:无法高效拆分代码为可复用单元;
- 兼容性问题:新语法(如ES6+)在旧浏览器中无法运行;
- 缺乏工程化支持:无自动化构建、打包、压缩等流程,开发效率低。
270.2 现代JS开发的解决方案
| 痛点 |
解决方案 |
核心工具/规范 |
| 作用域污染/依赖管理 |
模块化开发 |
ES6 Modules、CommonJS |
| 兼容性问题 |
语法转换(Transpilation)+ 垫片(Polyfill) |
Babel、core-js |
| 工程化支持 |
包管理 + 构建工具 |
NPM/Yarn、Parcel/Webpack/Vite |
| 代码质量 |
静态类型检查 + 代码规范 |
TypeScript、ESLint |
270.3 现代JS开发流程示例
- 初始化项目:
npm init 创建package.json(项目配置文件);
- 安装依赖:
npm install react react-dom(安装第三方库);
- 编写代码:使用ES6+语法、ES Modules拆分代码;
- 构建打包:通过Parcel自动转换语法、合并代码、压缩体积;
- 部署上线:将打包后的
dist目录部署到服务器(如Netlify)。
271 An Overview of Modules in JavaScript(JavaScript模块概述)
271.1 模块的核心概念
模块是独立的代码单元,具有以下特性:
- 封装性:模块内的变量/函数默认私有,仅通过
export暴露外部可访问的接口;
- 依赖管理:通过
import明确声明依赖的其他模块,解决加载顺序问题;
- 可复用性:一个模块可被多个其他模块导入,避免代码重复。
271.2 常见模块规范对比
| 规范 |
适用场景 |
导出语法 |
导入语法 |
加载时机 |
| ES6 Modules |
浏览器环境、现代Node.js |
export const a = 1; export default fn |
import { a } from './mod'; import fn from './mod' |
静态加载(编译时确定依赖) |
| CommonJS |
Node.js(传统) |
module.exports = { a: 1 } |
const { a } = require('./mod') |
动态加载(运行时确定依赖) |
| AMD |
浏览器环境(旧) |
define(['dep'], (dep) => ({ a: 1 })) |
require(['./mod'], (mod) => {}) |
异步加载 |
271.3 模块与脚本(Script)的区别
| 特性 |
模块(Module) |
普通脚本(Script) |
| 作用域 |
模块级作用域(私有) |
全局作用域 |
this指向 |
undefined |
浏览器中指向window,Node.js中指向global |
| 导入/导出 |
支持import/export |
不支持 |
| 延迟执行(Defer) |
默认延迟执行(同<script defer>) |
默认立即执行(阻塞HTML解析) |
271.4 浏览器中启用ES6 Modules
需在<script>标签中添加type="module"属性,示例:
1 2
| <script type="module" src="./main.js"></script>
|
1 2 3
| import { sum } from "./math.js"; console.log(sum(1, 2));
|
1 2
| export const sum = (a, b) => a + b;
|
272 Exporting and Importing in ES6 Modules(ES6模块的导出与导入)
272.1 命名导出(Named Exports)
用于导出多个独立的变量/函数/类,导出名称与导入名称必须一致(可通过as重命名)。
语法1:直接导出(声明时导出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export const PI = 3.14159; export function calculateArea(r) { return PI * r ** 2; } export class Circle { constructor(r) { this.radius = r; } get area() { return calculateArea(this.radius); } }
|
语法2:集中导出(声明后统一导出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const PI = 3.14159; function calculateArea(r) { return PI * r ** 2; } class Circle { constructor(r) { this.radius = r; } get area() { return calculateArea(this.radius); } }
export { PI, calculateArea, Circle };
|
语法3:导出时重命名(解决名称冲突)
1 2 3
| const PI = 3.14159; export { PI as MathPI };
|
命名导入示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import { PI, calculateArea, Circle } from "./mod.js"; console.log(calculateArea(2)); const c = new Circle(3); console.log(c.area);
import { MathPI as Pi } from "./mod.js"; console.log(Pi);
import * as MathModule from "./mod.js"; console.log(MathModule.PI); console.log(MathModule.calculateArea(2));
|
272.2 默认导出(Default Exports)
用于导出模块的“主内容”(每个模块仅允许1个默认导出),导入时可自定义名称(无需加花括号)。
语法1:直接默认导出
1 2 3 4 5
| export default function formatDate(date) { return date.toLocaleDateString("zh-CN"); }
|
语法2:先声明后默认导出
1 2 3 4 5
| function formatDate(date) { return date.toLocaleDateString("zh-CN"); } export default formatDate;
|
语法3:默认导出匿名内容
1 2
| export default (a, b) => a + b;
|
默认导入示例
1 2 3 4 5 6 7 8
|
import format from "./utils.js"; console.log(format(new Date()));
import formatDate, { PI } from "./utils.js"; console.log(formatDate(new Date()), PI);
|
272.3 导出与导入的“生命联系”(Live Binding)
ES6 Modules的import与export并非“值的拷贝”,而是指向同一内存地址的引用(生命联系)。当导出模块中的值修改时,导入模块中的值会同步更新。
示例:生命联系的体现
1 2 3 4 5
| export let count = 0; export function increment() { count++; }
|
1 2 3 4 5 6
| import { count, increment } from "./mod.js";
console.log(count); increment(); console.log(count);
|
272.4 常见错误与注意事项
- 导入路径错误:相对路径需以
./或../开头(如import from 'mod.js'会被当作第三方库,而非本地模块);
- 默认导出与命名导出混淆:默认导入无需加花括号(如
import { fn } from './mod'会报错,若fn是默认导出);
- 重复导出:同一模块中不可对同一名称重复命名导出(如
export const a=1; export const a=2会报错);
- 静态导入限制:
import需在模块顶层(不可在if语句或函数内动态导入,动态导入需用import()函数)。
273 Top-Level await (ES2022)(顶层await,ES2022特性)
273.1 顶层await的核心作用
await关键字原本仅能在async函数内部使用,顶层await允许在模块顶层直接使用await,用于:
- 模块初始化时等待异步操作完成(如加载数据、初始化第三方库);
- 避免在
async函数中包裹顶层异步逻辑,简化代码。
273.2 顶层await的使用场景
场景1:等待API数据加载
1 2 3 4
| const res = await fetch("https://api.example.com/data"); const data = await res.json(); export default data;
|
1 2 3
| import data from "./data.js"; console.log(data);
|
场景2:等待第三方库初始化
1 2 3 4
| import mongoose from "mongoose"; await mongoose.connect("mongodb://localhost:27017/myDB"); export const User = mongoose.model("User", { name: String });
|
273.3 顶层await的注意事项
- 仅支持模块环境:顶层await仅能在
type="module"的脚本或ES Modules文件中使用,普通脚本(无type="module")会报错;
- 模块加载阻塞:若A模块使用顶层await,导入A模块的B模块会等待A的异步操作完成后才继续执行,需避免在核心模块中使用(防止整体加载变慢);
- 错误处理:需用
try/catch捕获顶层await的错误,否则模块会抛出未捕获的Promise拒绝:1 2 3 4 5 6 7 8 9 10
| let data; try { const res = await fetch("https://api.example.com/data"); data = await res.json(); } catch (err) { console.error("数据加载失败:", err); data = { fallback: "默认数据" }; } export default data;
|
273.4 顶层await vs 异步函数包裹
| 方式 |
代码示例 |
优缺点 |
| 顶层await |
const res = await fetch(...) |
代码简洁,无需额外函数包裹;但阻塞模块加载 |
| 异步函数包裹 |
async function init() { await fetch(...) } init(); |
不阻塞模块加载;但需手动调用函数,数据导出需处理Promise |
274 The Module Pattern(模块模式)
274.1 模块模式的核心思想
在ES6 Modules出现前,开发者通过立即执行函数表达式(IIFE) 实现模块化,核心是利用函数作用域封装私有变量,仅暴露指定的公共接口。
274.2 模块模式的实现步骤
- 定义IIFE(创建私有作用域);
- 在IIFE内部声明私有变量/函数(仅内部可访问);
- 返回包含公共接口的对象(外部通过该对象访问模块功能);
- 将返回的对象赋值给全局变量(或挂载到
window),供外部使用。
274.3 模块模式示例:计算器模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const Calculator = (function () { let currentValue = 0;
function validateNumber(n) { return typeof n === "number" && !isNaN(n); }
return { add: function (n) { if (validateNumber(n)) currentValue += n; return currentValue; }, subtract: function (n) { if (validateNumber(n)) currentValue -= n; return currentValue; }, getValue: function () { return currentValue; }, reset: function () { currentValue = 0; return currentValue; }, }; })();
console.log(Calculator.add(5)); console.log(Calculator.subtract(2)); console.log(Calculator.getValue()); console.log(Calculator.currentValue); Calculator.validateNumber(3);
|
274.4 模块模式的优缺点
| 优点 |
缺点 |
| 解决全局作用域污染 |
无原生依赖管理,需手动处理模块加载顺序 |
| 实现私有变量/函数 |
无法动态导入模块 |
| 兼容性好(支持所有浏览器) |
模块间无法共享私有成员,复用性有限 |
274.5 模块模式与ES6 Modules的对比
- 模块模式:基于函数作用域,动态执行,无原生
import/export,适合旧环境;
- ES6 Modules:基于模块作用域,静态加载,原生支持
import/export,依赖管理清晰,是现代开发的首选。
275 CommonJS Modules(CommonJS模块)
275.1 CommonJS的核心应用场景
CommonJS是Node.js的传统模块规范,主要用于服务器端开发,也可通过Browserify等工具在浏览器中使用。其设计思路是“同步加载模块”(适合服务器端,文件加载速度快,无需异步)。
275.2 CommonJS的导出与导入语法
1. 导出语法(module.exports)
- 导出对象:直接赋值给
module.exports(推荐,可导出任意类型);
- 导出属性:通过
exports对象添加属性(exports是module.exports的引用,不可直接赋值为新对象)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
const PI = 3.14; function area(r) { return PI * r ** 2; } module.exports = { PI: PI, calculateArea: area, };
exports.add = (a, b) => a + b; exports.subtract = (a, b) => a - b;
|
2. 导入语法(require())
通过require('模块路径')导入模块,返回模块导出的对象,支持解构赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
const mod = require("./mod.js"); console.log(mod.PI); console.log(mod.calculateArea(2));
const { add, subtract } = require("./mod.js"); console.log(add(1, 2)); console.log(subtract(5, 3));
const fs = require("fs"); fs.readFile("test.txt", "utf8", (err, data) => { console.log(data); });
|
275.3 CommonJS的加载机制
- 同步加载:
require()执行时会阻塞后续代码,直到模块加载完成;
- 缓存机制:模块第一次被
require时会执行并缓存,后续require同一模块会直接返回缓存结果(避免重复执行);
- 模块标识符:
- 内置模块(如
fs):直接写模块名;
- 第三方模块(如
lodash):从node_modules目录查找;
- 本地模块:以
./或../开头的相对路径,或绝对路径。
示例:CommonJS缓存机制
1 2 3 4 5 6 7
| console.log("模块执行"); let count = 0; module.exports = { increment: () => count++, getCount: () => count, };
|
1 2 3 4 5 6 7
| const mod1 = require("./mod.js"); const mod2 = require("./mod.js");
mod1.increment(); console.log(mod1.getCount()); console.log(mod2.getCount());
|
275.4 CommonJS与ES6 Modules的核心区别
| 特性 |
CommonJS |
ES6 Modules |
| 加载时机 |
运行时动态加载(执行require时) |
编译时静态加载(解析import时) |
this指向 |
模块内this指向module.exports |
模块内this指向undefined |
| 默认导出 |
module.exports = value |
export default value |
| 命名导出 |
exports.key = value |
export const key = value |
| 动态导入 |
原生支持(require可在条件语句中) |
需用import()函数(返回Promise) |
| Node.js支持 |
原生支持(默认) |
需设置"type": "module"(在package.json中) |
276 A Brief Introduction to the Command Line(命令行简介)
276.1 命令行的核心作用
命令行(Command Line Interface,CLI)是与操作系统交互的文本界面,现代JS开发中用于:
- 执行NPM命令(如
npm install、npm run build);
- 操作文件/目录(如创建、删除、移动文件);
- 运行Node.js脚本(如
node main.js);
- 部署项目(如
netlify deploy)。
276.2 常见命令行工具
| 操作系统 |
默认命令行工具 |
推荐增强工具 |
| Windows |
命令提示符(CMD)、PowerShell |
Windows Terminal、Git Bash |
| macOS |
Terminal |
iTerm2、Oh My Zsh |
| Linux |
Terminal |
Oh My Zsh |
276.3 常用命令(通用)
| 命令 |
作用 |
示例 |
pwd |
显示当前工作目录路径 |
pwd → /Users/username/project |
ls |
列出当前目录下的文件/目录 |
ls -l → 详细列表(包含权限、大小) |
cd 路径 |
切换工作目录 |
cd ../ → 回到上级目录;cd src → 进入src目录 |
mkdir 目录名 |
创建新目录 |
mkdir dist → 创建dist目录 |
touch 文件名 |
创建新文件(macOS/Linux);Windows用type nul > 文件名 |
touch index.js → 创建index.js文件 |
rm 文件名 |
删除文件 |
rm test.txt → 删除test.txt |
rm -r 目录名 |
递归删除目录及内容(macOS/Linux);Windows用rmdir /s 目录名 |
rm -r node_modules → 删除node_modules目录 |
cp 源文件 目标路径 |
复制文件 |
cp index.js src/ → 复制index.js到src目录 |
mv 源文件 目标路径 |
移动/重命名文件 |
mv old.js new.js → 重命名为new.js |
276.4 JS开发常用命令示例
- 初始化NPM项目:
npm init -y(-y表示默认回答所有问题,快速创建package.json);
- 安装开发依赖:
npm install --save-dev parcel(--save-dev缩写-D,依赖仅用于开发环境);
- 安装生产依赖:
npm install axios(依赖用于生产环境,会写入dependencies);
- 运行脚本:
npm run dev(执行package.json中scripts下的dev命令,如"dev": "parcel index.html");
- 查看Node.js版本:
node -v → v18.16.0;
- 运行JS脚本:
node main.js → 执行main.js文件。
277 Introduction to NPM(NPM简介)
277.1 NPM的核心概念
NPM(Node Package Manager)是Node.js的包管理工具,主要功能:
- 包管理:下载、安装、更新、删除第三方库(如React、Lodash);
- 项目配置:通过
package.json管理项目信息、依赖、脚本;
- 脚本执行:通过
npm run 脚本名执行自定义命令(如构建、测试、部署)。
277.2 NPM的核心文件:package.json
package.json是项目的配置清单,包含以下核心字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| { "name": "my-js-project", "version": "1.0.0", "description": "A modern JavaScript project", "main": "index.js", "type": "module", "scripts": { "dev": "parcel index.html", "build": "parcel build index.html", "test": "jest" }, "dependencies": { "axios": "^1.4.0" }, "devDependencies": { "parcel": "^2.9.1", "jest": "^29.5.0" }, "author": "Your Name", "license": "MIT" }
|
277.3 语义化版本(Semantic Versioning)
NPM依赖版本遵循MAJOR.MINOR.PATCH格式,含义:
- MAJOR(主版本):不兼容的API变更(如从v1.x到v2.x);
- MINOR(次版本):向后兼容的功能新增(如从v1.2.x到v1.3.x);
- PATCH(补丁版本):向后兼容的问题修复(如从v1.3.0到v1.3.1)。
版本前缀含义:
^1.4.0:允许次版本和补丁版本更新(如1.4.0 → 1.5.2);
~1.4.0:仅允许补丁版本更新(如1.4.0 → 1.4.3);
1.4.0:固定版本(不允许任何更新)。
277.4 NPM常用命令
| 命令 |
作用 |
示例 |
npm init |
初始化项目,创建package.json |
npm init -y → 快速初始化(默认配置) |
npm install <包名> |
安装生产依赖 |
npm install axios |
npm install -D <包名> |
安装开发依赖 |
npm install -D parcel |
npm install <包名>@<版本> |
安装指定版本的包 |
npm install axios@1.0.0 |
npm update <包名> |
更新包到最新兼容版本 |
npm update axios |
npm uninstall <包名> |
删除包 |
npm uninstall axios |
npm run <脚本名> |
执行scripts中的自定义脚本 |
npm run dev → 执行dev脚本 |
npm list |
查看已安装的包及其依赖树 |
npm list --depth 0 → 仅显示顶层依赖 |
npm outdated |
检查过时的包(版本落后于package.json) |
npm outdated |
277.5 NPM与Yarn的对比
Yarn是Facebook推出的包管理工具,解决早期NPM的性能问题,核心区别:
| 特性 |
NPM |
Yarn |
| 安装速度 |
较慢(早期),v5+优化后较快 |
较快(并行安装、缓存机制) |
| 锁文件 |
package-lock.json(v5+) |
yarn.lock(默认) |
| 命令差异 |
npm install |
yarn add |
|
npm uninstall |
yarn remove |
|
npm run dev |
yarn dev |
| 全局安装 |
npm install -g <包名> |
yarn global add <包名> |
选择建议:现代项目中NPM(v5+)与Yarn功能差异不大,可根据团队习惯选择;Vite等新工具默认支持两者。
278 Bundling With Parcel and NPM Scripts(使用Parcel和NPM脚本打包)
278.1 打包工具的核心作用
打包工具(如Parcel、Webpack)是现代JS开发的核心工具,主要功能:
- 模块合并:将多个ES Modules/CommonJS模块合并为少数几个文件(减少HTTP请求);
- 语法转换:将ES6+语法转换为ES5(兼容旧浏览器);
- 资源处理:处理CSS、图片、字体等非JS资源(如将CSS注入HTML,图片压缩);
- 代码优化:压缩JS/CSS代码(移除空格、注释,变量重命名),提升加载速度;
- 自动化:监听文件变化,自动重新打包(热更新,Hot Reload)。
278.2 Parcel的核心优势
Parcel是零配置打包工具,相比Webpack的复杂配置,Parcel无需手动编写配置文件(如webpack.config.js),开箱即用,适合快速开发。
278.3 使用Parcel打包项目的步骤
步骤1:初始化项目并安装Parcel
1 2 3 4 5 6 7 8
| mkdir parcel-demo && cd parcel-demo
npm init -y
npm install -D parcel
|
步骤2:创建项目文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html> <head> <title>Parcel Demo</title> <link rel="stylesheet" href="./style.css" /> </head> <body> <div id="app"></div> <script type="module" src="./main.js"></script> </body> </html>
|
1 2 3 4 5 6 7 8 9 10 11
| body { background: #f0f0f0; font-family: Arial, sans-serif; } #app { font-size: 24px; color: #333; text-align: center; margin-top: 50px; }
|
1 2
| export const sum = (a, b) => a + b;
|
1 2 3 4 5
| import { sum } from "./math.js";
const app = document.getElementById("app"); app.textContent = `1 + 2 = ${sum(1, 2)}`;
|
步骤3:配置NPM脚本
修改package.json的scripts字段:
1 2 3 4 5 6
| { "scripts": { "dev": "parcel index.html", "build": "parcel build index.html" } }
|
步骤4:运行开发模式
Parcel会自动:
- 启动开发服务器(默认端口1234);
- 打开浏览器访问
http://localhost:1234;
- 监听文件变化,修改后自动重新打包并刷新页面。
步骤5:打包生产版本
Parcel会生成dist目录,包含:
- 合并并压缩后的
index.html、main.[hash].js、style.[hash].css;
- 自动处理资源路径(如图片URL);
- 移除未使用的代码(Tree Shaking)。
278.4 Parcel的核心特性示例
1. 热更新(Hot Reload)
修改style.css的background为#fff,浏览器会立即更新样式,无需手动刷新。
2. 自动处理CSS
Parcel会将style.css注入到HTML的<head>中(开发模式),或打包为独立的CSS文件(生产模式)。
3. 图片处理
在index.html中添加图片:
1
| <img src="./logo.png" alt="Logo" />
|
Parcel会自动压缩图片,并在生产模式下生成带哈希值的文件名(如logo.abc123.png),避免缓存问题。
4. Tree Shaking(移除未使用代码)
若math.js中有未被导入的函数:
1 2 3
| export const sum = (a, b) => a + b; export const multiply = (a, b) => a * b;
|
Parcel在生产模式下会自动移除multiply函数,减小打包体积。
278.5 常见问题与解决方案
- 端口被占用:修改
dev脚本为parcel index.html --port 3000(指定端口3000);
- 打包后路径错误:若部署到子目录(如
https://example.com/my-app),需添加--public-url ./:1
| "build": "parcel build index.html --public-url ./"
|
- 缓存问题:生产模式下Parcel会为文件名添加哈希值(如
main.abc123.js),确保新版本不被浏览器缓存。
279 Configuring Babel and Polyfilling(配置Babel与垫片)
279.1 Babel的核心作用
Babel是JS语法转换工具,用于将ES6+(如箭头函数、let/const、解构赋值)转换为ES5语法,解决旧浏览器(如IE11)的兼容性问题。
279.2 垫片(Polyfill)的核心作用
Babel仅转换语法,无法处理ES6+新增的API(如Promise、Array.prototype.includes、Object.assign)。垫片(如core-js)会为旧浏览器添加这些API的实现,确保代码正常运行。
279.3 Babel与Polyfill的配置步骤
步骤1:安装依赖
1 2 3 4 5
| npm install -D @babel/core @babel/cli @babel/preset-env
npm install core-js@3
|
@babel/core:Babel的核心转换功能;
@babel/cli:Babel的命令行工具;
@babel/preset-env:预设集合,自动根据目标浏览器选择需要转换的语法;
core-js@3:ES6+ API的垫片库。
步骤2:创建Babel配置文件
在项目根目录创建babel.config.json(Babel 7+推荐的配置文件格式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "presets": [ [ "@babel/preset-env", { "targets": { "ie": "11", "chrome": "58", "firefox": "54" }, "useBuiltIns": "usage", "corejs": 3 } ] ] }
|
步骤3:配置NPM脚本
修改package.json的scripts字段,添加Babel转换命令:
1 2 3 4 5 6 7
| { "scripts": { "dev": "parcel index.html", "build": "parcel build index.html", "babel:transpile": "babel src --out-dir dist/src" } }
|
步骤4:测试语法转换与垫片
创建包含ES6+语法和API的文件:
1 2 3 4 5 6 7
| const arr = [1, 2, 3]; const hasTwo = arr.includes(2); const promise = new Promise((resolve) => resolve("ok"));
console.log(hasTwo); promise.then(console.log);
|
运行npm run babel:transpile,转换后的dist/src/main.js会:
- 将
const转换为var(兼容IE11);
- 自动导入
core-js的Array.prototype.includes和Promise垫片;
- 保留代码逻辑不变。
279.4 Babel预设与插件
- 预设(Preset):是插件的集合,用于简化配置(如
@babel/preset-env包含所有ES6+语法转换插件);
- 插件(Plugin):用于转换特定语法(如
@babel/plugin-proposal-class-properties用于转换类的私有属性)。
示例:添加类私有属性支持
- 安装插件:
npm install -D @babel/plugin-proposal-class-properties;
- 修改
babel.config.json:1 2 3 4
| { "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-proposal-class-properties"] }
|
- 测试代码:
1 2 3 4 5 6 7 8
| class Person { #name = "Alice"; getName() { return this.#name; } } const p = new Person(); console.log(p.getName());
|
转换后会兼容旧浏览器。
279.5 useBuiltIns的三种模式
| 模式 |
作用 |
优点 |
缺点 |
"usage" |
自动按需导入垫片(仅导入使用的API) |
垫片体积最小 |
需指定corejs版本 |
"entry" |
在入口文件手动导入core-js,按需添加 |
可控性强 |
需手动在入口文件添加import 'core-js' |
false |
不自动添加垫片 |
适合仅需要语法转换的场景 |
需手动导入所有需要的垫片,易遗漏 |
推荐使用"usage",平衡体积与便利性。
280 Review: Writing Clean and Modern JavaScript(复习:编写简洁现代的JavaScript)
280.1 变量声明:优先使用const/let,避免var
const:用于不可变变量(如常量、函数、对象/数组引用);
let:用于可变变量(如循环变量、条件赋值变量);
var:存在变量提升、全局污染问题,避免使用。
示例:正确的变量声明
1 2 3 4 5 6 7 8 9
| const PI = 3.14; const user = { name: "Alice" }; let count = 0; count++;
var age = 20; age = 21;
|
280.2 函数定义:优先使用箭头函数和简洁语法
- 单行函数:用箭头函数简化代码(
(a,b) => a+b);
- 多行函数:若需
this绑定(如对象方法),用function关键字;
- 避免匿名函数(如
setTimeout(function() {}, 1000)),使用命名函数提升可读性。
示例:简洁的函数定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const sum = (a, b) => a + b; const getUser = () => ({ name: "Bob", age: 30 });
const calculator = { value: 0, add(n) { this.value += n; return this.value; }, };
function multiply(a, b) { return a * b; }
setTimeout(function () { console.log("done"); }, 1000);
|
280.3 数据结构:优先使用数组方法和对象简写
- 数组遍历:用
map/filter/reduce替代for循环(函数式编程,可读性高);
- 对象简写:属性名与变量名相同时,用
{ name }替代{ name: name };
- 解构赋值:快速提取数组/对象的属性,避免重复代码。
示例:现代数据结构操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const numbers = [1, 2, 3, 4]; const doubled = numbers.map((n) => n * 2); const even = numbers.filter((n) => n % 2 === 0); const sum = numbers.reduce((acc, n) => acc + n, 0);
const name = "Charlie"; const age = 25; const person = { name, age };
const { name: userName } = person; const [first, second] = numbers;
|
280.4 模块化:使用ES6 Modules拆分代码
- 按功能拆分模块(如
math.js、utils.js);
- 明确导出接口(优先用命名导出,单一功能用默认导出);
- 避免全局变量,通过
import/export管理依赖。
示例:模块化代码
1 2 3 4 5 6 7 8 9 10
| export const formatDate = (date) => date.toLocaleDateString("zh-CN"); export const isEmpty = (value) => value === null || value === undefined || value === "";
import { formatDate, isEmpty } from "./utils.js";
console.log(formatDate(new Date())); console.log(isEmpty(""));
|
280.5 错误处理:用try/catch捕获异步/同步错误
- 同步错误:直接用
try/catch包裹;
- 异步错误:
async/await中用try/catch,或Promise中用.catch();
- 避免“沉默错误”(如不处理
Promise的reject)。
示例:正确的错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function parseJSON(str) { try { return JSON.parse(str); } catch (err) { console.error("JSON解析错误:", err); return null; } }
async function fetchData() { try { const res = await fetch("https://api.example.com/data"); if (!res.ok) throw new Error(`HTTP错误:${res.status}`); const data = await res.json(); return data; } catch (err) { console.error("数据加载失败:", err); return { fallback: "默认数据" }; } }
|
280.6 代码风格:遵循ESLint规范
- 安装ESLint:
npm install -D eslint;
- 初始化配置:
npx eslint --init(选择Airbnb/Standard等流行规范);
- 自动修复:
npx eslint src --fix(自动修复多数代码风格问题)。
示例:ESLint检测的常见问题
- 未使用的变量(如
const a = 1;未被使用);
- 语句末尾缺少分号(或多余分号,根据规范);
- 缩进不一致(如混用空格和制表符);
- 字符串引号不统一(如混用单引号和双引号)。