JavaScript 从入门到进阶:核心语法、异步编程与工程化实践

JavaScript 是现代 Web 开发的核心语言之一。它最初用于为网页添加交互能力,如今已经广泛应用于浏览器前端、服务端、桌面应用、移动应用、自动化脚本以及物联网等领域。

本文将从 JavaScript 的基本概念开始,逐步介绍变量、数据类型、函数、对象、原型、异步编程、DOM、网络请求、模块化、Node.js、性能优化和安全实践,并通过一个完整的待办事项项目串联核心知识。


1. JavaScript 是什么

JavaScript 是一种高级、动态类型、基于原型、多范式的编程语言。

它支持多种编程方式:

  • 命令式编程
  • 面向对象编程
  • 函数式编程
  • 事件驱动编程
  • 异步编程

JavaScript 与 ECMAScript 经常同时出现,但二者并不完全相同:

  • ECMAScript:JavaScript 语言的标准规范。
  • JavaScript:ECMAScript 规范的一种实际实现,以及围绕它形成的运行时和生态系统。
  • DOM、Fetch、localStorage:浏览器提供的 Web API,并不是 ECMAScript 核心语言的一部分。
  • 文件、网络、进程 API:通常由 Node.js 等服务端运行时提供。

可以把一个 JavaScript 运行环境理解为:

text 复制代码
JavaScript 运行环境
├── JavaScript 引擎
│   ├── 解析源代码
│   ├── 编译或即时编译
│   ├── 执行代码
│   └── 垃圾回收
├── 宿主环境 API
│   ├── 浏览器:DOM、Fetch、Storage
│   └── Node.js:文件、网络、进程
└── 任务调度机制
    ├── 调用栈
    ├── 任务队列
    └── 微任务队列

JavaScript 并不只是传统意义上的"解释型语言"。现代 JavaScript 引擎通常会对代码进行解析、编译和即时优化。


2. JavaScript 的运行环境

2.1 浏览器

浏览器是学习 JavaScript 最方便的环境。

F12 打开开发者工具,在 Console 控制台中输入:

javascript 复制代码
console.log("Hello, JavaScript!");

浏览器不仅可以执行 JavaScript,还提供了:

  • DOM 页面操作
  • 用户事件
  • 网络请求
  • 本地存储
  • 定时器
  • Canvas
  • Web Worker
  • WebSocket

2.2 Node.js

Node.js 允许 JavaScript 脱离浏览器运行,常用于:

  • Web 服务端
  • 命令行工具
  • 自动化脚本
  • 构建工具
  • 服务端渲染
  • 桌面应用的底层能力

创建 hello.js

javascript 复制代码
console.log("Hello from Node.js!");

执行:

bash 复制代码
node hello.js

2.3 浏览器与 Node.js 的区别

能力 浏览器 Node.js
JavaScript 核心语法 支持 支持
DOM 支持 默认不支持
window 支持 不支持
document 支持 不支持
文件系统 浏览器中受限制 支持
process 不支持 支持
CommonJS 默认不支持 支持
ES Module 支持 支持

因此,JavaScript 代码能否跨环境运行,取决于它是否依赖特定宿主提供的 API。


3. 第一个 JavaScript 程序

创建一个 index.html 文件:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JavaScript 示例</title>
</head>
<body>
  <button id="hello-button">点击我</button>

  <script type="module">
    const button = document.querySelector("#hello-button");

    button.addEventListener("click", () => {
      alert("Hello, JavaScript!");
    });
  </script>
</body>
</html>

示例完成了三个操作:

  1. 使用 document.querySelector() 获取按钮。
  2. 使用 addEventListener()监听点击事件。
  3. 点击按钮后调用 alert() 显示消息。

实际项目通常将 JavaScript 放在单独文件中:

html 复制代码
<script type="module" src="./main.js"></script>

main.js

javascript 复制代码
const button = document.querySelector("#hello-button");

button.addEventListener("click", () => {
  alert("Hello, JavaScript!");
});

使用 ES Module 时,建议通过本地 HTTP 服务打开页面,而不是直接使用 file:// 协议。


4. 变量与常量

JavaScript 可以使用 constletvar 声明变量。

4.1 const

const 声明的变量不能被重新赋值:

javascript 复制代码
const siteName = "JavaScript 教程";

console.log(siteName);

下面的代码会报错:

javascript 复制代码
const siteName = "JavaScript 教程";
siteName = "新的名称";

但是,const 只限制变量本身不能指向另一个值,并不意味着对象内部完全不可修改:

javascript 复制代码
const user = {
  name: "张三",
  age: 20
};

user.age = 21;

console.log(user);

如果希望冻结对象,可以使用:

javascript 复制代码
const config = Object.freeze({
  host: "localhost",
  port: 8080
});

Object.freeze() 默认只进行浅层冻结,嵌套对象仍可能被修改。

4.2 let

let 适合声明需要重新赋值的变量:

javascript 复制代码
let count = 0;

count += 1;
count += 1;

console.log(count);

4.3 var

var 是早期 JavaScript 的变量声明方式:

javascript 复制代码
var message = "Hello";

它存在以下特点:

  • 使用函数作用域,而不是块级作用域。
  • 声明会被提升。
  • 允许在同一作用域中重复声明。
  • 在循环、异步回调和大型函数中容易产生误解。

示例:

javascript 复制代码
if (true) {
  var value = 100;
}

console.log(value); // 100

letconst 具有块级作用域:

javascript 复制代码
if (true) {
  const value = 100;
}

console.log(value); // ReferenceError

现代 JavaScript 推荐:

  1. 默认使用 const
  2. 确实需要重新赋值时使用 let
  3. 新代码通常不再使用 var

4.4 暂时性死区

letconst 在声明语句执行前不能访问:

javascript 复制代码
console.log(count); // ReferenceError

let count = 10;

从进入作用域到变量完成初始化之间的区域,通常称为"暂时性死区"。


5. 数据类型

JavaScript 数据类型分为原始类型和对象类型。

5.1 原始类型

JavaScript 有以下原始类型:

类型 示例
undefined undefined
null null
boolean truefalse
number 103.14NaN
bigint 9007199254740993n
string "Hello"
symbol Symbol("id")

5.2 undefined

变量已经声明但尚未赋值时,其值通常是 undefined

javascript 复制代码
let result;

console.log(result);        // undefined
console.log(typeof result); // "undefined"

5.3 null

null 通常表示开发者明确指定"没有值":

javascript 复制代码
const selectedUser = null;

需要注意:

javascript 复制代码
console.log(typeof null); // "object"

这是 JavaScript 长期保留的历史行为。判断 null 应直接比较:

javascript 复制代码
if (selectedUser === null) {
  console.log("当前没有选中用户");
}

5.4 boolean

布尔值只有两个:

javascript 复制代码
const isAdmin = true;
const isDeleted = false;

以下值转换为布尔值时为 false

  • false
  • 0
  • -0
  • 0n
  • ""
  • null
  • undefined
  • NaN

其他值通常都为 true,包括空数组和空对象:

javascript 复制代码
console.log(Boolean([]));    // true
console.log(Boolean({}));    // true
console.log(Boolean("false")); // true

5.5 number

JavaScript 的普通数字使用 number 类型:

javascript 复制代码
const age = 18;
const price = 99.9;
const temperature = -10;

常见特殊值:

javascript 复制代码
console.log(1 / 0);           // Infinity
console.log(-1 / 0);          // -Infinity
console.log(Number("hello")); // NaN

判断 NaN 推荐使用:

javascript 复制代码
console.log(Number.isNaN(NaN));        // true
console.log(Number.isNaN("hello"));    // false
console.log(Number.isNaN(Number("x"))); // true

JavaScript 浮点数可能存在精度问题:

javascript 复制代码
console.log(0.1 + 0.2); // 0.30000000000000004

金额计算通常应该:

  • 使用整数最小货币单位,例如"分"。
  • 或使用经过验证的高精度十进制库。
  • 不要直接依赖二进制浮点数完成严格财务结算。
javascript 复制代码
const priceInCents = 1990;
const quantity = 3;
const totalInCents = priceInCents * quantity;

console.log(totalInCents / 100); // 59.7

5.6 bigint

bigint 用于表示超出安全整数范围的整数:

javascript 复制代码
const largeNumber = 9007199254740993n;

console.log(typeof largeNumber); // "bigint"

bigint 不能直接与 number 混合运算:

javascript 复制代码
const result = 10n + 20n;

console.log(result); // 30n

下面的代码会报错:

javascript 复制代码
10n + 20;

5.7 string

字符串可以使用单引号、双引号或模板字符串:

javascript 复制代码
const firstName = "张";
const lastName = '三';
const fullName = `${firstName}${lastName}`;

console.log(fullName);

字符串是不可变的:

javascript 复制代码
const text = "JavaScript";
text[0] = "T";

console.log(text); // JavaScript

5.8 symbol

每次调用 Symbol() 都会创建一个唯一值:

javascript 复制代码
const id1 = Symbol("id");
const id2 = Symbol("id");

console.log(id1 === id2); // false

Symbol 可以用作对象属性键:

javascript 复制代码
const internalId = Symbol("internalId");

const user = {
  name: "张三",
  [internalId]: 1001
};

console.log(user[internalId]);

5.9 对象类型

对象可以保存多个属性:

