POST · 前端 · 文章
JavaScript 从入门到进阶
2026/11/13 · by ZHANG XING YUN · 阅读 1 次 · 4352 字 · 预计阅读 11 分钟
本文按"语法 → 对象与函数 → 异步 → 模块与浏览器 → 工程实践"的顺序整理 JavaScript 的核心知识,可作为系统学习与查漏的参考。
- 环境:Node.js 20+、现代浏览器(ES2020+)
- 示例可直接在浏览器控制台或
node中运行 - 约定:文中"现代写法"指 ES2015 之后的语法
一、语言定位与运行环境
JavaScript 是一门动态类型、单线程、基于原型的脚本语言。它有三个容易混淆的概念:
| 概念 | 含义 |
|---|---|
| ECMAScript | 语言规范,定义语法与内置对象 |
| 宿主环境 | 浏览器(DOM/BOM)或 Node(文件/网络/进程) |
| 引擎 | V8(Chrome/Node)、JavaScriptCore(Safari)、SpiderMonkey(Firefox) |
单线程指的是执行 JS 的线程只有一个,但浏览器还提供网络、定时器、渲染等其它线程,异步能力来自"事件循环 + 任务队列"(见第十一节)。
二、基础语法
2.1 变量声明
var a = 1; // 函数作用域,会提升,可重复声明
let b = 2; // 块级作用域,存在暂时性死区
const c = 3; // 块级作用域,绑定不可重新赋值(对象内容仍可修改)
| 对比 | var |
let / const |
|---|---|---|
| 作用域 | 函数 | 块 |
| 提升 | 提升且初始化为 undefined |
提升但未初始化(TDZ) |
| 重复声明 | 允许 | 报错 |
| 全局属性 | 会成为 window 属性 |
不会 |
console.log(x); // undefined
var x = 1;
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 1;
实践:默认 const,需要重新赋值时用 let,避免 var。
2.2 运算符要点
// 相等
1 == "1"; // true(隐式转换)
1 === "1"; // false(推荐)
// 特殊值
Object.is(NaN, NaN); // true
NaN === NaN; // false
Object.is(-0, 0); // false
-0 === 0; // true
// 逻辑运算返回的是操作数,不是布尔值
const name = input || "匿名"; // 空字符串、0、null 都会走默认值
const size = input ?? 10; // 只有 null/undefined 才走默认值
?? 与 || 的区别是高频错误点:|| 会把 0、""、false 也当成"空"。
2.3 可选链与空值合并
const city = user?.profile?.address?.city ?? "未知";
const length = list?.[0]?.name?.length ?? 0;
const result = obj.method?.(); // 方法存在才调用
三、类型与类型转换
3.1 七种原始类型与对象
typeof "a"; // "string"
typeof 1; // "number"
typeof 1n; // "bigint"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol(); // "symbol"
typeof null; // "object"(历史遗留 bug)
typeof {}; // "object"
typeof []; // "object"(数组需 Array.isArray)
typeof function () {}; // "function"
判断类型:
Array.isArray([]); // true
Object.prototype.toString.call([]); // "[object Array]"
Number.isInteger(1); // true
Number.isNaN(NaN); // true(比全局 isNaN 安全,不做隐式转换)
3.2 类型转换
Number("12"); // 12
Number("12px"); // NaN
parseInt("12px", 10); // 12(会截断)
parseFloat("1.5rem"); // 1.5
String(123); // "123"
Boolean(""); // false
Boolean("0"); // true(非空字符串都是 true)
Boolean([]); // true(对象都是 true)
隐式转换的经典结果:
"5" - 2; // 3
"5" + 2; // "52"
[] + []; // ""
[] + {}; // "[object Object]"
1 + true; // 2
null + 1; // 1
undefined + 1; // NaN
浮点数:
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// 金额用整数(分)或 decimal 库
Math.round(0.1 * 100 + 0.2 * 100) / 100; // 0.3
四、函数
4.1 三种写法
// 函数声明:会提升
function sum(a, b) {
return a + b;
}
// 函数表达式:不提升
const sum2 = function (a, b) {
return a + b;
};
// 箭头函数:没有自己的 this / arguments / prototype,不能 new
const sum3 = (a, b) => a + b;
4.2 参数
function createUser(name, { age = 18, role = "user" } = {}) {
return { name, age, role };
}
createUser("张三"); // { name: '张三', age: 18, role: 'user' }
createUser("李四", { age: 20 });
// 剩余参数
function sumAll(...nums) {
return nums.reduce((total, n) => total + n, 0);
}
4.3 高阶函数与柯里化
const withLogging = fn => (...args) => {
console.log("调用参数", args);
return fn(...args);
};
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(1, 2);
// 柯里化
const curry = fn => {
const arity = fn.length;
const curried = (...args) =>
args.length >= arity ? fn(...args) : (...rest) => curried(...args, ...rest);
return curried;
};
const add3 = curry((a, b, c) => a + b + c);
add3(1)(2)(3); // 6
4.4 防抖与节流
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
function throttle(fn, interval = 300) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}
区别:防抖是"停止触发后执行一次",节流是"固定频率执行"。
五、作用域、闭包与 this
5.1 作用域链
const global = "g";
function outer() {
const outerVar = "o";
function inner() {
const innerVar = "i";
// 可以访问 innerVar、outerVar、global
return [innerVar, outerVar, global].join("-");
}
return inner();
}
函数在定义时决定作用域(词法作用域),不是调用时。
5.2 闭包
function createCounter() {
let count = 0;
return {
increment() {
count += 1;
return count;
},
get value() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
counter.value; // 1
// 外部无法直接访问 count,只能通过暴露的方法
闭包用途:私有状态、函数工厂、防抖节流、模块模式。
副作用:闭包持有的变量不会被回收,滥用会造成内存增长(见第十四节)。
5.3 this 的四种绑定
const obj = {
name: "obj",
regular() {
return this.name;
},
arrow: () => {
// 箭头函数没有自己的 this,取决于外层作用域
return this;
}
};
obj.regular(); // "obj"
// 1) 默认绑定:非严格模式指向全局,严格模式 undefined
function loose() {
return this;
}
// 2) 隐式绑定:作为对象方法调用
// 3) 显式绑定:call / apply / bind
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
greet.call({ name: "张三" }, "你好");
greet.apply({ name: "张三" }, ["你好"]);
const bound = greet.bind({ name: "张三" });
// 4) new 绑定:构造函数中的 this 指向新对象
// 常见陷阱:方法作为回调传递后丢失 this
const user = {
name: "张三",
say() {
console.log(this.name);
}
};
setTimeout(user.say, 0); // undefined(非严格模式下是全局对象)
setTimeout(() => user.say(), 0); // "张三"
setTimeout(user.say.bind(user), 0); // "张三"
六、对象与原型
6.1 创建与访问
const user = {
name: "张三",
"user-id": 1,
say() {
return this.name;
}
};
user.name;
user["user-id"];
const key = "name";
user[key];
// 属性是否存在
"name" in user; // true
Object.hasOwn(user, "name"); // true(只查自身)
// 动态键
const field = "age";
const obj = { [field]: 18 };
6.2 原型链
const parent = { greet() { return "hello"; } };
const child = Object.create(parent);
child.greet(); // "hello"
// 原型链查找:child → parent → Object.prototype → null
Object.getPrototypeOf(child) === parent; // true
6.3 class
class Animal {
static kingdom = "animalia";
#secret = "private"; // 真正的私有字段
constructor(name) {
this.name = name;
}
get label() {
return `动物:${this.name}`;
}
speak() {
return `${this.name} 发出声音`;
}
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
return `${super.speak()}(汪)`;
}
}
new Dog("旺财", "柴犬").speak();
要点:
class本质是原型继承的语法糖,extends会设置原型链。#field是运行时私有,private只能是 TypeScript 的编译期约束。- 子类构造函数中必须先
super()才能使用this。
6.4 对象常用方法
Object.keys({ a: 1 }); // ["a"]
Object.values({ a: 1 }); // [1]
Object.entries({ a: 1 }); // [["a", 1]]
Object.fromEntries([["a", 1]]); // { a: 1 }
Object.assign({}, { a: 1 }, { b: 2 }); // { a: 1, b: 2 }
Object.freeze(obj); // 冻结(浅冻结)
Object.preventExtensions(obj);
structuredClone({ a: { b: 1 } }); // 深拷贝(支持 Map/Set/Date)
深拷贝注意:
structuredClone不支持函数、DOM、class 实例。- 简单场景可用
JSON.parse(JSON.stringify(obj)),但会丢失undefined、函数、Date会变字符串。
七、数组与常用方法
7.1 创建与判断
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
Array.of(1, 2, 3);
Array.isArray([]);
7.2 遍历与转换
| 方法 | 作用 | 是否改变原数组 | 返回值 |
|---|---|---|---|
forEach |
遍历 | 否 | undefined |
map |
映射 | 否 | 新数组 |
filter |
过滤 | 否 | 新数组 |
reduce |
归约 | 否 | 累积值 |
find / findIndex |
找第一个匹配 | 否 | 元素 / 索引 |
some / every |
存在 / 全部满足 | 否 | 布尔 |
sort |
排序 | 是 | 原数组 |
reverse |
反转 | 是 | 原数组 |
splice |
增删 | 是 | 被删除元素 |
slice |
截取 | 否 | 新数组 |
concat / flat |
合并 / 摊平 | 否 | 新数组 |
const users = [
{ name: "张三", age: 18 },
{ name: "李四", age: 24 }
];
users.map(u => u.name); // ["张三", "李四"]
users.filter(u => u.age >= 18);
users.reduce((sum, u) => sum + u.age, 0);
users.find(u => u.name === "李四");
users.some(u => u.age > 20);
7.3 排序陷阱
[10, 9, 1, 2].sort(); // [1, 10, 2, 9](默认按字符串比较)
[10, 9, 1, 2].sort((a, b) => a - b); // [1, 2, 9, 10]
// 对象排序
users.sort((a, b) => a.age - b.age);
// 不改原数组
[...users].sort((a, b) => a.age - b.age);
sort 会就地修改数组,在 React/Vue 状态中直接调用可能导致意外的更新行为。
7.4 其它常用
[1, [2, [3]]].flat(Infinity); // [1, 2, 3]
[1, 2].flatMap(x => [x, x * 2]); // [1, 2, 2, 4]
[1, 2, 3].at(-1); // 3
[1, 2, 3].includes(2);
Array.from(new Set([1, 1, 2])); // 去重
[...new Set([1, 1, 2])];
八、字符串、数字、日期与正则
8.1 字符串
const s = "Hello World";
s.length;
s.toLowerCase();
s.includes("World");
s.startsWith("Hello");
s.slice(0, 5);
s.split(" ");
s.replace("World", "JS"); // 只替换第一个
s.replaceAll("l", "L");
s.trim();
s.padStart(15, "-");
"a,b,,c".split(","); // ["a", "b", "", "c"]
8.2 数字
(1234.5678).toFixed(2); // "1234.57"(返回字符串)
Number(" 12 "); // 12
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.isFinite(1 / 0); // false
Number.parseFloat("1.5px"); // 1.5
超过 MAX_SAFE_INTEGER 的整数要用 BigInt:
const big = 9007199254740993n;
big + 1n;
8.3 日期
const now = new Date();
now.toISOString(); // "2026-01-13T10:00:00.000Z"(UTC)
now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
Date.now(); // 毫秒时间戳
// 解析:推荐 ISO 8601
new Date("2026-01-13T10:00:00Z");
// 注意 "2026-01-13" 会被解析为 UTC 零点,展示时可能差 8 小时
涉及复杂时区与格式化时,使用 dayjs / date-fns,不要手写补零。
8.4 正则
const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = "2026-01-13".match(re);
match[1]; // "2026"
/(?<year>\d{4})-(?<month>\d{2})/.exec("2026-01").groups; // { year: '2026', month: '01' }
"a1b2".replace(/\d/g, "#"); // "a#b#"
"a1b2".split(/\d/); // ["a", "b", ""]
/^\S+@\S+\.\S+$/.test("a@b.com"); // true(简单校验,复杂场景用库)
九、解构、展开与现代语法糖
// 对象解构(可重命名、设默认值)
const { name: userName = "匿名", age = 0 } = user;
// 数组解构
const [first, , third = 0, ...rest] = [1, 2, 3, 4, 5];
// 函数参数解构
function render({ title, list = [] }) {}
// 展开
const merged = { ...defaults, ...options };
const copy = [...list];
// 模板字符串
const text = `用户 ${user.name},年龄 ${user.age}`;
注意浅拷贝:{ ...obj } 只复制第一层,嵌套对象仍是引用。
十、异步编程
10.1 Promise
const fetchUser = id =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) resolve({ id, name: "张三" });
else reject(new Error("id 不合法"));
}, 100);
});
fetchUser(1)
.then(user => user.name)
.catch(error => console.error(error))
.finally(() => console.log("结束"));
组合方法:
| 方法 | 行为 |
|---|---|
Promise.all |
全部成功才成功,任一失败立即失败 |
Promise.allSettled |
等全部结束,返回每项的状态 |
Promise.race |
第一个结束的(成功或失败)决定结果 |
Promise.any |
第一个成功的结果;全部失败才失败 |
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
10.2 async / await
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("加载失败", error);
throw error;
}
}
要点:
await会暂停当前async函数,但不阻塞主线程。- 多个互不依赖的请求应并行:
// 串行:总耗时是两者之和
const user = await getUser();
const orders = await getOrders();
// 并行:总耗时取较慢者
const [user2, orders2] = await Promise.all([getUser(), getOrders()]);
- 循环里
await会串行执行,需要并行时用Promise.all+map:
const results = await Promise.all(ids.map(id => fetchById(id)));
10.3 并发控制
async function mapLimit(items, limit, task) {
const results = new Array(items.length);
let cursor = 0;
async function worker() {
while (cursor < items.length) {
const index = cursor++;
results[index] = await task(items[index], index);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return results;
}
10.4 错误处理
// 不要吞掉错误
try {
await doSomething();
} catch (error) {
logger.error(error);
throw error; // 让上层决定如何处理
}
// 非 Promise 的错误也要捕获
await new Promise((resolve, reject) => {
fs.readFile("a.txt", (error, data) => {
if (error) reject(error);
else resolve(data);
});
});
十一、事件循环
JS 单线程执行,任务分为:
- 同步代码:在主线程立即执行。
- 微任务:
Promise.then、queueMicrotask、MutationObserver。 - 宏任务:
setTimeout、setInterval、I/O、UI 渲染(渲染不在任务队列里,但受其影响)。
规则:每个宏任务执行完后,清空所有微任务,再进行渲染,然后取下一个宏任务。
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 输出:1 4 3 2
嵌套示例:
setTimeout(() => {
console.log("timeout");
Promise.resolve().then(() => console.log("micro-in-timeout"));
}, 0);
Promise.resolve().then(() => {
console.log("micro");
setTimeout(() => console.log("timeout-in-micro"), 0);
});
// 输出:micro → timeout → micro-in-timeout → timeout-in-micro
实际影响:
- 微任务中死循环会阻塞渲染(页面卡死)。
- 大量同步计算要用
requestIdleCallback、Web Worker 或分片处理。
十二、模块
12.1 ESM
// math.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}
export default class Calculator {}
// main.js
import Calculator, { PI, add } from "./math.js";
import * as math from "./math.js";
// 动态导入:返回 Promise,用于代码分割
const module = await import("./heavy.js");
要点:
- ESM 是静态分析,导入必须写在顶层(
import()除外)。 - 导出的是值的引用(live binding),不是快照。
- 顶层
await在 ESM 中可用。
12.2 CommonJS
// 导出
module.exports = { add };
exports.PI = 3.14;
// 导入
const { add } = require("./math");
区别:
| 维度 | ESM | CommonJS |
|---|---|---|
| 加载 | 静态、编译期 | 运行时 require |
| 导出 | live binding | 值的拷贝 |
| 顶层 await | 支持 | 不支持 |
| 浏览器 | 原生支持 | 需要打包 |
| 循环依赖 | 有明确规则 | 可能拿到未完成的对象 |
12.3 循环依赖
// a.js
import { b } from "./b.js";
export const a = "a";
console.log(b);
// b.js
import { a } from "./a.js"; // 此时 a 还未初始化
export const b = "b";
console.log(a); // 可能报错或 undefined
规避:把共享逻辑抽到第三个模块,避免双向依赖。
十三、浏览器 API 基础
13.1 DOM 操作
const el = document.querySelector("#app");
const items = document.querySelectorAll(".item"); // NodeList
el.textContent = "文本";
el.innerHTML = "<b>富文本</b>"; // 注意 XSS
el.classList.add("active");
el.setAttribute("data-id", "1");
el.dataset.id; // "1"
const div = document.createElement("div");
div.textContent = "动态节点";
el.appendChild(div);
13.2 事件
el.addEventListener("click", event => {
console.log(event.target, event.currentTarget);
});
// 事件委托:把监听挂在父元素
list.addEventListener("click", event => {
const item = event.target.closest("[data-id]");
if (!item) return;
console.log(item.dataset.id);
});
事件流:捕获(从外到内)→ 目标 → 冒泡(从内到外)。event.stopPropagation() 阻止传播,event.preventDefault() 阻止默认行为(两者不同)。
13.3 存储
localStorage.setItem("token", "abc");
localStorage.getItem("token");
sessionStorage.setItem("draft", "x");
// Cookie(会随请求发送)
document.cookie = "theme=dark; path=/; max-age=86400";
存储都只能存字符串,对象需要序列化;localStorage 同步阻塞,不适合大量数据。
13.4 fetch
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "张三" })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
要点:fetch 只在网络错误时 reject,HTTP 4xx/5xx 仍是 resolve,必须检查 response.ok;超时用 AbortController:
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timer);
}
十四、内存与性能
14.1 垃圾回收
- 主流引擎使用标记清除:从根对象出发,不可达的对象会被回收。
- 引用计数只作为补充(无法处理循环引用)。
14.2 常见泄漏
| 场景 | 原因 | 处理 |
|---|---|---|
| 定时器 | setInterval 未清理 |
组件卸载时 clearInterval |
| 事件监听 | 全局监听未解绑 | removeEventListener |
| 闭包 | 大对象被闭包长期持有 | 用完置 null 或缩小闭包范围 |
| 缓存 | Map 无限增长 | 设置上限或 TTL |
| DOM 引用 | 已移除的节点仍被变量引用 | 置 null |
14.3 性能原则
- 减少重排:批量改样式,避免读写交替(
offsetHeight会触发同步布局)。 - 长列表用虚拟滚动。
- 大计算放 Web Worker。
- 频繁触发的回调做防抖/节流。
- 用
requestAnimationFrame做动画,而不是setInterval。
十五、常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
[1, 10, 2].sort() 结果不对 |
默认按字符串排序 | 传比较函数 |
0.1 + 0.2 !== 0.3 |
浮点精度 | 整数运算或 decimal 库 |
NaN === NaN 为 false |
IEEE 754 规定 | 用 Number.isNaN |
typeof null === "object" |
历史遗留 | 用 value === null |
this 为 undefined |
方法被当作回调传递 | 箭头函数或 bind |
| 数组遍历中修改数组 | forEach 的行为不可依赖 |
用 map/filter 生成新数组 |
JSON.parse 报错 |
数据不是合法 JSON | 先 try/catch,校验来源 |
await 在 forEach 中无效 |
forEach 不处理返回值 |
用 for...of 或 Promise.all(map) |
| 对象比较永远为 false | 比较的是引用 | 用 JSON.stringify 或深比较库 |
parseInt("08") 旧环境问题 |
八进制解析 | 始终传第二个参数 10 |
十六、FAQ
Q1:let、const、var 怎么选?
默认 const;需要重新赋值用 let;不要用 var。
Q2:箭头函数能替代普通函数吗?
不能。箭头函数没有自己的 this、arguments,不能作为构造函数,也不适合定义对象方法。
Q3:== 什么时候可以用?
只在 x == null 判断 null/undefined 时使用,其余一律 ===。
Q4:深拷贝怎么做?
优先 structuredClone;不支持时用库(lodash cloneDeep);JSON 方式只适合纯数据且能接受类型丢失。
Q5:Promise.all 有一个失败会怎样?
立即 reject,其它任务仍在执行但结果被丢弃。需要全部结果时用 allSettled。
Q6:为什么 await 写在循环里很慢?
每次迭代都等待上一次完成。需要并行时用 Promise.all(items.map(...))。
Q7:ESM 和 CommonJS 能混用吗?
Node 中 ESM 可以 import CJS(默认导出为 module.exports),反之只能 import() 动态引入。
Q8:如何判断一个值是数组?
Array.isArray(value),不要用 typeof 或 instanceof(跨 iframe 会失效)。
Q9:事件循环中微任务和宏任务谁先执行?
当前宏任务结束后先清空微任务队列,再执行下一个宏任务。
Q10:localStorage 有什么限制?
只能存字符串、同步阻塞、容量约 5MB,且同源共享;敏感数据不要放。
附录:语法速查
// 声明
const a = 1;
let b = 2;
// 类型
typeof value;
Array.isArray(value);
Number.isNaN(value);
Object.prototype.toString.call(value);
// 对象
{ ...obj }, Object.keys/values/entries/fromEntries/assign/freeze
structuredClone(obj);
// 数组
map/filter/reduce/find/findIndex/some/every
sort((a,b)=>a-b), slice, splice, flat, flatMap, at, includes
Array.from, Array.of
// 函数
const fn = (a = 1, ...rest) => a + rest.length;
fn.call(thisArg, ...args), fn.apply(thisArg, args), fn.bind(thisArg)
// 类
class A { #p = 1; static s = 2; get v() {} set v(x) {} }
class B extends A { constructor() { super(); } }
// 异步
Promise.resolve/all/allSettled/race/any
await / async function
new AbortController().abort()
// 模块
export / export default / import / import()
require / module.exports
// 现代语法
obj?.a?.b ?? fallback
const [x, , y = 0] = arr
const { a: alias = 1 } = obj
`${a}-${b}`