JavaScript 是一门单线程、动态弱类型的脚本语言,运行在浏览器与 Node.js 等宿主环境里。本教程按"语法 → 作用域 → 函数 → 原型 → 异步 → DOM → 模块化"的顺序展开,每章都配有可运行示例,帮助你从"会写"走向"理解"。
一、语言基础与语法
JavaScript 区分大小写,语句以分号结尾(可省略但建议保留)。它有 7 种基本数据类型:string、number、boolean、null、undefined、symbol、bigint,以及引用类型 object。
类型判断
判断基本类型用 typeof,判断引用类型的具体构造用 instanceof,最稳妥的方式是 Object.prototype.toString.call()。
typeof 'mrgr' // 'string'
typeof 42 // 'number'
typeof undefined // 'undefined'
typeof null // 'object' ← 历史遗留 bug
[] instanceof Array // true
Object.prototype.toString.call(null) // '[object Null]'
提示:typeof null返回'object'是 JavaScript 早期的设计缺陷,判断 null 请直接用value === null。
二、变量与作用域
JavaScript 有三种声明方式:var(函数作用域,存在变量提升)、let(块级作用域,有暂时性死区)、const(块级作用域,不可重新赋值)。现代开发中应优先使用 const,需要变更时再用 let,几乎不再使用 var。
变量提升与暂时性死区
var 声明的变量会被提升到函数顶部,但赋值不会;let/const 同样有提升,但在声明语句执行前处于"暂时性死区"(TDZ),访问会抛出 ReferenceError。
console.log(a); // undefined(已提升,未赋值)
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
作用域链
函数在定义时就确定了它能访问的变量范围,形成"作用域链"。查找变量时沿链由内向外逐层查找,找到即停,找不到则抛出引用错误。
三、函数与闭包
JavaScript 中函数是一等公民,可以作为参数传递、作为返回值返回。函数有两种声明形式:函数声明(有提升)和函数表达式(无提升)。
闭包
闭包是"函数 + 该函数声明时所在的词法环境"的组合。它让内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
上例中,count 不会随 makeCounter 执行结束而被回收,因为它被返回的内部函数所引用。闭包常用于封装私有变量、实现模块模式与节流防抖。
- 封装私有变量:把不想暴露的状态藏在闭包里,只返回操作方法。
- 实现迭代器:利用闭包维持遍历游标。
- 注意内存:长期持有的闭包会延迟变量回收,避免不必要的引用。
四、原型链与继承
JavaScript 是基于原型的语言。每个对象都有一个隐式原型 [[Prototype]],可通过 __proto__ 或 Object.getPrototypeOf() 访问。访问对象属性时,若自身没有,会沿原型链向上查找,直到 null。
构造函数与 prototype
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + ' makes a sound');
};
const dog = new Animal('Dog');
dog.speak(); // Dog makes a sound
new 做了四件事:创建空对象、把对象的 __proto__ 指向构造函数的 prototype、把 this 绑定到新对象、若构造函数返回的是对象则用它否则返回新对象。
class 语法糖
ES6 的 class 只是构造函数的语法糖,底层仍是原型链。extends 实现继承,super 调用父类构造。
class Cat extends Animal {
constructor(name) {
super(name);
}
speak() {
console.log(this.name + ' meows');
}
}
五、异步编程
JavaScript 是单线程的,靠事件循环(Event Loop)处理异步任务。异步演进经历了"回调 → Promise → async/await"三个阶段。
Promise
Promise 是一个表示异步操作最终结果的对象,有三种状态:pending、fulfilled、rejected。状态一旦确定就不可逆。
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
id > 0 ? resolve({ id, name: 'mrgr' }) : reject(new Error('无效 id'));
}, 100);
});
}
fetchUser(1)
.then(user => console.log(user))
.catch(err => console.error(err));
async / await
async 函数总是返回 Promise,await 可以"暂停"函数执行直到 Promise 完成,让异步代码读起来像同步。
async function main() {
try {
const user = await fetchUser(1);
console.log(user);
} catch (err) {
console.error(err);
}
}
main();
要点:await只能在async函数或顶层模块中使用;并发场景请配合Promise.all,避免串行 await 拖慢性能。
六、DOM 与事件机制
DOM(文档对象模型)把 HTML 抽象为可操作的节点树。常用 API 包括 querySelector、addEventListener、createElement 等。
事件流:捕获 → 目标 → 冒泡
事件先从 window 向下传播到目标元素(捕获阶段),在目标上触发,再从目标冒泡回 window。addEventListener 第三个参数为 true 表示在捕获阶段监听,默认 false 在冒泡阶段监听。
document.querySelector('#btn').addEventListener('click', (e) => {
e.stopPropagation(); // 阻止冒泡
e.preventDefault(); // 阻止默认行为
console.log('clicked');
});
事件委托
利用事件冒泡,把子元素的事件统一绑定到父元素上,可以减少监听器数量、动态子元素也能自动生效,是列表场景的常用优化手段。
七、ES6+ 与模块化
ES6 持续迭代带来了大量实用特性,让 JavaScript 表达力更强、工程化更规范。
- 解构赋值:
const { name, age } = user快速取值。 - 展开与剩余:
...用于合并数组、复制对象、收集参数。 - 模板字符串:反引号 +
${}拼接,支持多行。 - 可选链与空值合并:
user?.name ?? '匿名'安全取值。 - 箭头函数:没有自己的
this,适合短回调。
ES Module
ES Module 是浏览器与 Node.js 都原生支持的模块标准,静态结构便于 tree-shaking。
// math.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from './math.js';
console.log(add(1, 2)); // 3
至此,JavaScript 的核心脉络就串联起来了。建议结合 实战项目 把这些概念用到一个真实页面上,理解会更扎实。遇到问题欢迎到 前端问答 提问。