javascript 复制代码
const user = {
  name: "张三",
  age: 20,
  skills: ["JavaScript", "HTML", "CSS"]
};

数组、函数、日期、正则表达式、Map 和 Set 本质上都属于对象体系:

javascript 复制代码
console.log(typeof {});             // "object"
console.log(typeof []);             // "object"
console.log(typeof function () {}); // "function"

判断数组应使用:

javascript 复制代码
console.log(Array.isArray([])); // true

5.10 值传递与引用共享

原始值赋值后互不影响:

javascript 复制代码
let a = 10;
let b = a;

b = 20;

console.log(a); // 10
console.log(b); // 20

对象变量保存的是对对象的引用:

javascript 复制代码
const user1 = {
  name: "张三"
};

const user2 = user1;
user2.name = "李四";

console.log(user1.name); // 李四

浅拷贝对象:

javascript 复制代码
const source = {
  name: "张三",
  address: {
    city: "上海"
  }
};

const copy = { ...source };

copy.name = "李四";
copy.address.city = "北京";

console.log(source.name);         // 张三
console.log(source.address.city); // 北京

展开语法只执行浅拷贝。适合的数据可以使用 structuredClone() 深拷贝:

javascript 复制代码
const cloned = structuredClone(source);

cloned.address.city = "深圳";

console.log(source.address.city); // 北京

6. 类型转换与相等比较

6.1 显式类型转换

转换为数字:

javascript 复制代码
console.log(Number("123"));   // 123
console.log(Number(""));      // 0
console.log(Number("12px"));  // NaN
console.log(parseInt("12px", 10)); // 12
console.log(parseFloat("3.14kg")); // 3.14

转换为字符串:

javascript 复制代码
console.log(String(123));       // "123"
console.log((123).toString());  // "123"

转换为布尔值:

javascript 复制代码
console.log(Boolean(1));     // true
console.log(Boolean(0));     // false
console.log(Boolean(""));    // false
console.log(Boolean("0"));   // true

6.2 隐式类型转换

JavaScript 在某些运算中会自动转换类型:

javascript 复制代码
console.log("5" + 1); // "51"
console.log("5" - 1); // 4
console.log(true + 1); // 2

+ 同时具有数字加法和字符串拼接两种含义,因此特别容易引起问题。

推荐在数据边界进行显式转换:

javascript 复制代码
const input = "5";
const value = Number(input);

if (!Number.isFinite(value)) {
  throw new Error("输入必须是有效数字");
}

console.log(value + 1);

6.3 == 与 ===

== 在比较前可能进行隐式类型转换:

javascript 复制代码
console.log(0 == false);  // true
console.log("" == false); // true
console.log("1" == 1);    // true

=== 不进行这种类型转换:

javascript 复制代码
console.log(0 === false); // false
console.log("1" === 1);   // false

现代项目通常优先使用:

  • ===
  • !==

6.4 Object.is()

Object.is()=== 大部分情况下相同,但对 NaN 和正负零的处理不同:

javascript 复制代码
console.log(NaN === NaN);           // false
console.log(Object.is(NaN, NaN));   // true

console.log(0 === -0);              // true
console.log(Object.is(0, -0));      // false

7. 运算符与流程控制

7.1 算术运算符

javascript 复制代码
const a = 10;
const b = 3;

console.log(a + b);  // 13
console.log(a - b);  // 7
console.log(a * b);  // 30
console.log(a / b);  // 3.333...
console.log(a % b);  // 1
console.log(a ** b); // 1000

7.2 逻辑运算符

javascript 复制代码
const isLogin = true;
const isAdmin = false;

console.log(isLogin && isAdmin); // false
console.log(isLogin || isAdmin); // true
console.log(!isLogin);           // false

&&|| 返回的不一定是布尔值:

javascript 复制代码
const user = {
  name: "张三"
};

const name = user && user.name;

console.log(name); // 张三

7.3 空值合并运算符

?? 只在左侧为 nullundefined 时使用默认值:

javascript 复制代码
const count = 0;

console.log(count || 10); // 10
console.log(count ?? 10); // 0

如果 0、空字符串或 false 是有效业务值,通常应该使用 ??

7.4 if 语句

javascript 复制代码
const score = 85;

if (score >= 90) {
  console.log("优秀");
} else if (score >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}

7.5 三元运算符

javascript 复制代码
const age = 20;
const message = age >= 18 ? "成年人" : "未成年人";

console.log(message);

三元运算符适合简单表达式,不适合嵌套大量业务逻辑。

7.6 switch 语句

javascript 复制代码
const status = "paid";

switch (status) {
  case "pending":
    console.log("待支付");
    break;
  case "paid":
    console.log("已支付");
    break;
  case "closed":
    console.log("已关闭");
    break;
  default:
    console.log("未知状态");
}

7.7 循环

普通 for 循环:

javascript 复制代码
for (let index = 0; index < 3; index += 1) {
  console.log(index);
}

for...of 遍历可迭代对象的值:

javascript 复制代码
const languages = ["JavaScript", "Java", "Python"];

for (const language of languages) {
  console.log(language);
}

for...in 遍历对象的可枚举属性键:

javascript 复制代码
const user = {
  name: "张三",
  age: 20
};

for (const key in user) {
  if (Object.hasOwn(user, key)) {
    console.log(key, user[key]);
  }
}

遍历数组通常使用 for...of,不要把 for...in 当作数组值循环。


8. 函数

函数用于封装可复用逻辑。

8.1 函数声明

javascript 复制代码
function add(a, b) {
  return a + b;
}

console.log(add(10, 20));

8.2 函数表达式

javascript 复制代码
const subtract = function (a, b) {
  return a - b;
};

console.log(subtract(20, 10));

8.3 箭头函数

javascript 复制代码
const multiply = (a, b) => {
  return a * b;
};

const square = value => value * value;

console.log(multiply(3, 4));
console.log(square(5));

箭头函数适合:

  • 简短回调
  • 数组处理
  • 不需要独立 this 的函数

箭头函数没有自己的:

  • this
  • arguments
  • prototype
  • 构造调用能力

因此不能使用 new 调用箭头函数。

8.4 默认参数

javascript 复制代码
function greet(name = "访客") {
  return `你好,${name}`;
}

console.log(greet());
console.log(greet("张三"));

8.5 剩余参数

javascript 复制代码
function sum(...numbers) {
  return numbers.reduce((total, number) => total + number, 0);
}

console.log(sum(1, 2, 3, 4));

8.6 回调函数

函数可以作为参数传递:

javascript 复制代码
function calculate(a, b, operation) {
  return operation(a, b);
}

const result = calculate(10, 20, (x, y) => x + y);

console.log(result);

8.7 高阶函数

接收函数或者返回函数的函数称为高阶函数:

javascript 复制代码
function createMultiplier(factor) {
  return value => value * factor;
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(10)); // 20
console.log(triple(10)); // 30

8.8 参数校验

JavaScript 不会自动限制参数类型:

javascript 复制代码
function divide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("a 和 b 必须是数字");
  }

  if (b === 0) {
    throw new RangeError("除数不能为 0");
  }

  return a / b;
}

9. 作用域与闭包

9.1 作用域

常见作用域包括:

  • 全局作用域
  • 模块作用域
  • 函数作用域
  • 块级作用域
javascript 复制代码
const globalValue = "global";

function outer() {
  const functionValue = "function";

  if (true) {
    const blockValue = "block";

    console.log(globalValue);
    console.log(functionValue);
    console.log(blockValue);
  }
}

内部作用域可以访问外部作用域,外部作用域不能直接访问内部变量。

9.2 词法作用域

函数能够访问哪些变量,主要由函数定义的位置决定:

javascript 复制代码
const name = "全局变量";

function outer() {
  const name = "外层变量";

  function inner() {
    console.log(name);
  }

  return inner;
}

const execute = outer();
execute(); // 外层变量

9.3 闭包

闭包可以理解为"函数与其定义时词法环境的组合"。

javascript 复制代码
function createCounter() {
  let count = 0;

  return {
    increment() {
      count += 1;
    },
    decrement() {
      count -= 1;
    },
    getValue() {
      return count;
    }
  };
}

const counter = createCounter();

counter.increment();
counter.increment();
counter.decrement();

console.log(counter.getValue()); // 1

即使 createCounter() 已经执行结束,返回的方法仍然可以访问 count

闭包常用于:

  • 数据封装
  • 创建工厂函数
  • 缓存
  • 防抖与节流
  • 事件处理
  • 模块私有状态

闭包也可能延长变量生命周期。如果闭包长期持有大型对象,就可能增加内存占用。


10. 对象与原型

10.1 创建对象

javascript 复制代码
const user = {
  id: 1001,
  name: "张三",
  age: 20,
  greet() {
    console.log(`你好,我是${this.name}`);
  }
};

user.greet();

10.2 访问属性

javascript 复制代码
console.log(user.name);
console.log(user["age"]);

const propertyName = "name";

console.log(user[propertyName]);

属性名动态计算时,应使用方括号。

10.3 新增、修改与删除属性

javascript 复制代码
const product = {
  name: "键盘"
};

product.price = 299;
product.name = "机械键盘";

delete product.price;

10.4 判断自有属性

javascript 复制代码
const user = {
  name: "张三"
};

console.log(Object.hasOwn(user, "name"));     // true
console.log(Object.hasOwn(user, "toString")); // false

