目 录CONTENT

文章目录

javascript

FatFish1
2026-03-26 / 0 评论 / 0 点赞 / 3 阅读 / 0 字 / 正在检测是否收录...

javascript学习路线

javascript学习路线包括以下模块:

模块一:基础语法与核心概念

  • 变量声明let(可变)、const(不可变)、var(函数作用域,不推荐)

  • 数据类型:7 种原始类型(stringnumberbooleannullundefinedsymbolbigint)和对象类型

  • 类型转换:显式(Number()String())与隐式(+==)规则

  • 运算符===== 区别;短路逻辑(&&||

  • 控制结构ifswitchfor/while 循环、break/continue

  • 严格模式"use strict" 的作用

模块二:函数与作用域

  • 函数定义:函数声明(提升)与函数表达式(不提升)

  • 箭头函数:无自己的 thisarguments,不可作为构造函数

  • 参数:默认值、剩余参数(...args

  • 作用域:全局、函数、块级(let/const

  • 闭包:函数内部访问外部变量,常见场景(模块化、回调、缓存)

  • 高阶函数:函数作为参数或返回值(如 mapfilter

模块三:对象、原型与面向对象编程

  • 对象创建:字面量、工厂模式、构造函数、Object.create

  • 原型链__proto__(实例)与 prototype(构造函数)的关系

  • 继承:原型链继承(缺点)、组合继承(构造 + 原型)、寄生组合继承(最佳)

  • ES6 类classextendssuper、静态方法、私有字段(#

  • this 指向:默认绑定(全局/undefined)、隐式绑定(对象调用)、显式绑定(call/apply/bind)、new 绑定、箭头函数绑定

模块四:异步编程

  • 异步模型:单线程、事件循环、宏任务(setTimeout)与微任务(Promise.then

  • 回调地狱:问题与解决方案

  • Promise:三种状态(pending/fulfilled/rejected)、链式调用、静态方法(allraceresolvereject

  • async/await:语法糖、错误处理(try...catch

  • 异步场景:网络请求(Fetch)、定时器、事件监听

模块五:现代 JavaScript(ES6+)

  • 解构:数组/对象解构、嵌套解构、默认值

  • 扩展运算符:数组/对象展开、浅拷贝

  • 模板字符串:反引号、插值(${}

  • 模块化export/import,默认导出与命名导出

  • 可选链?.)与空值合并(??

  • 新数据结构Set(去重)、Map(键不限类型)、WeakSet/WeakMap

  • 数组方法mapfilterreduceforEachfindsome/every

模块六:DOM 与浏览器 API

  • 选择器querySelector/querySelectorAllgetElementById

  • 元素操作:创建(createElement)、插入(append/appendChild)、删除(remove)、属性(setAttribute)、类名(classList

  • 事件addEventListener、事件对象、事件冒泡/捕获、事件代理

  • BOMwindow(全局对象)、location(跳转)、history(路由)、localStorage/sessionStorage(存储)

  • Fetch APIfetch(url)、处理响应(json()text())、错误捕获

模块七:错误处理与调试

  • 错误类型ErrorSyntaxErrorTypeErrorReferenceError

  • 异常捕获try...catch...finally,可抛出自定义错误(throw new Error()

  • 调试工具:浏览器开发者工具(断点、监视、调用栈)、console 方法(logtabletime

  • 单元测试基础:了解测试框架(Jest)的断言(expect)、测试用例结构

模块八:模块化与工程化基础

  • 模块化标准:CommonJS(require/module.exports)与 ES Module(import/export)区别

  • 包管理npm initpackage.json、依赖管理(dependencies/devDependencies)、锁文件(package-lock.json

  • 构建工具:Webpack 的核心概念(入口、输出、loader、插件);Vite 的快速开发理念

  • 代码规范:ESLint 配置(rulesextends)、Prettier 格式化

模块九:深入原理与进阶主题

  • 执行上下文:变量提升、作用域链、this 绑定规则

  • 闭包与内存:闭包导致变量不释放,可能引起内存泄漏

  • 垃圾回收:标记清除算法、引用计数及其缺陷(循环引用)

  • 原型链深入FunctionObject 的关系、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 defined

let

  • 作用域块级作用域{} 内的代码块),如 iffor{} 等。

  • 提升存在“暂时性死区”(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 declared

const

  • 特点:与 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()); // undefined

null

  • 表示“空”,是一个特殊值,代表对象为空或不存在。

  • typeof null 返回 "object"(历史遗留问题)。

  • 常用于手动清空对象引用。

let obj = null;
console.log(typeof obj); // "object"

boolean

  • 只有两个值:truefalse

  • 常用于条件判断、逻辑运算。

number

  • 整数和浮点数统一用 number 表示(64 位双精度浮点数)。

  • 特殊值:Infinity-InfinityNaN(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)); // true

string

  • 表示文本,可用单引号、双引号或反引号(模板字符串)包裹。

  • 字符串是不可变的,所有操作返回新字符串。

let str1 = 'Hello';
let str2 = "World";
let str3 = `Hello ${str2}`; // 模板字符串
console.log(str3); // Hello World

symbol(ES6)

  • 创建唯一标识符,常用于对象属性键,避免冲突。

  • 每次调用 Symbol() 都返回唯一值。

  • 可添加描述信息。

let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2); // false

let obj = {};
obj[sym1] = 'value';
console.log(obj[sym1]); // value

bigint(ES2020)

  • 表示任意精度的大整数,以 n 结尾。

  • 用于超出 Number.MAX_SAFE_INTEGER(2^53-1)的整数计算。

let big = 9007199254740991n;
let another = BigInt(9007199254740991);
console.log(big + 1n); // 9007199254740992n

2.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):转为布尔值(除 false0''nullundefinedNaN 外,均为 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')); // 12

JS也支持隐式转换:

发生在运算符或上下文期望某种类型时:

  • 算术运算:+ 遇到字符串会拼接,其他运算符将值转为数字。

  • 比较:== 会进行类型转换,=== 不转换。

  • 逻辑运算:&&|| 返回原值,但会被隐式转为布尔值判断。

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 defined

js还支持定义块作用域,即在代码块中,例如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()); // 2

inner 函数引用了 outercount 变量,即使 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 Alice
  • Object.create方法有两个可选参数,第一个参数是原型对象,第二个可选参数可定义属性描述符。

  • 常用于基于原型的继承,可以实现更纯粹的委托。

属性

JS对象的成员属性有三个属性描述符:

  • enumerable:是否可枚举(for...inObject.keys 中是否出现)

  • configurable:是否可删除或修改特性

  • writable:是否可修改值(仅对数据属性)

因此访问器属性除了常见的 getset方法,还有enumerableconfigurable 方法

可以通过以下方式查看属性描述符的值

const obj = { a: 1 };
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
// { value: 1, writable: true, enumerable: true, configurable: true }

可以通过Object.definePropertyObject.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); // true

Object.prototype是最上级的,即Object是所有类的父类,因此Object.protype == null

console.log(alice.__proto__);           // Person.prototype
console.log(alice.__proto__.__proto__); // Object.prototype
console.log(alice.__proto__.__proto__.__proto__); // null

instanceof方法可以判断自身类型,也可以判断父类型,这与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是可以实现异步的,它的实现模型是同步执行+事件队列:

  1. 执行全局同步代码(调用栈中的任务)。

  2. 当遇到异步 API(如 setTimeoutPromiseajax)时,将其回调交给相应的 Web API(或 Node.js 的 libuv),之后主线程继续执行。

  3. 异步任务完成后,其回调被放入任务队列(Task Queue)。

  4. 当调用栈为空时,事件循环从任务队列中取出一个回调放入调用栈执行。

  5. 重复此过程。

宏任务与微任务

事件队列中的回调任务分为宏任务和微任务:

  • 宏任务:整体代码块、setTimeoutsetIntervalsetImmediate(Node.js)、I/O、UI 渲染等。

  • 微任务Promise.then/catch/finallyMutationObserverqueueMicrotaskprocess.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 节点:整个文档的根节点,如 document

  • Element 节点:HTML 标签,如 <div><p>

  • Text 节点:标签内的文本内容

  • Attribute 节点:元素的属性(较少直接操作)

  • Comment 节点:注释

节点关系:

  • 父节点:parentNode

  • 子节点:childNodes(包含所有节点类型)、children(仅元素节点)

  • 兄弟节点:previousSiblingnextSibling

  • 第一个/最后一个子节点:firstChildlastChild

下面是一个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):通过类名获取实时 HTMLCollection

  • getElementsByTagName(tagName):通过标签名获取实时 HTMLCollection

  • getElementsByName(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')getAttributeremoveAttribute

  • 直接访问 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);
});

事件传播分为三个阶段:

  1. 捕获阶段:从 window 到目标元素的父级。

  2. 目标阶段:到达目标元素。

  3. 冒泡阶段:从目标元素向上回到 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);
  }
});

事件处理中的常见事件类型包括:

  • 鼠标事件clickdblclickmousedownmouseupmousemovemouseentermouseleave

  • 键盘事件keydownkeyupkeypress(已废弃)

  • 表单事件submitchangeinputfocusblur

  • 窗口事件loadresizescroll

  • 剪贴板事件copycutpaste

也可以创建自定义事件:

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();                    // 退出全屏

0

评论区