javascript学习路线
javascript学习路线包括以下模块:
模块一:基础语法与核心概念
变量声明:
let(可变)、const(不可变)、var(函数作用域,不推荐)数据类型:7 种原始类型(
string、number、boolean、null、undefined、symbol、bigint)和对象类型类型转换:显式(
Number()、String())与隐式(+、==)规则运算符:
==与===区别;短路逻辑(&&、||)控制结构:
if、switch、for/while循环、break/continue严格模式:
"use strict"的作用
模块二:函数与作用域
函数定义:函数声明(提升)与函数表达式(不提升)
箭头函数:无自己的
this、arguments,不可作为构造函数参数:默认值、剩余参数(
...args)作用域:全局、函数、块级(
let/const)闭包:函数内部访问外部变量,常见场景(模块化、回调、缓存)
高阶函数:函数作为参数或返回值(如
map、filter)
模块三:对象、原型与面向对象编程
对象创建:字面量、工厂模式、构造函数、
Object.create原型链:
__proto__(实例)与prototype(构造函数)的关系继承:原型链继承(缺点)、组合继承(构造 + 原型)、寄生组合继承(最佳)
ES6 类:
class、extends、super、静态方法、私有字段(#)this指向:默认绑定(全局/undefined)、隐式绑定(对象调用)、显式绑定(call/apply/bind)、new绑定、箭头函数绑定
模块四:异步编程
异步模型:单线程、事件循环、宏任务(
setTimeout)与微任务(Promise.then)回调地狱:问题与解决方案
Promise:三种状态(pending/fulfilled/rejected)、链式调用、静态方法(
all、race、resolve、reject)async/await:语法糖、错误处理(try...catch)异步场景:网络请求(Fetch)、定时器、事件监听
模块五:现代 JavaScript(ES6+)
解构:数组/对象解构、嵌套解构、默认值
扩展运算符:数组/对象展开、浅拷贝
模板字符串:反引号、插值(
${})模块化:
export/import,默认导出与命名导出可选链(
?.)与空值合并(??)新数据结构:
Set(去重)、Map(键不限类型)、WeakSet/WeakMap数组方法:
map、filter、reduce、forEach、find、some/every
模块六:DOM 与浏览器 API
选择器:
querySelector/querySelectorAll、getElementById等元素操作:创建(
createElement)、插入(append/appendChild)、删除(remove)、属性(setAttribute)、类名(classList)事件:
addEventListener、事件对象、事件冒泡/捕获、事件代理BOM:
window(全局对象)、location(跳转)、history(路由)、localStorage/sessionStorage(存储)Fetch API:
fetch(url)、处理响应(json()、text())、错误捕获
模块七:错误处理与调试
错误类型:
Error、SyntaxError、TypeError、ReferenceError等异常捕获:
try...catch...finally,可抛出自定义错误(throw new Error())调试工具:浏览器开发者工具(断点、监视、调用栈)、
console方法(log、table、time)单元测试基础:了解测试框架(Jest)的断言(
expect)、测试用例结构
模块八:模块化与工程化基础
模块化标准:CommonJS(
require/module.exports)与 ES Module(import/export)区别包管理:
npm init、package.json、依赖管理(dependencies/devDependencies)、锁文件(package-lock.json)构建工具:Webpack 的核心概念(入口、输出、loader、插件);Vite 的快速开发理念
代码规范:ESLint 配置(
rules、extends)、Prettier 格式化
模块九:深入原理与进阶主题
执行上下文:变量提升、作用域链、
this绑定规则闭包与内存:闭包导致变量不释放,可能引起内存泄漏
垃圾回收:标记清除算法、引用计数及其缺陷(循环引用)
原型链深入:
Function和Object的关系、instanceof原理事件循环进阶:
queueMicrotask、Node.js 中的process.nextTick与宏/微任务区别Proxy/Reflect:拦截对象操作,用于响应式原理
性能优化:防抖(debounce)、节流(throttle)、虚拟滚动、懒加载
安全:XSS(转义输入)、CSRF(token 验证)
模块十:框架与生态衔接(可选)
虚拟 DOM:概念、
diff算法基本思想(同层比较、key 的作用)JSX:语法规则、表达式嵌入、组件化思想
单文件组件(Vue):
<template>、<script>、<style>结构状态管理:React 的
useState/useReducer、Vue 的ref/reactive;Redux/Pinia 核心概念(store、action、mutation)Node.js 基础:
fs读写文件、http创建服务、Express 路由TypeScript 入门:类型注解、接口、泛型基本用法
模块一 基础语法与核心概念
变量声明 - var、let、const
var
作用域:函数作用域(在函数内声明的变量只能在函数内访问,不在任何函数内则是全局)。
提升:变量声明会被提升到作用域顶部,但赋值仍保留在原位置。
重复声明:允许在同一作用域内重复声明同一个变量。
全局变量:在全局作用域用
var声明的变量会成为window对象的属性(浏览器环境)。
console.log(a); // undefined(变量提升,但未赋值)
var a = 10;
var a = 20; // 允许重复声明
console.log(a); // 20
function test() {
var b = 30;
}
console.log(b); // ReferenceError: b is not definedlet
作用域:块级作用域(
{}内的代码块),如if、for、{}等。提升:存在“暂时性死区”(TDZ),在声明前访问会报错。
重复声明:同一作用域内不允许重复声明。
全局变量:不会成为
window的属性。
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
if (true) {
let y = 20;
}
console.log(y); // ReferenceError: y is not defined
let x = 20; // SyntaxError: Identifier 'x' has already been declaredconst
特点:与
let类似,但必须在声明时初始化,且不能重新赋值(但对于对象/数组,其内部属性可以修改)。作用域:块级作用域。
暂时性死区:与 let 相同
const PI = 3.14;
PI = 3.1415; // TypeError: Assignment to constant variable
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许修改属性
console.log(obj); // { name: 'Bob' }建议:优先使用 const,仅在确定需要重新赋值时使用 let,避免使用 var
数据类型
JS中有7中基本数据类型和一种引用类型
undefined
表示“未定义”,变量和函数声明未赋值时默认为
undefined。访问对象不存在的属性也会得到
undefined。
let a;
console.log(a); // undefined
function foo() {}
console.log(foo()); // undefinednull
表示“空”,是一个特殊值,代表对象为空或不存在。
typeof null返回"object"(历史遗留问题)。常用于手动清空对象引用。
let obj = null;
console.log(typeof obj); // "object"boolean
只有两个值:
true和false。常用于条件判断、逻辑运算。
number
整数和浮点数统一用
number表示(64 位双精度浮点数)。特殊值:
Infinity、-Infinity、NaN(Not a Number)。NaN与任何值(包括自身)都不相等,用isNaN()判断。
console.log(0.1 + 0.2); // 0.30000000000000004(浮点数精度问题)
console.log(1 / 0); // Infinity
console.log(NaN === NaN);// false
console.log(isNaN(NaN)); // truestring
表示文本,可用单引号、双引号或反引号(模板字符串)包裹。
字符串是不可变的,所有操作返回新字符串。
let str1 = 'Hello';
let str2 = "World";
let str3 = `Hello ${str2}`; // 模板字符串
console.log(str3); // Hello Worldsymbol(ES6)
创建唯一标识符,常用于对象属性键,避免冲突。
每次调用
Symbol()都返回唯一值。可添加描述信息。
let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2); // false
let obj = {};
obj[sym1] = 'value';
console.log(obj[sym1]); // valuebigint(ES2020)
表示任意精度的大整数,以
n结尾。用于超出
Number.MAX_SAFE_INTEGER(2^53-1)的整数计算。
let big = 9007199254740991n;
let another = BigInt(9007199254740991);
console.log(big + 1n); // 9007199254740992n2.8 引用类型:Object
包括普通对象、数组、函数、日期、正则等。
变量存储的是内存地址,复制时是引用传递。
let obj1 = { name: 'Alice' };
let obj2 = obj1;
obj2.name = 'Bob';
console.log(obj1.name); // Bob检测类型:
typeof对原始类型(除null)有效,对数组/对象返回"object"。Array.isArray()判断数组。instanceof判断构造函数的原型链。
运算符
算数运算符:+、-、*、/、%(取余)、**(幂,ES7)
console.log(5 % 2); // 1
console.log(2 ** 3); // 8比较运算符:返回布尔值:>、<、>=、<=、==(相等)、===(严格相等)、!=、!==
== 会进行类型转换(如 1 == '1' 为 true),通常建议使用 === 避免意外
console.log(0 == false); // true(布尔值转为数字 0)
console.log(0 === false); // false(类型不同)逻辑运算符:
&&(与):若左侧为假,返回左侧;否则返回右侧。&&的短路特性和java一样||(或):若左侧为真,返回左侧;否则返回右侧
!(非):返回布尔值的反
let x = 0;
let y = x || 10; // 0 为假,返回 10
console.log(y); // 10
let obj = null;
let val = obj && obj.prop; // 避免报错,返回 null赋值运算符:基本赋值 =,复合赋值:+=、-=、*=、/=、%=、**= 等
let a = 5;
a += 3; // 等同于 a = a + 3
console.log(a); // 8三目运算符:条件 ? 表达式1 : 表达式2
let age = 18;
let status = age >= 18 ? '成年' : '未成年';
console.log(status); // 成年位运算符:操作 32 位整数:&(与)、|(或)、^(异或)、~(非)、<<(左移)、>>(右移)、>>>(无符号右移)
console.log(5 & 1); // 1 (0101 & 0001 = 0001)流程控制
if...else、switch、for循环、while循环、do...while循环与java一致
类型转换
手动使用函数转换:
Number(value):转为数字,无法转换时返回NaN。String(value):转为字符串。Boolean(value):转为布尔值(除false、0、''、null、undefined、NaN外,均为true)。parseInt(string, radix)、parseFloat(string):解析整数/浮点数。
console.log(Number('123')); // 123
console.log(Number('abc')); // NaN
console.log(String(123)); // "123"
console.log(Boolean(0)); // false
console.log(parseInt('12.34')); // 12JS也支持隐式转换:
发生在运算符或上下文期望某种类型时:
算术运算:
+遇到字符串会拼接,其他运算符将值转为数字。比较:
==会进行类型转换,===不转换。逻辑运算:
&&、||返回原值,但会被隐式转为布尔值判断。
console.log('5' - 3); // 2(字符串转为数字)
console.log('5' + 3); // "53"(字符串拼接)
console.log('5' == 5); // true(隐式转换)
console.log('5' === 5); // false注意:尽量避免隐式转换
输入输出
console 对象 - 控制台输出
console.log():输出普通信息。console.error():输出错误信息(红色)。console.warn():输出警告。console.table():以表格形式输出数组/对象。console.time()/console.timeEnd():计时。
console.log('Hello');
console.table([{ a: 1, b: 2 }, { a: 3, b: 4 }]);
console.time('loop');
for (let i = 0; i < 1000000; i++) {}
console.timeEnd('loop');alert()方法 - 窗体输出
alert('欢迎学习 JavaScript!');prompt() - 弹出对话框
let name = prompt('请输入您的姓名:');
if (name) {
alert(`你好,${name}`);
}模块二 函数与作用域
函数定义
用function关键字定义函数
console.log(add(2, 3)); // 5,可以提前调用
function add(a, b) {
return a + b;
}必须有函数名。
函数体内可以使用
return返回值,若无return或只写return;,则返回undefined。
函数也可以赋值给变量进行调用
const subtract = function(a, b) {
return a - b;
};
console.log(subtract(5, 2)); // 3
const factorial = function fac(n) {
return n <= 1 ? 1 : n * fac(n - 1);
};
console.log(factorial(5)); // 120箭头函数(ES6)
类似java的lambda表达式
// 单参数,单表达式
const double = x => x * 2;
// 多参数,需括号
const sum = (a, b) => a + b;
// 多行语句需花括号和 return
const multiply = (a, b) => {
const result = a * b;
return result;
};
// 返回对象时,用括号包裹
const createUser = name => ({ name: name, age: 0 });注意:箭头函数不会创建自己的 this,它会从外围作用域捕获 this 值(词法作用域)。这使它在回调函数中非常方便。
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 这里的 this 指向 Timer 实例
}, 1000);
}
const timer = new Timer();参数
在参数定义时直接赋值,如果调用时未提供或传入 undefined,则使用默认值。
function greet(name = 'Guest') {
console.log(`Hello, ${name}`);
}
greet(); // Hello, Guest
greet(undefined); // Hello, Guest
greet('Alice'); // Hello, Alice可以使用前面的参数给后面的参数赋默认值
function fullName(first, last = first.toUpperCase()) {
return `${first} ${last}`;
}
console.log(fullName('John')); // John JOHN类似java,支持可变参数
function sum(base, ...numbers) {
return numbers.reduce((acc, n) => acc + n, base);
}
console.log(sum(10, 1, 2, 3)); // 16作用域(ES6)
全局作用域和函数作用域,与java类似
// 全局作用域
var globalVar = 'I am global';
function test() {
console.log(globalVar); // I am global
}
// 函数作用域
function example() {
var local = 'inside';
console.log(local);
}
example();
console.log(local); // ReferenceError: local is not definedjs还支持定义块作用域,即在代码块中,例如if-else中,这一点和java不太相同,java是默认块内就是块作用域,而JS必须显示声明是块作用域,用let和const声明
if (true) {
let blockVar = 'I am block';
const BLOCK = 'constant';
}
console.log(blockVar); // ReferenceError但如果不用let和const,用var声明,依然是全局作用域
当在某个作用域内访问变量时,JavaScript 会从当前作用域向外层作用域逐级查找,直到全局作用域,找不到则报 ReferenceError。
闭包
通常在一个函数内部定义另一个函数,并返回内部函数,内部函数会保留对外部函数变量的引用。
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2inner 函数引用了 outer 的 count 变量,即使 outer 执行完毕,count 也不会被垃圾回收,因为 inner 仍在使用它。
闭包在JS中的作用有:
模拟java中的私有成员变量
function createPerson(name) {
let _name = name;
return {
getName: () => _name,
setName: newName => { _name = newName; }
};
}
const p = createPerson('Alice');
console.log(p.getName()); // Alice以inner函数作为函数工厂,生产不同的outer函数
function multiplier(factor) {
return number => number * factor;
}
const double = multiplier(2);
console.log(double(5)); // 10代替let实现块作用域
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3 3 3
}
// 使用闭包解决
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// 或使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0 1 2
}高阶函数
与java类似,一个函数的参数或返回值是函数的时候,就叫高阶函数,JS中常见的高阶函数与java中很相似
forEach:遍历数组,无返回值。map:变换数组元素,返回新数组。filter:筛选元素,返回新数组。reduce:累积计算。sort:接受比较函数自定义排序。setTimeout/setInterval:接受回调函数。
模块三 对象
原型对象
JS中存在原型对象,与Java中的Object对象声明方式不同,使用Object.create
const proto = {
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const alice = Object.create(proto);
alice.name = 'Alice';
alice.age = 25;
alice.greet(); // Hello, I'm AliceObject.create方法有两个可选参数,第一个参数是原型对象,第二个可选参数可定义属性描述符。常用于基于原型的继承,可以实现更纯粹的委托。
属性
JS对象的成员属性有三个属性描述符:
enumerable:是否可枚举(for...in或Object.keys中是否出现)configurable:是否可删除或修改特性writable:是否可修改值(仅对数据属性)
因此访问器属性除了常见的 get、set方法,还有enumerable、configurable 方法
可以通过以下方式查看属性描述符的值
const obj = { a: 1 };
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
// { value: 1, writable: true, enumerable: true, configurable: true }可以通过Object.defineProperty和Object.defineProperties方法为一个对象新增属性定义,这就与java有很大区别了,java是纯静态的,定义好的类就不能再变了:
const obj = {};
Object.defineProperty(obj, 'a', {
value: 1,
writable: false,
enumerable: true,
configurable: true
});
obj.a = 2; // 静默失败(非严格模式),严格模式报错
console.log(obj.a); // 1
Object.defineProperties(obj, {
b: { value: 2, writable: true },
c: { value: 3 }
});还可以定义属性有哪些访问器
const person = {
firstName: 'John',
lastName: 'Doe'
};
Object.defineProperty(person, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
[this.firstName, this.lastName] = value.split(' ');
}
});
console.log(person.fullName); // John Doe
person.fullName = 'Jane Smith';
console.log(person.firstName); // Jane代理
与java的代理逻辑类似,但是功能又多一些,这主要还是因为js的对象可以新增属性
const target = { name: 'Alice' };
const handler = {
get(obj, prop) {
console.log(`读取属性 ${prop}`);
return prop in obj ? obj[prop] : 'default';
},
set(obj, prop, value) {
console.log(`设置属性 ${prop}=${value}`);
obj[prop] = value;
return true; // 表示成功
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 读取属性 name → Alice
proxy.age = 25; // 设置属性 age=25
console.log(proxy.age); // 读取属性 age → 25
console.log(proxy.unknown); // 读取属性 unknown → default继承
原型链
JS的类有一个默认属性叫原型链prototype,它会指向这个对象的原型对象,即它的父对象
当JS访问一个对象中没有的属性,会沿着prototype逐级向上找
prototype属性可通过 __proto__ 或 Object.getPrototypeOf() 访问
可以理解为:_proto_是对象的public属性,而prototype是类的public属性
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
const alice = new Person('Alice');
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // trueObject.prototype是最上级的,即Object是所有类的父类,因此Object.protype == null
console.log(alice.__proto__); // Person.prototype
console.log(alice.__proto__.__proto__); // Object.prototype
console.log(alice.__proto__.__proto__.__proto__); // nullinstanceof方法可以判断自身类型,也可以判断父类型,这与java中的instanceof差不多
ES6版本的JS引入了class类关键字、extend、super关键字、constructor构造函数,这使得JS继承写法与java面向对象更像了
但是本质上这些关键字并不是真实的代码,而是prototype的语法糖
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数,必须在使用 this 之前调用
this.grade = grade;
}
study() {
console.log(`${this.name} is studying.`);
}
greet() {
// 重写父类方法
console.log(`Hello, I'm student ${this.name}`);
}
introduce() {
super.greet(); // 调用父类方法
console.log(`I'm in grade ${this.grade}`);
}
}
const bob = new Student('Bob', 18, 12);
bob.greet(); // Hello, I'm student Bob
bob.introduce(); // Hello, I'm Bob → I'm in grade 12
console.log(bob instanceof Student); // true
console.log(bob instanceof Person); // true请注意:JS中的类并不是一个真正的类,而是一种特殊的方法,其类中的方法是定义在prototype上的
私有成员
ES2022新增了正八经的私有成员定义,使用#可以定义私有成员方法和私有成员变量,只能在类内部访问
class Wallet {
#balance = 0; // 私有字段
#log(amount) { // 私有方法
console.log(`Transaction: ${amount}`);
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#log(amount);
}
}
get balance() {
return this.#balance;
}
}
const wallet = new Wallet();
wallet.deposit(100);
console.log(wallet.balance); // 100
console.log(wallet.#balance); // SyntaxError: Private field '#balance' must be declared显式绑定
有点像java中的代理invoke
call(thisArg, arg1, arg2, ...):立即调用,参数逐个传递apply(thisArg, [argsArray]):立即调用,参数以数组形式传递bind(thisArg, arg1, arg2, ...):返回一个新函数,this永久绑定,可预设参数
function greet(age) {
console.log(`${this.name}, age ${age}`);
}
const person = { name: 'Bob' };
greet.call(person, 25); // Bob, age 25
greet.apply(person, [25]); // Bob, age 25
const boundGreet = greet.bind(person, 25);
boundGreet(); // Bob, age 25模块四 异步编程
JS的异步模型
JS是单线程语言,代码只能按顺序执行,没有真正意义上的多线程异步操作
但是JS是可以实现异步的,它的实现模型是同步执行+事件队列:
执行全局同步代码(调用栈中的任务)。
当遇到异步 API(如
setTimeout、Promise、ajax)时,将其回调交给相应的 Web API(或 Node.js 的 libuv),之后主线程继续执行。异步任务完成后,其回调被放入任务队列(Task Queue)。
当调用栈为空时,事件循环从任务队列中取出一个回调放入调用栈执行。
重复此过程。
宏任务与微任务
事件队列中的回调任务分为宏任务和微任务:
宏任务:整体代码块、
setTimeout、setInterval、setImmediate(Node.js)、I/O、UI 渲染等。微任务:
Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node.js)。
执行顺序为:每执行完一个宏任务,会清空当前所有的微任务队列,然后进行 UI 渲染(浏览器),再取出下一个宏任务执行
看以下案例:
console.log('1'); // 同步宏任务
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步宏任务
// 输出顺序:1 → 4 → 3 → 2解释:
同步代码先执行(属于第一个宏任务),打印 1、4。
同步代码结束后,检查微任务队列,执行
then回调,打印 3。微任务清空后,取出下一个宏任务(
setTimeout),打印 2。
Promise
Promise语法是ES6新增的,优化回调的一个写法
Promise的状态包括:
pending:初始状态,既不是成功也不是失败。
fulfilled:操作成功完成。
rejected:操作失败。
Promise的链式方法包括:
then(onFulfilled, onRejected):注册成功和失败的回调,返回一个新的 Promise,支持链式调用。catch(onRejected):捕获失败的场景,等价于then(null, onRejected)。finally(onFinally):无论成功或失败都会执行的回调。
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('成功的数据');
} else {
reject('失败的原因');
}
}, 1000);
});
promise
.then((data) => {
console.log(data);
return data.toUpperCase();
})
.then((upperData) => {
console.log(upperData);
throw new Error('出错了');
})
.catch((err) => {
console.error(err.message);
})
.finally(() => {
console.log('操作完成');
});这样的链式编程,写起来就很简洁
Promise还支持其他的方法包括:
Promise.resolve(value)与Promise.reject(reason):快速创建已成功或已失败的 Promise
Promise.resolve('直接成功').then(console.log);
Promise.reject('直接失败').catch(console.error);Promise.all(iterable):接收一个 Promise 数组,返回一个新的 Promise。当所有 Promise 都成功时,返回成功结果数组;若任意一个失败,则立即失败,返回失败原因
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => console.log(values)) // [1, 2, 3]
.catch(err => console.error(err));Promise.allSettled(iterable):ES2020规范,等待所有 Promise 都完成(无论成功或失败),返回每个 Promise 的状态和结果数组。
Promise.allSettled([Promise.resolve(1), Promise.reject('error')])
.then(results => console.log(results));
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' }
// ]Promise.race(iterable):ES2021规范,返回第一个完成的 Promise 的结果(无论成功或失败)。
const fast = new Promise(resolve => setTimeout(() => resolve('快'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('慢'), 200));
Promise.race([fast, slow]).then(console.log); // '快'async和await
写法上已经很类似java了,本质实际上是Promise的语法糖
async function fetchData() {
const user = await getUser(1); // 等待 getUser 完成
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
console.log(details);
}
fetchData();await会阻塞其所在的async函数,但不会阻塞主线程。不能在顶层直接使用
await(但 ES2022 允许在模块的顶层使用)。async函数返回的 Promise 在没有await的情况下也会立即执行到第一个await前。
如果需要同时发起多个独立的异步任务,应使用 Promise.all 结合 await,避免串行等待
// 错误:串行执行,总耗时 = 1s + 1s + 1s
const a = await taskA();
const b = await taskB();
const c = await taskC();
// 正确:并发执行,总耗时 ≈ max(1s, 1s, 1s)
const [a, b, c] = await Promise.all([taskA(), taskB(), taskC()]);异步场景
定时器
setTimeout(callback, delay):延迟执行一次。setInterval(callback, delay):每隔 delay 毫秒重复执行。clearTimeout/clearInterval:取消定时器。
const timer = setTimeout(() => {
console.log('1秒后执行');
}, 1000);
// 取消
clearTimeout(timer);网络请求 fetch api
现代浏览器提供的 Fetch API 返回 Promise,便于异步处理。
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.json(); // 解析 JSON,返回 Promise
})
.then(data => console.log(data))
.catch(err => console.error(err));也可以使用async和await写
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('网络请求失败');
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err);
}
}事件监听
button.addEventListener('click', () => {
console.log('点击事件');
});文件操作 Node.js
Node.js 提供了基于回调的 fs 模块,也有 Promise 版本(fs/promises)。
const fs = require('fs/promises');
async function readFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}异步调度
queueMicrotask 可以将一个函数作为微任务执行(在下一个宏任务之前)
queueMicrotask(() => {
console.log('微任务');
});
console.log('同步代码');
// 输出:同步代码 → 微任务模块六 DOM与浏览器API
DOM结构
DOM(Document Object Model)将 HTML 文档表示为一个树形结构,每个节点都是一个对象。通过 DOM API 可以动态访问和修改文档内容、结构和样式
DOM的节点包括:
Document 节点:整个文档的根节点,如
documentElement 节点:HTML 标签,如
<div>、<p>Text 节点:标签内的文本内容
Attribute 节点:元素的属性(较少直接操作)
Comment 节点:注释
节点关系:
父节点:
parentNode子节点:
childNodes(包含所有节点类型)、children(仅元素节点)兄弟节点:
previousSibling、nextSibling第一个/最后一个子节点:
firstChild、lastChild
下面是一个h5页面和获取页面节点的例子:
<div id="app">
<p class="text">Hello</p>
<!-- 注释 -->
</div>const div = document.getElementById('app');
console.log(div.childNodes); // NodeList: [text, p, text, comment, text]
console.log(div.children); // HTMLCollection: [p]
console.log(div.firstChild); // 文本节点(换行)
console.log(div.firstElementChild); // <p> 元素选择器
传统方法
getElementById(id):通过 id 获取单个元素(最快)getElementsByClassName(className):通过类名获取实时 HTMLCollectiongetElementsByTagName(tagName):通过标签名获取实时 HTMLCollectiongetElementsByName(name):通过 name 属性获取 NodeList(主要用于表单元素)
const header = document.getElementById('header');
const items = document.getElementsByClassName('item'); // 实时集合
const divs = document.getElementsByTagName('div');新方法
querySelector(selector):返回第一个匹配的 CSS 选择器的元素querySelectorAll(selector):返回所有匹配的静态 NodeList(可迭代,但非实时)
const firstBtn = document.querySelector('.btn');
const allBtns = document.querySelectorAll('.btn');
allBtns.forEach(btn => console.log(btn));特殊方法
document.documentElement:<html>元素。document.body:<body>元素。document.head:<head>元素。
元素操作
创建
document.createElement(tagName) 创建新元素
const div = document.createElement('div');
div.textContent = '新内容';插入元素
parent.appendChild(child):将 child 插入到父节点末尾。parent.insertBefore(newNode, referenceNode):在参考节点前插入。parent.append(...nodes):可同时插入多个节点或字符串(现代 API)。parent.prepend(...nodes):插入到开头。
const container = document.getElementById('container');
const newDiv = document.createElement('div');
container.append(newDiv, '文本内容');删除元素
element.remove():直接删除自身(现代)。parent.removeChild(child):父节点删除子节点。
const elem = document.querySelector('.to-remove');
elem.remove(); // 删除自身复制元素
element.cloneNode(deep):deep 为 true 时深拷贝(包含子元素),否则只拷贝自身。
const clone = document.querySelector('.card').cloneNode(true);
document.body.appendChild(clone);替换元素
parent.replaceChild(newNode, oldNode)
属性操作
标准属性:
elem.id = 'newId'、elem.className = 'class'(注意 className 是字符串,classList 更好)classList方法:add(class)、remove(class)、toggle(class)、contains(class)
自定义属性:
elem.setAttribute('data-id', '123')、getAttribute、removeAttribute直接访问 data-* 属性:
elem.dataset.id(等同于data-id)
const btn = document.querySelector('button');
btn.classList.add('active');
btn.dataset.index = 5; // 设置 data-index="5"内容操作
textContent:纯文本,设置时会转义 HTML 标签,性能好且安全。innerHTML:解析 HTML 字符串,可插入标签,但有 XSS 风险。innerText:类似 textContent,但会触发重排,且只返回可见文本。
div.textContent = '<strong>安全文本</strong>'; // 显示为字符串,不会加粗
div.innerHTML = '<strong>加粗文本</strong>'; // 显示为加粗事件处理
建议使用addEventListener绑定事件
HTML 属性:
<button onclick="handleClick()">(不推荐,分离关注点差)DOM 属性:
element.onclick = function(只能绑定一个处理函数)addEventListener:推荐,可绑定多个,支持事件捕获/冒泡配置。
const btn = document.getElementById('btn');
btn.addEventListener('click', (event) => {
console.log('点击', event);
});事件处理函数的参数 event 包含事件信息:
event.target:实际触发事件的元素。event.currentTarget:绑定事件的元素(通常与 this 一致)。event.preventDefault():阻止默认行为(如链接跳转、表单提交)。event.stopPropagation():阻止事件冒泡。event.type:事件类型。
link.addEventListener('click', (e) => {
e.preventDefault(); // 阻止跳转
e.stopPropagation(); // 阻止冒泡
console.log(e.target.href);
});事件传播分为三个阶段:
捕获阶段:从 window 到目标元素的父级。
目标阶段:到达目标元素。
冒泡阶段:从目标元素向上回到 window。
addEventListener 的第三个参数可以控制监听阶段:
false(默认):在冒泡阶段触发。true:在捕获阶段触发。
parent.addEventListener('click', () => console.log('父级捕获'), true);
child.addEventListener('click', () => console.log('子级冒泡'), false);利用事件冒泡,将事件监听绑定在父元素上,通过 event.target 判断具体子元素。常用于动态添加元素。
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('点击了列表项:', e.target.textContent);
}
});事件处理中的常见事件类型包括:
鼠标事件:
click、dblclick、mousedown、mouseup、mousemove、mouseenter、mouseleave键盘事件:
keydown、keyup、keypress(已废弃)表单事件:
submit、change、input、focus、blur窗口事件:
load、resize、scroll剪贴板事件:
copy、cut、paste
也可以创建自定义事件:
const myEvent = new CustomEvent('myEvent', { detail: { message: 'Hello' } });
element.dispatchEvent(myEvent);
element.addEventListener('myEvent', (e) => console.log(e.detail.message));BOM操作
BOM(Browser Object Model)提供了与浏览器窗口交互的对象
window对象
window对象用于操作浏览器窗口或标签页。所有全局变量都是 window 的属性(var 声明的),let/const 不会成为 window 属性。
console.log(window.innerWidth); // 视口宽度
console.log(window.innerHeight); // 视口高度
window.scrollTo(0, 0); // 滚动到顶部location对象
管理当前 URL 的信息和导航
history对象
管理浏览器历史记录栈
navigator对象
提供浏览器和系统信息
navigator.userAgent:用户代理字符串navigator.language:浏览器语言navigator.onLine:是否联网navigator.geolocation:地理位置 API
if (navigator.onLine) {
console.log('在线');
}
// 获取位置
navigator.geolocation.getCurrentPosition((pos) => {
console.log(pos.coords.latitude, pos.coords.longitude);
});存储
localStorage 与 sessionStorage对象
localStorage:持久化存储,除非手动清除,否则永久保存。同源共享。
sessionStorage:会话级存储,关闭标签页后清除。同源且同窗口共享。
cookie对象
容量小(约 4KB),每次 HTTP 请求都会携带到服务器
可设置过期时间(
expires)或max-age可设置
HttpOnly防止 JavaScript 访问(安全)通过
document.cookie读写,API 较原始
// 设置 cookie
document.cookie = "username=Alice; path=/; max-age=3600";
// 读取
console.log(document.cookie); // "username=Alice"function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}IndexedDB
浏览器端非关系型数据库,支持大量结构化数据存储,异步 API,功能强大但复杂。适用于需要存储大量数据的场景(如离线应用)
网络请求
XMLHttpRequest(XHR)
传统的 Ajax 请求方式,基于事件回调
AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写。它不是一种单一技术,而是一套结合了多种 Web 技术的编程模式,允许网页在不重新加载整个页面的情况下,与服务器进行异步数据交换,并动态更新页面部分内容
AJAX说白了就是通过浏览器内置的 XMLHttpRequest 对象(或现代的 fetch API)向服务器发送请求,不会阻塞用户操作
接收到响应后,通过 JavaScript 操作 DOM,只更新需要改变的区域,而不是整个页面
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send();优点:兼容性好。
缺点:API 较繁琐,回调嵌套复杂。
FETCH API
现代、基于 Promise 的 HTTP 请求接口
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Alice' })
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // 或 response.text()
})
.then(data => console.log(data))
.catch(err => console.error(err));FetchAPI的特点是:
默认不携带 cookie,需设置
credentials: 'include'。不会 reject 非 2xx 的 HTTP 状态码(如 404),只有网络错误才会 reject。
支持
AbortController中止请求。
其他API
requestAnimationFrame
用于实现动画,在浏览器下次重绘之前执行,比 setTimeout 更平滑且节能
IntersectionObserver
异步观察目标元素与其祖先元素或视口交叉状态的变化,常用于懒加载、无限滚动
WebSocket
全双工通信协议,适合实时应用(聊天、游戏)
const ws = new WebSocket('wss://example.com/socket');
ws.onopen = () => ws.send('Hello');
ws.onmessage = (event) => console.log('收到', event.data);
ws.onclose = () => console.log('连接关闭');拖拽API
通过 dragstart、dragend、drop 等事件实现原生拖拽
document.documentElement.requestFullscreen(); // 进入全屏
document.exitFullscreen(); // 退出全屏全屏API
document.documentElement.requestFullscreen(); // 进入全屏
document.exitFullscreen(); // 退出全屏
评论区