10.5 遍历对象

javascript 复制代码
const user = {
  name: "张三",
  age: 20
};

console.log(Object.keys(user));
console.log(Object.values(user));
console.log(Object.entries(user));

for (const [key, value] of Object.entries(user)) {
  console.log(key, value);
}

10.6 原型链

JavaScript 对象可以通过原型继承其他对象的属性和方法:

javascript 复制代码
const animal = {
  speak() {
    console.log(`${this.name} 发出了声音`);
  }
};

const dog = Object.create(animal);

dog.name = "旺财";
dog.speak();

当访问 dog.speak 时,JavaScript 会:

  1. 检查 dog 自身是否存在 speak
  2. 如果不存在,继续查找 dog 的原型。
  3. 沿原型链继续向上查找。
  4. 到达 null 后仍未找到,则返回 undefined

10.7 不要随意修改内置原型

不推荐这样做:

javascript 复制代码
Array.prototype.first = function () {
  return this[0];
};

修改全局内置原型可能造成:

  • 命名冲突
  • 枚举异常
  • 第三方库兼容问题
  • 难以排查的全局副作用

更安全的方式是编写普通函数:

javascript 复制代码
function first(array) {
  return array[0];
}

11. 数组与集合

11.1 创建数组

javascript 复制代码
const numbers = [1, 2, 3, 4];
const empty = [];

11.2 常用操作

javascript 复制代码
const languages = ["JavaScript", "Java"];

languages.push("Python");
languages.unshift("Go");

const last = languages.pop();
const first = languages.shift();

console.log(first);
console.log(last);
console.log(languages);

11.3 map

map() 将每个元素转换为新元素:

javascript 复制代码
const numbers = [1, 2, 3];
const squares = numbers.map(number => number ** 2);

console.log(squares); // [1, 4, 9]

11.4 filter

javascript 复制代码
const users = [
  { name: "张三", active: true },
  { name: "李四", active: false },
  { name: "王五", active: true }
];

const activeUsers = users.filter(user => user.active);

console.log(activeUsers);

11.5 find 与 findIndex

javascript 复制代码
const users = [
  { id: 1, name: "张三" },
  { id: 2, name: "李四" }
];

const user = users.find(item => item.id === 2);
const index = users.findIndex(item => item.id === 2);

console.log(user);
console.log(index);

11.6 some 与 every

javascript 复制代码
const scores = [80, 90, 59];

console.log(scores.some(score => score < 60));   // true
console.log(scores.every(score => score >= 60)); // false

11.7 reduce

javascript 复制代码
const orders = [
  { amount: 100 },
  { amount: 200 },
  { amount: 300 }
];

const total = orders.reduce(
  (sum, order) => sum + order.amount,
  0
);

console.log(total); // 600

使用 reduce() 时应提供清晰的初始值。

11.8 sort

sort() 默认按照字符串顺序排序,并且会修改原数组:

javascript 复制代码
const numbers = [10, 2, 30];

numbers.sort();

console.log(numbers); // [10, 2, 30]

数字排序应传入比较函数:

javascript 复制代码
const numbers = [10, 2, 30];

numbers.sort((a, b) => a - b);

console.log(numbers); // [2, 10, 30]

不希望修改原数组时:

javascript 复制代码
const original = [3, 1, 2];
const sorted = [...original].sort((a, b) => a - b);

console.log(original); // [3, 1, 2]
console.log(sorted);   // [1, 2, 3]

支持较新数组方法的环境也可以使用 toSorted()

javascript 复制代码
const sorted = original.toSorted((a, b) => a - b);

在生产项目中使用较新 API 前,应检查目标运行环境兼容性。

11.9 Set

Set 用于保存不重复的值:

javascript 复制代码
const values = new Set([1, 2, 2, 3]);

values.add(4);
values.delete(1);

console.log(values.has(2)); // true
console.log([...values]);   // [2, 3, 4]

数组去重:

javascript 复制代码
const numbers = [1, 1, 2, 3, 3];
const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers);

11.10 Map

Map 的键可以是任意类型:

javascript 复制代码
const user = {
  id: 1
};

const cache = new Map();

cache.set(user, {
  lastLoginTime: Date.now()
});

console.log(cache.get(user));
console.log(cache.has(user));

对象通常适合表示固定结构的数据,Map 更适合频繁增删和动态键值映射。

11.11 WeakMap 与 WeakSet

WeakMap 的键必须是对象,并且不会阻止键对象被垃圾回收:

javascript 复制代码
const privateData = new WeakMap();

class User {
  constructor(name) {
    privateData.set(this, {
      name
    });
  }

  getName() {
    return privateData.get(this).name;
  }
}

WeakMapWeakSet 不可枚举,适合保存与对象生命周期关联的附加数据。


12. 字符串、日期、正则表达式与 JSON

12.1 字符串常用方法

javascript 复制代码
const text = "  JavaScript Tutorial  ";

console.log(text.trim());
console.log(text.toLowerCase());
console.log(text.toUpperCase());
console.log(text.includes("Script"));
console.log(text.startsWith("  Java"));
console.log(text.replace("Tutorial", "Guide"));

分割与拼接:

javascript 复制代码
const value = "JavaScript,Java,Python";
const languages = value.split(",");

console.log(languages);
console.log(languages.join(" | "));

12.2 Unicode 注意事项

JavaScript 字符串的 length 统计的是 UTF-16 代码单元,不一定等于用户看到的字符数:

javascript 复制代码
const emoji = "😀";

console.log(emoji.length);      // 2
console.log([...emoji].length); // 1

复杂 Emoji、组合字符和自然语言分词可能需要 Intl 相关 API 处理。

12.3 日期

javascript 复制代码
const now = new Date();

console.log(now.toISOString());
console.log(now.getFullYear());
console.log(now.getMonth() + 1);
console.log(now.getDate());

注意:

  • getMonth() 返回 011
  • ISO 时间中的 Z 表示 UTC。
  • 不带时区的日期字符串可能存在解析差异。
  • 前后端应明确时间戳单位、时区和展示规则。

格式化日期可以使用:

javascript 复制代码
const formatter = new Intl.DateTimeFormat("zh-CN", {
  dateStyle: "long",
  timeStyle: "medium",
  timeZone: "Asia/Shanghai"
});

console.log(formatter.format(new Date()));

12.4 正则表达式

验证简单格式:

javascript 复制代码
const phonePattern = /^1\d{10}$/;

console.log(phonePattern.test("13800138000"));

提取内容:

javascript 复制代码
const text = "订单号:A1001,金额:299";
const match = text.match(/订单号:([A-Z]\d+)/);

console.log(match?.[1]);

替换内容:

javascript 复制代码
const result = "hello javascript".replace(
  /javascript/i,
  "JavaScript"
);

console.log(result);

正则表达式适合模式匹配,但不应使用过于复杂的正则解析完整 HTML 或复杂编程语言语法。

12.5 JSON

对象转换为 JSON:

javascript 复制代码
const user = {
  id: 1,
  name: "张三"
};

const json = JSON.stringify(user);

console.log(json);

JSON 转换为对象:

javascript 复制代码
const parsed = JSON.parse(json);

console.log(parsed.name);

需要注意:

  • JSON 不支持函数。
  • JSON 不支持 undefined
  • JSON 不支持循环引用。
  • Date 序列化后通常变成字符串。
  • BigInt 不能直接使用默认 JSON.stringify() 序列化。
  • 外部 JSON 数据仍然是不可信输入,解析后应验证结构。

13. 解构、展开语法与可选链

13.1 数组解构

javascript 复制代码
const coordinates = [120.1, 30.2];

const [longitude, latitude] = coordinates;

console.log(longitude);
console.log(latitude);

交换变量:

javascript 复制代码
let a = 10;
let b = 20;

[a, b] = [b, a];

console.log(a, b);

13.2 对象解构

javascript 复制代码
const user = {
  id: 1,
  name: "张三",
  age: 20
};

const {
  name,
  age,
  id: userId
} = user;

console.log(name);
console.log(age);
console.log(userId);

默认值:

javascript 复制代码
const config = {
  host: "localhost"
};

const {
  host,
  port = 8080
} = config;

默认值只在属性值为 undefined 时生效,不会替换 null

13.3 展开语法

复制数组:

javascript 复制代码
const source = [1, 2, 3];
const copy = [...source];

合并数组:

javascript 复制代码
const frontend = ["HTML", "CSS"];
const programming = ["JavaScript", "TypeScript"];

const skills = [...frontend, ...programming];

合并对象:

javascript 复制代码
const defaultConfig = {
  timeout: 5000,
  retry: 2
};

const customConfig = {
  timeout: 10000
};

const finalConfig = {
  ...defaultConfig,
  ...customConfig
};

console.log(finalConfig);

后面的同名属性会覆盖前面的属性。

13.4 可选链

javascript 复制代码
const user = {
  profile: {
    address: {
      city: "上海"
    }
  }
};

console.log(user.profile?.address?.city);
console.log(user.company?.name); // undefined

调用可能不存在的方法:

javascript 复制代码
const result = user.onLogin?.();

13.5 空值合并

javascript 复制代码
const settings = {
  pageSize: 0
};

const pageSize = settings.pageSize ?? 20;

console.log(pageSize); // 0

14. this 的工作机制

