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}`
← 返回帖子 更多前端内容 →