this 的值通常由函数如何被调用决定,而不是简单由函数写在哪里决定。

14.1 方法调用

javascript 复制代码
const user = {
  name: "张三",
  greet() {
    console.log(this.name);
  }
};

user.greet(); // 张三

此时 this 指向点号左侧的 user

14.2 普通函数调用

在严格模式或 ES Module 中:

javascript 复制代码
function showThis() {
  console.log(this);
}

showThis(); // undefined

14.3 显式指定 this

javascript 复制代码
function greet(message) {
  console.log(`${message},${this.name}`);
}

const user = {
  name: "张三"
};

greet.call(user, "你好");
greet.apply(user, ["早上好"]);

const boundGreet = greet.bind(user);
boundGreet("欢迎");

区别:

  • call():立即调用,参数逐个传递。
  • apply():立即调用,参数通过数组传递。
  • bind():返回绑定后的新函数。

14.4 构造调用

javascript 复制代码
function User(name) {
  this.name = name;
}

const user = new User("张三");

console.log(user.name);

使用 new 时,this 指向新创建的对象。

14.5 箭头函数中的 this

箭头函数不会创建自己的 this,而是使用外层作用域的 this

javascript 复制代码
const user = {
  name: "张三",
  greetLater() {
    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
};

user.greetLater();

不要把需要动态 this 的对象方法随意写成箭头函数:

javascript 复制代码
const user = {
  name: "张三",
  greet: () => {
    console.log(this.name);
  }
};

上面的 this 通常不会指向 user

14.6 方法被单独取出

javascript 复制代码
const user = {
  name: "张三",
  greet() {
    console.log(this.name);
  }
};

const greet = user.greet;
greet();

单独调用 greet() 时,调用者信息已经丢失。可以使用:

javascript 复制代码
const greet = user.greet.bind(user);
greet();

15. 类与继承

JavaScript 的 class 是建立在原型机制之上的语法。

15.1 定义类

javascript 复制代码
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `你好,我是${this.name}`;
  }
}

const user = new User("张三", 20);

console.log(user.greet());

15.2 私有字段

javascript 复制代码
class BankAccount {
  #balance = 0;

  deposit(amount) {
    if (amount <= 0) {
      throw new RangeError("充值金额必须大于 0");
    }

    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();

account.deposit(100);
console.log(account.getBalance());

类外部不能直接访问 #balance

15.3 getter 与 setter

javascript 复制代码
class Temperature {
  #celsius = 0;

  get celsius() {
    return this.#celsius;
  }

  set celsius(value) {
    if (!Number.isFinite(value)) {
      throw new TypeError("温度必须是有效数字");
    }

    this.#celsius = value;
  }
}

15.4 静态方法

javascript 复制代码
class MathUtils {
  static sum(a, b) {
    return a + b;
  }
}

console.log(MathUtils.sum(10, 20));

静态方法属于类本身,不属于实例。

15.5 继承

javascript 复制代码
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} 发出了声音`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  speak() {
    return `${this.name} 汪汪叫`;
  }
}

const dog = new Dog("旺财", "柴犬");

console.log(dog.speak());

继承适合明确的"是一个"关系。复杂业务中,组合通常比多层继承更容易维护。


16. 迭代器与生成器

16.1 可迭代对象

数组、字符串、Set 和 Map 都是可迭代对象,可以使用 for...of

javascript 复制代码
for (const character of "JavaScript") {
  console.log(character);
}

一个对象实现 [Symbol.iterator]() 后,也可以参与迭代:

javascript 复制代码
const range = {
  start: 1,
  end: 3,

  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;

    return {
      next() {
        if (current <= end) {
          return {
            value: current++,
            done: false
          };
        }

        return {
          value: undefined,
          done: true
        };
      }
    };
  }
};

console.log([...range]); // [1, 2, 3]

16.2 生成器

生成器函数使用 function* 声明,通过 yield 逐步产生值:

javascript 复制代码
function* createRange(start, end) {
  for (let value = start; value <= end; value += 1) {
    yield value;
  }
}

for (const value of createRange(1, 3)) {
  console.log(value);
}

生成器适合:

  • 惰性序列
  • 分页数据
  • 状态机
  • 自定义迭代逻辑
  • 避免一次性创建巨大数组

17. 异常处理

17.1 try、catch 与 finally

javascript 复制代码
function parseConfig(json) {
  try {
    return JSON.parse(json);
  } catch (error) {
    console.error("配置解析失败:", error);
    return null;
  } finally {
    console.log("解析流程结束");
  }
}

finally 无论是否发生异常通常都会执行,适合释放资源或恢复状态。

17.2 抛出异常

javascript 复制代码
function withdraw(balance, amount) {
  if (!Number.isFinite(amount) || amount <= 0) {
    throw new TypeError("提现金额必须是大于 0 的有效数字");
  }

  if (amount > balance) {
    throw new RangeError("账户余额不足");
  }

  return balance - amount;
}

推荐抛出 Error 或其子类,不要直接抛出字符串:

javascript 复制代码
throw new Error("操作失败");

17.3 自定义异常

javascript 复制代码
class BusinessError extends Error {
  constructor(code, message, details = undefined) {
    super(message);

    this.name = "BusinessError";
    this.code = code;
    this.details = details;
  }
}

function createOrder(stock) {
  if (stock <= 0) {
    throw new BusinessError(
      "OUT_OF_STOCK",
      "商品库存不足",
      { stock }
    );
  }
}

17.4 不要吞掉异常

不推荐:

javascript 复制代码
try {
  riskyOperation();
} catch {
  // 什么也不做
}

至少应该记录、转换、恢复或继续抛出:

javascript 复制代码
try {
  riskyOperation();
} catch (error) {
  console.error("执行失败", error);
  throw error;
}

18. JavaScript 异步模型与事件循环

18.1 为什么需要异步

以下操作通常需要等待:

  • 网络请求
  • 文件读写
  • 数据库访问
  • 定时任务
  • 用户输入
  • 浏览器事件

如果等待期间阻塞主线程,页面可能无法响应用户操作。

JavaScript 通过宿主环境、回调队列、Promise 和事件循环协调异步操作。

18.2 调用栈

同步代码按照调用顺序进入调用栈:

javascript 复制代码
function first() {
  second();
}

function second() {
  third();
}

function third() {
  console.log("执行 third");
}

first();

调用关系:

text 复制代码
first()
└── second()
    └── third()

函数执行完成后,从调用栈依次退出。

18.3 任务与微任务

浏览器中的异步调度通常会涉及:

  • 任务:定时器、用户事件、消息等。
  • 微任务 :Promise 回调、queueMicrotask() 等。

示例:

javascript 复制代码
console.log("start");

setTimeout(() => {
  console.log("timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise");
});

queueMicrotask(() => {
  console.log("microtask");
});

console.log("end");

典型输出:

text 复制代码
start
end
promise
microtask
timeout

基本过程可以简化理解为:

  1. 执行当前同步代码。
  2. 当前调用栈清空。
  3. 执行已经排队的微任务。
  4. 进入下一轮事件循环,选择可执行任务。
  5. 浏览器在合适时机进行渲染。

事件循环的具体细节由宿主环境决定。浏览器与 Node.js 的调度阶段并不完全相同。

18.4 setTimeout 并不保证精确时间

javascript 复制代码
setTimeout(() => {
  console.log("至少等待约 1000 毫秒后进入可执行队列");
}, 1000);

1000 毫秒表示达到最小等待条件后进入调度,并不保证回调在第 1000 毫秒精确执行。

如果主线程正忙,回调会继续等待。

18.5 避免阻塞主线程

javascript 复制代码
const start = Date.now();

while (Date.now() - start < 5000) {
  // 阻塞主线程约 5 秒
}

这段代码可能导致:

  • 页面无法点击
  • 动画卡顿
  • 输入无响应
  • 定时器延迟
  • 渲染停止

CPU 密集型任务可以考虑:

  • 拆分为小任务
  • 优化算法
  • 使用 Web Worker
  • 在 Node.js 中使用 Worker Threads
  • 将重计算交给服务端或专用服务

19. Promise 与 async/await

19.1 Promise 的状态

Promise 有三种状态:

  • pending:等待中
  • fulfilled:已成功
  • rejected:已失败

状态一旦从 pending 变为成功或失败,就不会再次改变。

19.2 创建 Promise

javascript 复制代码
function delay(milliseconds) {
  return new Promise(resolve => {
    setTimeout(resolve, milliseconds);
  });
}

delay(1000).then(() => {
  console.log("等待完成");
});

19.3 then、catch 与 finally

javascript 复制代码
fetchUser()
  .then(user => {
    console.log(user);
    return user.id;
  })
  .then(userId => {
    console.log("用户 ID:", userId);
  })
  .catch(error => {
    console.error("操作失败:", error);
  })
  .finally(() => {
    console.log("流程结束");
  });

每次调用 then()catch()finally(),通常都会返回一个新的 Promise,因此可以形成链式调用。

19.4 async/await

async/await 是建立在 Promise 之上的语法:

javascript 复制代码
async function loadUser() {
  try {
    const user = await fetchUser();

    console.log(user);
    return user;
  } catch (error) {
    console.error("加载用户失败:", error);
    throw error;
  }
}

async 函数总是返回 Promise:

javascript 复制代码
async function getValue() {
  return 100;
}

getValue().then(value => {
  console.log(value);
});

19.5 串行与并行

串行执行:

javascript 复制代码
const user = await loadUser();
const orders = await loadOrders();

如果两个操作没有依赖关系,可以并行启动:

javascript 复制代码
const [user, orders] = await Promise.all([
  loadUser(),
  loadOrders()
]);

错误地先后 await 独立任务,会增加总等待时间。

19.6 Promise.all

全部成功时返回结果数组,只要有一个失败就会整体拒绝:

javascript 复制代码
const [users, products, orders] = await Promise.all([
  loadUsers(),
  loadProducts(),
  loadOrders()
]);

Promise.all() 失败后,其他已经启动的任务不会自动取消。

19.7 Promise.allSettled

需要收集每个任务结果时:

javascript 复制代码
const results = await Promise.allSettled([
  loadUsers(),
  loadProducts(),
  loadOrders()
]);

for (const result of results) {
  if (result.status === "fulfilled") {
    console.log("成功:", result.value);
  } else {
    console.error("失败:", result.reason);
  }
}

19.8 Promise.race 与 Promise.any

Promise.race() 返回第一个完成的结果,无论成功还是失败:

javascript 复制代码
const result = await Promise.race([
  requestFromServerA(),
  requestFromServerB()
]);

Promise.any() 返回第一个成功的结果。如果全部失败,则拒绝并产生 AggregateError

javascript 复制代码
const result = await Promise.any([
  requestFromServerA(),
  requestFromServerB()
]);

19.9 不要在 forEach 中直接 await

错误示例:

javascript 复制代码
users.forEach(async user => {
  await saveUser(user);
});

console.log("保存结束");

forEach() 不会等待异步回调完成。

串行处理:

javascript 复制代码
for (const user of users) {
  await saveUser(user);
}

并行处理:

javascript 复制代码
await Promise.all(
  users.map(user => saveUser(user))
);

并发量较大时,应使用并发限制,而不是一次启动成千上万个请求。


20. 浏览器 DOM 操作

DOM 是浏览器将 HTML 文档表示为对象树的方式。

20.1 查询元素

javascript 复制代码
const title = document.querySelector("#title");
const buttons = document.querySelectorAll(".button");

querySelector() 返回第一个匹配元素,找不到时返回 null

javascript 复制代码
const title = document.querySelector("#title");

if (!title) {
  throw new Error("没有找到标题元素");
}

20.2 修改文本和属性

javascript 复制代码
const title = document.querySelector("#title");

title.textContent = "新的标题";
title.classList.add("active");
title.setAttribute("data-status", "ready");

操作表单元素:

javascript 复制代码
const input = document.querySelector("#username");

console.log(input.value);
input.value = "张三";

20.3 创建元素

javascript 复制代码
const list = document.querySelector("#user-list");

const item = document.createElement("li");
item.textContent = "张三";
item.dataset.userId = "1001";

list.append(item);

20.4 批量创建元素

javascript 复制代码
const users = ["张三", "李四", "王五"];
const fragment = document.createDocumentFragment();

for (const user of users) {
  const item = document.createElement("li");
  item.textContent = user;
  fragment.append(item);
}

document.querySelector("#user-list").append(fragment);

使用 DocumentFragment 可以减少反复插入 DOM 的次数。

20.5 textContent 与 innerHTML

处理不可信文本时,优先使用:

javascript 复制代码
element.textContent = userInput;

下面的方式可能产生 XSS 风险:

javascript 复制代码
element.innerHTML = userInput;

如果业务必须渲染 HTML,应使用经过验证的 HTML 清理方案,并配置适当的内容安全策略。


21. 浏览器事件系统

21.1 监听事件

javascript 复制代码
const button = document.querySelector("#submit-button");

button.addEventListener("click", event => {
  console.log("按钮被点击", event);
});

21.2 阻止默认行为

javascript 复制代码
const form = document.querySelector("#login-form");

form.addEventListener("submit", event => {
  event.preventDefault();

  console.log("执行自定义提交逻辑");
});

21.3 事件传播

浏览器事件通常经历:

  1. 捕获阶段
  2. 到达目标
  3. 冒泡阶段
javascript 复制代码
document.body.addEventListener("click", () => {
  console.log("body");
});

document.querySelector("#button").addEventListener("click", () => {
  console.log("button");
});

默认监听器通常在冒泡阶段执行。

21.4 事件委托

如果列表有大量动态元素,可以只在父元素上注册监听器:

javascript 复制代码
const list = document.querySelector("#user-list");

list.addEventListener("click", event => {
  if (!(event.target instanceof Element)) {
    return;
  }

  const button = event.target.closest("button[data-action]");

  if (!button || !list.contains(button)) {
    return;
  }

  const action = button.dataset.action;
  const userId = button.dataset.userId;

  console.log(action, userId);
});

事件委托的优点:

  • 减少事件监听器数量
  • 动态新增元素无需重新绑定
  • 统一管理交互逻辑

21.5 移除监听器

要移除监听器,需要保留同一个函数引用:

javascript 复制代码
function handleClick() {
  console.log("clicked");
}

button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);

也可以借助 AbortController 管理监听器生命周期:

javascript 复制代码
const controller = new AbortController();

button.addEventListener(
  "click",
  () => {
    console.log("clicked");
  },
  { signal: controller.signal }
);

controller.abort();

22. Fetch 网络请求

22.1 GET 请求

javascript 复制代码
async function loadUsers() {
  const response = await fetch("/api/users");

  if (!response.ok) {
    throw new Error(`请求失败,HTTP 状态码:${response.status}`);
  }

  return response.json();
}

需要特别注意:fetch() 遇到 404500 等 HTTP 错误状态时,通常不会自动拒绝 Promise,因此必须检查 response.okresponse.status

22.2 POST 请求

javascript 复制代码
async function createUser(user) {
  const response = await fetch("/api/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(user)
  });

  if (!response.ok) {
    throw new Error(`创建用户失败:${response.status}`);
  }

  return response.json();
}

22.3 请求超时与取消

javascript 复制代码
async function fetchJson(url, timeout = 5000) {
  const controller = new AbortController();

  const timer = setTimeout(() => {
    controller.abort();
  }, timeout);

  try {
    const response = await fetch(url, {
      signal: controller.signal
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
  } finally {
    clearTimeout(timer);
  }
}

调用:

javascript 复制代码
try {
  const data = await fetchJson("/api/data", 3000);
  console.log(data);
} catch (error) {
  if (error.name === "AbortError") {
    console.error("请求已取消或超时");
  } else {
    console.error("请求失败", error);
  }
}

22.4 并非所有响应都是 JSON

javascript 复制代码
const response = await fetch("/api/report");

const contentType = response.headers.get("content-type") ?? "";

if (contentType.includes("application/json")) {
  const data = await response.json();
  console.log(data);
} else {
  const text = await response.text();
  console.log(text);
}

22.5 CORS

跨源请求受到浏览器同源策略和 CORS 机制约束。

CORS 是否允许访问主要由目标服务器的响应头决定。前端代码不能通过添加一个普通请求头来"关闭 CORS",mode: "no-cors" 也不是读取任意跨域响应的通用解决方案。


23. 浏览器存储

23.1 localStorage

javascript 复制代码
const settings = {
  theme: "dark",
  pageSize: 20
};

localStorage.setItem(
  "settings",
  JSON.stringify(settings)
);

const saved = localStorage.getItem("settings");
const parsed = saved ? JSON.parse(saved) : null;

console.log(parsed);

特点:

  • 按源隔离。
  • 数据以字符串形式保存。
  • API 是同步的。
  • 通常会长期保留,直到用户或程序删除。
  • 容量与具体浏览器策略有关。

解析外部存储数据时应处理异常:

javascript 复制代码
function loadSettings() {
  try {
    const raw = localStorage.getItem("settings");

    if (!raw) {
      return {};
    }

    const value = JSON.parse(raw);

    return value && typeof value === "object"
      ? value
      : {};
  } catch (error) {
    console.error("读取设置失败", error);
    return {};
  }
}

23.2 sessionStorage

sessionStorage 的 API 与 localStorage 类似,但生命周期通常与当前页面会话相关:

javascript 复制代码
sessionStorage.setItem("currentStep", "2");

Cookie 可以由浏览器随 HTTP 请求发送到服务器。

安全相关 Cookie 通常应由服务端设置:

  • HttpOnly
  • Secure
  • 合适的 SameSite
  • 合理的 Path 和有效期

不要把 Cookie、localStorage 或 sessionStorage 当作可信数据库。

23.4 IndexedDB

当浏览器需要保存较多结构化数据时,可以考虑 IndexedDB。

适用场景包括:

  • 离线数据
  • 大量结构化记录
  • 二进制数据
  • 客户端缓存
  • 支持索引和事务的本地存储

24. JavaScript 模块化

24.1 导出模块

math.js

javascript 复制代码
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

export const PI = 3.1415926;

24.2 导入模块

main.js

javascript 复制代码
import {
  add,
  subtract,
  PI
} from "./math.js";

console.log(add(10, 20));
console.log(subtract(20, 10));
console.log(PI);

24.3 默认导出

logger.js

javascript 复制代码
export default function log(message) {
  console.log(`[LOG] ${message}`);
}

导入:

javascript 复制代码
import log from "./logger.js";

log("应用启动");

一个模块只能有一个默认导出,但可以有多个命名导出。

24.4 重命名导入

javascript 复制代码
import {
  add as calculateTotal
} from "./math.js";

console.log(calculateTotal(10, 20));

24.5 动态导入

javascript 复制代码
async function loadEditor() {
  const module = await import("./editor.js");

  module.openEditor();
}

动态导入适合:

  • 路由懒加载
  • 按需加载大型功能
  • 降低初始资源体积
  • 根据运行条件加载模块

24.6 浏览器模块

html 复制代码
<script type="module" src="./main.js"></script>

浏览器模块具有以下特点:

  • 默认使用严格模式。
  • 支持 importexport
  • 默认延迟执行。
  • 相对导入通常需要写明文件扩展名。
  • 使用独立模块作用域,不会把顶层变量自动挂载到 window
  • 本地开发通常需要通过 HTTP 服务访问。

24.7 Node.js ES Module

package.json

json 复制代码
{
  "type": "module"
}

之后可以在 .js 文件中使用:

javascript 复制代码
import fs from "node:fs/promises";

const content = await fs.readFile("./data.txt", "utf8");

console.log(content);

Node.js 同时存在 ES Module 和 CommonJS 两种模块体系。新项目应明确选择模块类型,避免依赖模糊推断。

CommonJS 示例:

javascript 复制代码
const fs = require("node:fs");

module.exports = {
  readFile: fs.readFile
};

不要在不了解互操作规则的情况下随意混用 importexportrequire()module.exports


25. Node.js 与 npm

25.1 创建项目

bash 复制代码
mkdir javascript-demo
cd javascript-demo
npm init -y

生成的 package.json 用于描述:

  • 项目名称
  • 项目版本
  • 模块类型
  • 执行脚本
  • 生产依赖
  • 开发依赖

25.2 package.json

json 复制代码
{
  "name": "javascript-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/main.js",
    "test": "node --test"
  }
}

运行脚本:

bash 复制代码
npm run start
npm test

25.3 安装依赖

生产依赖:

bash 复制代码
npm install package-name

开发依赖:

bash 复制代码
npm install --save-dev package-name

不要盲目安装依赖。一个简单函数能解决的问题,未必需要引入大型第三方包。

25.4 锁文件

npm 通常会生成 package-lock.json

锁文件用于记录精确依赖解析结果,有助于:

  • 提高安装结果一致性
  • 追踪间接依赖
  • 在 CI 中复现依赖树

应用项目通常应将锁文件提交到版本控制。

25.5 环境变量

javascript 复制代码
const port = Number(process.env.PORT ?? 3000);
const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
  throw new Error("缺少 DATABASE_URL 环境变量");
}

环境变量的值通常是字符串或 undefined,使用前应完成类型转换和校验。

不要把密码、令牌或私钥直接写入源代码。


26. 函数式编程思想

26.1 纯函数

纯函数具有两个主要特点:

  1. 相同输入产生相同输出。
  2. 不修改函数外部状态。
javascript 复制代码
function addTax(price, taxRate) {
  return price * (1 + taxRate);
}

非纯函数示例:

javascript 复制代码
let total = 0;

function addToTotal(value) {
  total += value;
}

26.2 不可变更新

不直接修改原对象:

javascript 复制代码
const user = {
  id: 1,
  name: "张三",
  age: 20
};

const updatedUser = {
  ...user,
  age: 21
};

console.log(user.age);        // 20
console.log(updatedUser.age); // 21

更新数组:

javascript 复制代码
const users = [
  { id: 1, name: "张三" },
  { id: 2, name: "李四" }
];

const updatedUsers = users.map(user =>
  user.id === 2
    ? { ...user, name: "王五" }
    : user
);

26.3 函数组合

javascript 复制代码
const trim = value => value.trim();
const toLowerCase = value => value.toLowerCase();
const removeSpaces = value => value.replaceAll(" ", "-");

const normalizeSlug = value =>
  removeSpaces(toLowerCase(trim(value)));

console.log(normalizeSlug("  Hello JavaScript  "));

26.4 声明式处理

命令式:

javascript 复制代码
const result = [];

for (const number of numbers) {
  if (number % 2 === 0) {
    result.push(number * 2);
  }
}

声明式:

javascript 复制代码
const result = numbers
  .filter(number => number % 2 === 0)
  .map(number => number * 2);

声明式代码不一定永远更快,但通常更容易表达数据转换意图。


27. 性能优化

27.1 先测量,再优化

不要只凭感觉优化。可以使用:

  • 浏览器 Performance 面板
  • Network 面板
  • Memory 面板
  • Lighthouse
  • Node.js 性能分析工具
  • performance.now()
  • console.time()
javascript 复制代码
console.time("calculation");

performCalculation();

console.timeEnd("calculation");

27.2 选择合适的数据结构

频繁按键查询时,Map 或普通对象通常比反复遍历数组更合适:

javascript 复制代码
const users = [
  { id: 1, name: "张三" },
  { id: 2, name: "李四" }
];

const usersById = new Map(
  users.map(user => [user.id, user])
);

console.log(usersById.get(2));

27.3 减少重复计算

javascript 复制代码
function memoize(fn) {
  const cache = new Map();

  return value => {
    if (cache.has(value)) {
      return cache.get(value);
    }

    const result = fn(value);
    cache.set(value, result);

    return result;
  };
}

缓存需要控制:

  • 缓存键
  • 数据有效期
  • 最大容量
  • 失效策略
  • 对象引用与内存占用

27.4 防抖

防抖适合搜索输入、窗口尺寸变化等高频事件:

javascript 复制代码
function debounce(fn, delay) {
  let timer;

  return function (...args) {
    clearTimeout(timer);

    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

const handleSearch = debounce(keyword => {
  console.log("搜索:", keyword);
}, 300);

27.5 节流

javascript 复制代码
function throttle(fn, interval) {
  let lastExecutionTime = 0;

  return function (...args) {
    const now = Date.now();

    if (now - lastExecutionTime < interval) {
      return;
    }

    lastExecutionTime = now;
    fn.apply(this, args);
  };
}

节流适合滚动、拖动和指针移动等需要定期响应的场景。

27.6 减少 DOM 开销

建议:

  • 批量创建和插入节点。
  • 避免在循环中重复查询相同元素。
  • 使用事件委托。
  • 避免不必要的布局读取与写入交错。
  • 大列表使用分页、虚拟列表或增量渲染。

27.7 代码拆分

通过动态导入按需加载:

javascript 复制代码
button.addEventListener("click", async () => {
  const { openChart } = await import("./chart.js");

  openChart();
});

27.8 异步不等于多线程

把 CPU 密集型函数标记为 async,不会自动让它进入另一个线程:

javascript 复制代码
async function heavyCalculation() {
  // 这里仍可能阻塞当前 JavaScript 执行线程
}

真正需要并行计算时,应考虑 Worker。


28. JavaScript 安全实践

28.1 防止 XSS

危险示例:

javascript 复制代码
result.innerHTML = userInput;

安全显示纯文本:

javascript 复制代码
result.textContent = userInput;

还应结合:

  • 服务端输出编码
  • HTML 清理
  • 内容安全策略
  • 安全的模板系统
  • Cookie 安全属性

28.2 不要在前端保存真正的秘密

前端代码最终会发送到用户设备,用户可以查看:

  • JavaScript 源代码
  • 网络请求
  • 浏览器存储
  • 构建产物
  • Source Map

因此不能把以下内容作为秘密硬编码在前端:

  • 数据库密码
  • 服务端私钥
  • 管理员令牌
  • 无访问限制的第三方密钥

28.3 避免 eval

javascript 复制代码
eval(userInput);

eval() 会执行字符串代码,可能带来代码注入和维护问题。

动态数据应通过 JSON 解析:

javascript 复制代码
const data = JSON.parse(jsonText);

28.4 验证外部输入

任何外部数据都应该被视为不可信:

  • 表单输入
  • URL 参数
  • localStorage
  • Cookie
  • API 响应
  • WebSocket 消息
  • 第三方 SDK 返回值
javascript 复制代码
function validateUser(value) {
  if (!value || typeof value !== "object") {
    throw new TypeError("用户数据必须是对象");
  }

  if (typeof value.id !== "number") {
    throw new TypeError("用户 ID 必须是数字");
  }

  if (typeof value.name !== "string") {
    throw new TypeError("用户名必须是字符串");
  }

  return value;
}

前端校验主要改善用户体验,不能代替服务端权限校验和数据校验。

28.5 防范原型污染

合并不可信对象时,应限制允许的字段:

javascript 复制代码
function updateProfile(input) {
  const profile = {};

  if (typeof input.name === "string") {
    profile.name = input.name;
  }

  if (typeof input.avatar === "string") {
    profile.avatar = input.avatar;
  }

  return profile;
}

不要无条件把外部对象的所有属性复制到内部配置或全局对象。

28.6 依赖安全

建议:

  • 控制依赖数量。
  • 检查包的维护状态。
  • 提交锁文件。
  • 审查自动执行的安装脚本。
  • 定期扫描已知漏洞。
  • 不要只根据包名相似度安装依赖。
  • 升级前查看变更说明并运行测试。

29. 调试、测试与工程化

29.1 console

javascript 复制代码
console.log("普通信息");
console.info("提示信息");
console.warn("警告信息");
console.error("错误信息");

表格展示:

javascript 复制代码
console.table([
  { id: 1, name: "张三" },
  { id: 2, name: "李四" }
]);

断言:

javascript 复制代码
console.assert(
  total >= 0,
  "总金额不应该小于 0"
);

29.2 debugger

javascript 复制代码
function calculateTotal(items) {
  debugger;

  return items.reduce(
    (total, item) => total + item.price,
    0
  );
}

浏览器开发者工具打开时,代码会在 debugger 处暂停。

29.3 错误堆栈

javascript 复制代码
try {
  performOperation();
} catch (error) {
  console.error(error.message);
  console.error(error.stack);
}

排查错误时,应优先记录:

  • 错误类型
  • 错误消息
  • 调用堆栈
  • 输入参数摘要
  • 业务 ID
  • 请求 ID
  • 运行环境

记录日志时应避免泄露密码、令牌和个人敏感信息。

29.4 使用 Node.js 内置测试能力

src/sum.js

javascript 复制代码
export function sum(a, b) {
  return a + b;
}

test/sum.test.js

javascript 复制代码
import test from "node:test";
import assert from "node:assert/strict";

import { sum } from "../src/sum.js";

test("sum 应返回两个数字之和", () => {
  assert.equal(sum(10, 20), 30);
});

test("sum 应正确处理负数", () => {
  assert.equal(sum(-10, 5), -5);
});

运行:

bash 复制代码
node --test

29.5 测试应覆盖什么

常见测试层次:

  • 单元测试
  • 集成测试
  • API 测试
  • 端到端测试
  • 性能测试
  • 安全测试
  • 人工验收

重点场景包括:

  • 正常输入
  • 空值
  • 非法类型
  • 边界值
  • 重复提交
  • 并发操作
  • 网络超时
  • 接口失败
  • 权限不足
  • 异步竞争
  • 数据恢复

29.6 ESLint 与格式化工具

静态检查工具可以帮助发现:

  • 未使用变量
  • 可疑类型转换
  • 未处理 Promise
  • 不一致代码风格
  • 潜在逻辑问题

代码格式化工具则用于统一缩进、换行和标点风格。

工具不能代替代码审查和测试,但可以减少机械性错误。

29.7 JSDoc

javascript 复制代码
/**
 * 计算订单总金额。
 *
 * @param {Array<{price: number, quantity: number}>} items 订单项
 * @returns {number} 总金额
 */
function calculateTotal(items) {
  return items.reduce(
    (total, item) => total + item.price * item.quantity,
    0
  );
}

29.8 TypeScript

TypeScript 在 JavaScript 基础上增加了静态类型系统。

它适合:

  • 中大型项目
  • 多人协作项目
  • 公共组件库
  • 复杂数据模型
  • 需要大规模重构的系统

但学习 TypeScript 不能替代 JavaScript 基础。作用域、闭包、原型、this、事件循环和 Promise 等机制仍然属于 JavaScript 本身。


30. 完整实战:待办事项应用

下面使用原生 HTML、CSS 和 JavaScript 实现一个待办事项应用。

它包含:

  • 新增任务
  • 完成任务
  • 删除任务
  • 本地持久化
  • 事件委托
  • 输入校验
  • 安全的 DOM 更新

项目结构:

text 复制代码
todo-app/
├── index.html
├── style.css
└── main.js

30.1 index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JavaScript 待办事项</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <main class="todo-app">
    <h1>待办事项</h1>

    <form id="todo-form" class="todo-form">
      <label for="todo-input">任务名称</label>

      <div class="todo-form__controls">
        <input
          id="todo-input"
          name="title"
          type="text"
          maxlength="80"
          autocomplete="off"
          placeholder="请输入任务名称"
          required
        >

        <button type="submit">添加</button>
      </div>
    </form>

    <p id="todo-summary" aria-live="polite"></p>

    <ul id="todo-list" class="todo-list"></ul>
  </main>

  <script type="module" src="./main.js"></script>
</body>
</html>

30.2 style.css

css 复制代码
* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  margin: 0;
  padding: 40px 16px;
  color: #1f2937;
  background: #f3f4f6;
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
}

button,
input {
  font: inherit;
}

.todo-app {
  width: min(100%, 640px);
  margin: 0 auto;
  padding: 32px;
  border-radius: 16px;
  background: #ffffff;
  box-shadow: 0 16px 40px rgb(15 23 42 / 10%);
}

.todo-app h1 {
  margin-top: 0;
}

.todo-form label {
  display: block;
  margin-bottom: 8px;
  font-weight: 600;
}

.todo-form__controls {
  display: flex;
  gap: 12px;
}

.todo-form input {
  flex: 1;
  min-width: 0;
  padding: 12px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
}

.todo-form button,
.todo-item__delete {
  padding: 10px 16px;
  border: 0;
  border-radius: 8px;
  color: #ffffff;
  background: #2563eb;
  cursor: pointer;
}

.todo-list {
  display: grid;
  gap: 10px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
}

.todo-item__title {
  flex: 1;
  overflow-wrap: anywhere;
}

.todo-item--completed .todo-item__title {
  color: #64748b;
  text-decoration: line-through;
}

.todo-item__delete {
  background: #dc2626;
}

button:focus-visible,
input:focus-visible {
  outline: 3px solid rgb(37 99 235 / 30%);
  outline-offset: 2px;
}

30.3 main.js

javascript 复制代码
const STORAGE_KEY = "javascript-blog.todos.v1";

const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-input");
const list = document.querySelector("#todo-list");
const summary = document.querySelector("#todo-summary");

if (!form || !input || !list || !summary) {
  throw new Error("页面缺少待办事项应用所需元素");
}

let todos = loadTodos();

render();

form.addEventListener("submit", event => {
  event.preventDefault();

  const title = input.value.trim();

  if (!title) {
    input.focus();
    return;
  }

  todos = [
    {
      id: createId(),
      title,
      completed: false,
      createdAt: new Date().toISOString()
    },
    ...todos
  ];

  saveTodos();
  render();

  form.reset();
  input.focus();
});

list.addEventListener("change", event => {
  const target = event.target;

  if (
    !(target instanceof HTMLInputElement) ||
    target.dataset.action !== "toggle"
  ) {
    return;
  }

  const item = target.closest("li[data-id]");

  if (!item) {
    return;
  }

  const todoId = item.dataset.id;

  todos = todos.map(todo =>
    todo.id === todoId
      ? {
          ...todo,
          completed: target.checked
        }
      : todo
  );

  saveTodos();
  render();
});

list.addEventListener("click", event => {
  if (!(event.target instanceof Element)) {
    return;
  }

  const button = event.target.closest(
    "button[data-action='delete']"
  );

  if (!button || !list.contains(button)) {
    return;
  }

  const item = button.closest("li[data-id]");

  if (!item) {
    return;
  }

  const todoId = item.dataset.id;

  todos = todos.filter(todo => todo.id !== todoId);

  saveTodos();
  render();
});

function createId() {
  if (globalThis.crypto?.randomUUID) {
    return crypto.randomUUID();
  }

  return [
    Date.now(),
    Math.random().toString(16).slice(2)
  ].join("-");
}

function loadTodos() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);

    if (!raw) {
      return [];
    }

    const value = JSON.parse(raw);

    if (!Array.isArray(value)) {
      return [];
    }

    return value.filter(isValidTodo);
  } catch (error) {
    console.error("读取待办事项失败", error);
    return [];
  }
}

function isValidTodo(todo) {
  return (
    todo &&
    typeof todo === "object" &&
    typeof todo.id === "string" &&
    typeof todo.title === "string" &&
    typeof todo.completed === "boolean"
  );
}

function saveTodos() {
  try {
    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify(todos)
    );
  } catch (error) {
    console.error("保存待办事项失败", error);
  }
}

function render() {
  const fragment = document.createDocumentFragment();

  for (const todo of todos) {
    const item = document.createElement("li");
    item.className = "todo-item";
    item.dataset.id = todo.id;

    if (todo.completed) {
      item.classList.add("todo-item--completed");
    }

    const checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.checked = todo.completed;
    checkbox.dataset.action = "toggle";
    checkbox.setAttribute(
      "aria-label",
      `将"${todo.title}"标记为${todo.completed ? "未完成" : "已完成"}`
    );

    const title = document.createElement("span");
    title.className = "todo-item__title";
    title.textContent = todo.title;

    const deleteButton = document.createElement("button");
    deleteButton.type = "button";
    deleteButton.className = "todo-item__delete";
    deleteButton.dataset.action = "delete";
    deleteButton.textContent = "删除";
    deleteButton.setAttribute(
      "aria-label",
      `删除任务"${todo.title}"`
    );

    item.append(
      checkbox,
      title,
      deleteButton
    );

    fragment.append(item);
  }

  list.replaceChildren(fragment);

  const completedCount = todos.filter(
    todo => todo.completed
  ).length;

  summary.textContent =
    `共 ${todos.length} 项,已完成 ${completedCount} 项`;
}

30.4 项目知识点分析

这个项目使用了:

  • constlet
  • 数组和对象
  • 模板字符串
  • 函数
  • ES Module
  • DOM 查询
  • DOM 节点创建
  • textContent
  • 表单事件
  • 事件委托
  • map()filter()
  • 对象展开语法
  • localStorage
  • JSON 序列化
  • 异常处理
  • 数据结构校验
  • 可访问性属性

任务标题通过 textContent 写入页面,没有直接拼接到 innerHTML 中,因此降低了注入恶意 HTML 的风险。


31. 常见错误与避坑指南

31.1 浮点数精度

javascript 复制代码
console.log(0.1 + 0.2 === 0.3); // false

不要直接使用普通浮点数进行严格财务计算。

31.2 使用 == 导致隐式转换

javascript 复制代码
console.log("" == 0); // true

优先使用严格相等:

javascript 复制代码
console.log("" === 0); // false

31.3 把空数组和空对象当作 false

javascript 复制代码
if ([]) {
  console.log("空数组也是 truthy");
}

if ({}) {
  console.log("空对象也是 truthy");
}

判断数组是否为空:

javascript 复制代码
if (array.length === 0) {
  console.log("数组为空");
}

31.4 展开语法不是深拷贝

javascript 复制代码
const source = {
  profile: {
    city: "上海"
  }
};

const copy = {
  ...source
};

copy.profile.city = "北京";

console.log(source.profile.city); // 北京

31.5 Array.fill 共享对象

javascript 复制代码
const rows = Array(3).fill({ count: 0 });

rows[0].count = 10;

console.log(rows);

三个位置引用的是同一个对象。

正确创建独立对象:

javascript 复制代码
const rows = Array.from(
  { length: 3 },
  () => ({ count: 0 })
);

31.6 sort 修改原数组

javascript 复制代码
const source = [3, 1, 2];
const sorted = source.sort((a, b) => a - b);

console.log(source); // 已经被修改

可以先复制:

javascript 复制代码
const sorted = [...source].sort((a, b) => a - b);

31.7 forEach 不等待异步函数

javascript 复制代码
items.forEach(async item => {
  await processItem(item);
});

需要等待时使用 for...ofPromise.all()

31.8 return 后错误换行

javascript 复制代码
function createUser() {
  return
  {
    name: "张三"
  };
}

由于自动分号插入,实际相当于:

javascript 复制代码
function createUser() {
  return;
}

应该写成:

javascript 复制代码
function createUser() {
  return {
    name: "张三"
  };
}

31.9 丢失 this

javascript 复制代码
const user = {
  name: "张三",
  greet() {
    console.log(this.name);
  }
};

setTimeout(user.greet, 100);

可以显式绑定:

javascript 复制代码
setTimeout(user.greet.bind(user), 100);

31.10 fetch 不自动处理 HTTP 错误

javascript 复制代码
const response = await fetch("/api/users");

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

不要认为进入 then() 就一定代表业务请求成功。

31.11 忘记处理 Promise

javascript 复制代码
saveData();

如果 saveData() 返回 Promise,调用方应根据需要等待或明确处理:

javascript 复制代码
await saveData();

或者:

javascript 复制代码
saveData().catch(error => {
  console.error(error);
});

31.12 把异步误认为并行

async/await 主要改善异步流程表达方式,不会自动把 CPU 计算放到另一个线程。

31.13 忽略日期和时区

以下概念必须明确区分:

  • UTC 时间
  • 用户本地时间
  • 服务端时区
  • 数据库存储时区
  • 秒级时间戳
  • 毫秒级时间戳
  • 只表示日期、不表示时刻的业务字段

32. JavaScript 学习路线

第一阶段:语言基础

学习:

  • 变量
  • 数据类型
  • 运算符
  • 条件判断
  • 循环
  • 函数
  • 数组
  • 对象

练习:

  • 计算器
  • 成绩统计
  • 字符串处理
  • 数组去重
  • 数据分组

第二阶段:核心机制

学习:

  • 作用域
  • 闭包
  • this
  • 原型链
  • 模块
  • 错误处理

练习:

  • 计数器
  • 缓存函数
  • 数据模型
  • 模块化工具库

第三阶段:异步编程

学习:

  • 回调函数
  • 事件循环
  • Promise
  • async/await
  • Fetch
  • 超时与取消
  • 并发控制

练习:

  • 请求用户列表
  • 并行加载数据
  • 请求重试
  • 搜索防抖
  • 分页加载

第四阶段:浏览器开发

学习:

  • HTML
  • CSS
  • DOM
  • 事件
  • 表单
  • Storage
  • 浏览器调试工具
  • 安全基础

练习:

  • 待办事项
  • 记账应用
  • Markdown 编辑器
  • 天气查询页面

第五阶段:Node.js 与工程化

学习:

  • npm
  • package.json
  • ES Module
  • 文件系统
  • HTTP 服务
  • 环境变量
  • 日志
  • 测试
  • 代码规范
  • Git

第六阶段:框架

具备 JavaScript 基础后,再选择:

  • React
  • Vue
  • Angular
  • Svelte
  • Node.js 服务端框架

不要只会调用框架 API,而不了解 JavaScript 的作用域、闭包、对象、异步和模块机制。

第七阶段:TypeScript

掌握 JavaScript 后学习:

  • 基础类型
  • 接口
  • 类型别名
  • 泛型
  • 联合类型
  • 类型收窄
  • 工具类型
  • 声明文件

33. 常见问题

33.1 JavaScript 是单线程的吗

更准确地说,一个 JavaScript Agent 通常一次执行一个任务中的 JavaScript 代码,但宿主环境可以使用其他线程处理网络、计时器、文件 I/O、渲染或 Worker。

因此,"JavaScript 单线程"不等于整个浏览器或 Node.js 进程只有一个线程。

33.2 JavaScript 和 Java 有什么关系

二者是不同的语言。

主要区别包括:

  • 类型系统不同
  • 对象模型不同
  • 运行环境不同
  • 并发模型不同
  • 生态系统不同

名称相似不代表语言机制相同。

33.3 应该使用 var、let 还是 const

推荐:

  1. 默认使用 const
  2. 需要重新赋值时使用 let
  3. 新代码通常不使用 var

33.4 什么时候学习 TypeScript

建议先掌握:

  • 函数
  • 对象
  • 数组
  • 作用域
  • 闭包
  • 原型
  • Promise
  • 模块化

然后再学习 TypeScript。这样才能区分语言运行时问题和类型检查问题。

33.5 什么时候开始学习框架

当你能够使用原生 JavaScript 完成以下内容时,就可以开始学习框架:

  • 操作 DOM
  • 处理表单
  • 发起网络请求
  • 管理简单状态
  • 编写模块
  • 处理异步错误
  • 使用 npm
  • 调试代码

33.6 Promise 和 async/await 有什么关系

async/await 是基于 Promise 的语法。

它不会替代 Promise,而是让 Promise 链在很多场景下更接近同步代码的阅读方式。

33.7 学习 JavaScript 最重要的内容是什么

除了基础语法,最重要的核心机制包括:

  • 作用域
  • 闭包
  • 原型链
  • this
  • 值与对象引用
  • 事件循环
  • Promise
  • 模块化
  • 运行环境差异

理解这些内容后,学习前端框架和 Node.js 都会更加顺利。


34. 总结

JavaScript 已经从一种网页脚本语言发展为通用编程语言。它既可以处理网页交互,也可以开发服务端程序、桌面应用、命令行工具和自动化系统。

学习 JavaScript 不应该只停留在语法层面,还需要建立以下完整认识:

  1. JavaScript 核心语言与宿主 API 是不同层次。
  2. 默认使用 const,需要重新赋值时使用 let
  3. 了解原始值和对象引用的区别。
  4. 使用严格相等,减少隐式类型转换。
  5. 理解作用域、闭包、原型和 this
  6. 理解调用栈、任务、微任务和事件循环。
  7. 使用 Promise 和 async/await 管理异步流程。
  8. 网络请求必须处理 HTTP 状态、超时和异常。
  9. 外部输入、浏览器存储和接口响应都需要校验。
  10. 通过模块、测试、代码检查和持续集成提高项目质量。
  11. 在性能优化前先进行测量。
  12. 不要把前端校验当作服务端安全措施。

真正掌握 JavaScript 的关键不是记住所有 API,而是理解它的运行模型,并通过持续编写、调试和重构真实项目形成稳定的编程能力。


35. 参考资料

相关推荐
落魄大学生之流水线上谋生计1 小时前
Java锁全面指南:从基础概念到企业级应用
java·开发语言
for_ever_love__1 小时前
python基础语法学习: 闭包
开发语言·python·学习·闭包
ly76891 小时前
Python 全面入门:从核心语法到工程实践
开发语言·python
艾伦_耶格宇3 小时前
【AI】-4 OpenCode Go 接入 Obsidian 完整指南
运维·开发语言·人工智能·agent·opencode
沙盘客3 小时前
AFSIM 示例解读(03)· 六自由度飞行仿真 six_dof(下):机场运作与弹道导弹投送
java·javascript·经验分享
旋生万物3 小时前
【终极实战】用Python从零“生成“一个宇宙:螺旋干涉模型的代码实现
开发语言·前端·人工智能·react.js·php·wpf
16月6日-晴4 小时前
Java面向对象进阶—多态
java·开发语言
冯汉栩4 小时前
Swift Control RollingCountdownView(动画倒计时)
开发语言·ios·swift
lisanmengmeng4 小时前
搭建elk环境并接入frostmourne,实现监控报警效果(五)
开发语言·elk·日志·日志监控