一、入门基础(深度版)

1.1 JavaScript 核心本质

1.2 语法基础(补充细节)

1.2.1 标识符与命名规范

1.2.2 严格模式(Strict Mode)

强制规范代码,避免静默错误,推荐在项目中使用:
javascript
 
运行
 
 
 
 
// 全局严格模式(文件开头)
"use strict";

// 函数内严格模式(局部)
function fn() {
  "use strict";
  // 严格模式下的规则:
  // 1. 变量必须声明后使用(未声明赋值会报错,非严格模式会创建全局变量)
  // 2. 禁止删除变量/函数(delete a 报错)
  // 3. this 不能指向全局对象(全局作用域 this 为 undefined)
  // 4. 禁止重复参数(function(a,a){} 报错)
}
 

1.2.3 变量提升(Hoisting)深度解析

javascript
 
运行
 
 
 
 
// 1. var 提升:声明提升,赋值不提升
console.log(a); // undefined(提升了声明,未提升赋值)
var a = 10;

// 等价于:
var a;
console.log(a);
a = 10;

// 2. 函数声明提升:整个函数(声明+实现)都提升
console.log(fn1()); // "hello"
function fn1() {
  return "hello";
}

// 3. 函数表达式(let/const)无提升,存在「暂时性死区(TDZ)」
console.log(fn2); // 报错:Cannot access 'fn2' before initialization
let fn2 = function() {
  return "world";
};
 

1.2.4 数据类型(补充细节)

基本类型 vs 引用类型核心区别
特性 基本类型 引用类型
存储位置 栈内存(Stack) 堆内存(Heap)+ 栈存引用
赋值方式 值拷贝(独立) 引用拷贝(指向同一内存)
比较方式 比较值 比较引用(地址)
可变性 不可变(重新赋值≠修改) 可变(可修改属性 / 元素)
示例:
javascript
 
运行
 
 
 
 
// 基本类型:值拷贝,互不影响
let a = 10;
let b = a;
b = 20;
console.log(a); // 10(a 不受 b 影响)

// 引用类型:引用拷贝,修改一个影响另一个
let obj1 = { name: "JS" };
let obj2 = obj1;
obj2.name = "JavaScript";
console.log(obj1.name); // JavaScript(指向同一堆内存)

// 基本类型不可变:看似修改,实际是创建新值
let str = "hello";
str.toUpperCase(); // 返回 "HELLO",但原 str 不变
console.log(str); // hello
str = str.toUpperCase(); // 重新赋值(创建新字符串)
console.log(str); // HELLO
 
NaN 特殊处理
javascript
 
运行
 
 
 
 
// NaN 是 Number 类型,且不等于任何值(包括自身)
console.log(typeof NaN); // number
console.log(NaN === NaN); // false

// 正确判断 NaN 的方式
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("123")); // false(仅判断是否为 NaN)
console.log(isNaN("123")); // false(先转数字:123→非NaN)
console.log(isNaN("abc")); // true(先转数字:NaN→是NaN)
 

二、核心语法(深度版)

2.1 运算符(补充细节)

2.1.1 赋值运算符

javascript
 
运行
 
 
 
 
let a = 10;
a += 5; // 等价于 a = a + 5 → 15
a -= 3; // 等价于 a = a - 3 → 12
a *= 2; // 等价于 a = a * 2 → 24
a /= 4; // 等价于 a = a / 4 → 6
a %= 2; // 等价于 a = a % 2 → 0
 

2.1.2 逻辑运算符的「短路特性」实战

javascript
 
运行
 
 
 
 
// 1. &&:第一个为假返回第一个,否则返回第二个
let res1 = 0 && 10; // 0(假值)
let res2 = "hello" && "world"; // "world"(真值)
// 实战:仅当条件为真时执行函数
let isLogin = true;
isLogin && getUserInfo(); // 等价于 if (isLogin) { getUserInfo() }

// 2. ||:第一个为真返回第一个,否则返回第二个
let res3 = 10 || 20; // 10
let res4 = "" || "默认值"; // "默认值"
// 实战:设置默认值(ES6 前的写法)
function fn(name) {
  name = name || "游客";
  console.log(name);
}
fn(); // 游客
fn("小明"); // 小明

// 3. ??(空值合并运算符,ES2020):仅当值为 null/undefined 时返回默认值
let res5 = 0 ?? 10; // 0(区别于 ||,0 不触发默认值)
let res6 = undefined ?? 10; // 10
// 实战:避免 0/""/false 被误判为假值
let count = 0;
let showCount = count ?? "暂无数据"; // 0(而非 "暂无数据")
 

2.1.3 可选链运算符(?.,ES2020)

解决「访问嵌套对象属性时,某一层为 undefined/null 导致报错」的问题:
javascript
 
运行
 
 
 
 
let user = {
  name: "小明",
  address: {
    city: "北京"
  }
};

// 传统写法(需层层判断)
let city1 = user && user.address && user.address.city; // 北京

// 可选链写法(简洁)
let city2 = user?.address?.city; // 北京
let street = user?.address?.street; // undefined(不报错)

// 可选链 + 函数调用
let fn = user?.getInfo?.(); // 若 getInfo 不存在,返回 undefined

// 可选链 + 数组
let arr = null;
let first = arr?.[0]; // undefined(不报错)
 

2.2 流程控制(补充细节)

2.2.1 break/continue 与标签语句

javascript
 
运行
 
 
 
 
// 1. break:跳出当前循环/switch
for (let i = 0; i < 5; i++) {
  if (i === 3) break; // 当i=3时跳出循环
  console.log(i); // 0 1 2
}

// 2. continue:跳过当前循环,进入下一次
for (let i = 0; i < 5; i++) {
  if (i === 3) continue; // 跳过i=3
  console.log(i); // 0 1 2 4
}

// 3. 标签语句:跳出多层循环(实战少用,可读性差)
outer: for (let i = 0; i < 3; i++) {
  inner: for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) break outer; // 直接跳出outer循环
    console.log(`i=${i}, j=${j}`);
  }
}
 

2.2.2 条件判断的简化写法

javascript
 
运行
 
 
 
 
// 1. 三元运算符(简单条件)
let score = 85;
let level = score >= 90 ? "优秀" : score >= 80 ? "良好" : "及格";
console.log(level); // 良好

// 2. 逻辑判断简化
let isAdult = age >= 18;
if (isAdult) { /* 逻辑 */ }

// 3. 空值判断简化
if (obj && obj.name) { /* 避免 obj 为 null/undefined */ }
// 等价于
if (obj?.name) { /* 可选链 */ }
 

2.3 函数(核心进阶)

2.3.1 作用域与作用域链

示例:
javascript
 
运行
 
 
 
 
// 全局作用域
let a = 10;
function fn1() {
  // 函数作用域
  let b = 20;
  function fn2() {
    // 函数作用域
    let c = 30;
    console.log(a + b + c); // 60(向上查找 a、b)
  }
  fn2();
  // console.log(c); // 报错:c is not defined(作用域链不向下)
}
fn1();
 

2.3.2 闭包(Closure)—— JS 核心难点

定义
闭包是「有权访问另一个函数作用域中变量的函数」,本质是函数执行后,其作用域未被销毁,被内部函数引用。
核心示例
javascript
 
运行
 
 
 
 
function outer() {
  let count = 0; // 外部函数变量,本应执行后销毁
  // 内部函数引用外部变量,形成闭包
  return function inner() {
    count++;
    console.log(count);
  };
}

let fn = outer();
fn(); // 1(count 未销毁)
fn(); // 2(count 持续累加)
fn(); // 3
 
闭包的应用场景
  1. 封装私有变量(模拟私有属性):
    javascript
     
    运行
     
     
     
     
    function createPerson(name) {
      let age = 18; // 私有变量,外部无法直接访问
      return {
        getName: () => name,
        getAge: () => age,
        setAge: (newAge) => {
          if (newAge >= 0) age = newAge; // 可控的修改
        }
      };
    }
    
    let person = createPerson("小明");
    console.log(person.name); // undefined(无法直接访问)
    console.log(person.getName()); // 小明
    console.log(person.getAge()); // 18
    person.setAge(20);
    console.log(person.getAge()); // 20
    
     
     
  2. 防抖 / 节流函数(见下文「实战工具函数」)。
  3. 柯里化函数(参数复用)。
闭包的坑:内存泄漏
闭包会导致外部函数作用域不销毁,若滥用(如全局闭包),会占用内存,甚至内存泄漏。

2.3.3 this 指向(全场景解析)

this 是函数执行时的上下文,指向调用函数的对象,不同场景指向不同:
执行场景 this 指向 示例
全局作用域 / 普通函数调用 浏览器:window;Node.js:global;严格模式:undefined function fn() { console.log(this) }
对象方法调用 该对象 obj.fn() → this 指向 obj
构造函数(new) 新创建的实例对象 new Person() → this 指向实例
箭头函数 定义时的外层作用域 this(无自身 this) () => { console.log(this) }
事件绑定 触发事件的 DOM 元素 btn.addEventListener('click', fn)
call/apply/bind 手动指定的对象 fn.call(obj) → this 指向 obj
示例:
javascript
 
运行
 
 
 
 
// 1. 全局/普通函数
function fn1() {
  console.log(this); // 浏览器:window
}
fn1();

// 2. 对象方法
let obj = {
  name: "JS",
  fn2: function() {
    console.log(this.name); // JS(this 指向 obj)
  }
};
obj.fn2();

// 3. 构造函数
function Person(name) {
  this.name = name; // this 指向实例
}
let p = new Person("小明");
console.log(p.name); // 小明

// 4. 箭头函数(无自身 this)
let obj2 = {
  name: "JS",
  fn3: () => {
    console.log(this.name); // undefined(this 指向全局)
  },
  fn4: function() {
    let inner = () => {
      console.log(this.name); // JS(this 指向 obj2)
    };
    inner();
  }
};
obj2.fn3();
obj2.fn4();

// 5. 手动绑定 this(call/apply/bind)
function fn5(a, b) {
  console.log(this.name, a + b);
}
let obj3 = { name: "绑定对象" };
fn5.call(obj3, 1, 2); // 绑定对象 3(立即执行,参数逐个传)
fn5.apply(obj3, [1, 2]); // 绑定对象 3(立即执行,参数数组传)
let boundFn = fn5.bind(obj3, 1, 2); // 不立即执行,返回新函数
boundFn(); // 绑定对象 3
 

三、数组与对象(深度版)

3.1 数组(补充进阶操作)

3.1.1 数组排序

javascript
 
运行
 
 
 
 
// 1. 数字排序(默认按字符串排序,需传回调)
let arr = [10, 2, 25, 5];
// 升序
arr.sort((a, b) => a - b);
console.log(arr); // [2,5,10,25]
// 降序
arr.sort((a, b) => b - a);
console.log(arr); // [25,10,5,2]

// 2. 对象数组排序
let users = [
  { name: "小明", age: 20 },
  { name: "小红", age: 18 },
  { name: "小李", age: 22 }
];
// 按年龄升序
users.sort((a, b) => a.age - b.age);
console.log(users); // 小红、小明、小李
 

3.1.2 数组扁平化

javascript
 
运行
 
 
 
 
// 1. 多层数组扁平化(ES6+ flat)
let arr = [1, [2, [3, [4]]]];
// flat(Infinity):扁平化所有层级
let flatArr = arr.flat(Infinity);
console.log(flatArr); // [1,2,3,4]

// 2. 手动实现扁平化(递归)
function flatten(arr) {
  let result = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      result = result.concat(flatten(item)); // 递归
    } else {
      result.push(item);
    }
  });
  return result;
}
console.log(flatten(arr)); // [1,2,3,4]
 

3.1.3 数组去重

javascript
 
运行
 
 
 
 
// 1. Set 去重(ES6+,简洁)
let arr = [1, 2, 2, 3, 3, 3];
let uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1,2,3]

// 2. 手动去重(兼容性好)
function unique(arr) {
  let result = [];
  arr.forEach(item => {
    if (!result.includes(item)) {
      result.push(item);
    }
  });
  return result;
}
console.log(unique(arr)); // [1,2,3]
 

3.2 对象(补充进阶操作)

3.2.1 深浅拷贝

1. 浅拷贝(仅拷贝第一层,深层引用类型仍共享)
javascript
 
运行
 
 
 
 
// 方法1:Object.assign
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = Object.assign({}, obj1);
obj2.b.c = 3;
console.log(obj1.b.c); // 3(深层仍共享)

// 方法2:扩展运算符
let obj3 = { ...obj1 };
obj3.b.c = 4;
console.log(obj1.b.c); // 4
 
2. 深拷贝(拷贝所有层级,互不影响)
javascript
 
运行
 
 
 
 
// 方法1:JSON.parse(JSON.stringify())(简单场景,有局限)
let obj4 = { a: 1, b: { c: 2 }, fn: () => {} };
let obj5 = JSON.parse(JSON.stringify(obj4));
console.log(obj5.fn); // undefined(无法拷贝函数、Symbol、循环引用)

// 方法2:手动递归深拷贝(通用)
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") return obj; // 非对象直接返回
  if (obj instanceof Date) return new Date(obj); // 处理日期
  if (obj instanceof Array) return obj.map(item => deepClone(item)); // 处理数组
  // 处理普通对象
  let cloneObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { // 仅拷贝自身属性(排除原型链)
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}

let obj6 = { a: 1, b: { c: 2 }, fn: () => console.log("hello") };
let obj7 = deepClone(obj6);
obj7.b.c = 3;
console.log(obj6.b.c); // 2(互不影响)
console.log(obj7.fn()); // hello(函数拷贝成功)
 

3.2.2 原型与原型链(JS 继承核心)

核心概念
图解(Mermaid)

proto

proto

proto

prototype

proto

proto

实例 p

Person.prototype

Object.prototype

null

Person 函数

Function.prototype

proto

proto

proto

prototype

proto

proto

实例 p

Person.prototype

Object.prototype

null

Person 函数

Function.prototype

示例
javascript
 
运行
 
 
 
 
// 构造函数
function Person(name) {
  this.name = name;
}
// 原型上添加共享方法
Person.prototype.sayHi = function() {
  console.log(`你好,${this.name}`);
};

// 创建实例
let p = new Person("小明");
console.log(p.name); // 小明(自身属性)
p.sayHi(); // 你好,小明(原型链查找 sayHi)

// 验证原型链
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

// 继承:通过原型链实现
function Student(name, score) {
  Person.call(this, name); // 继承属性
  this.score = score;
}
// 继承方法
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student; // 修复构造函数指向
Student.prototype.showScore = function() {
  console.log(`分数:${this.score}`);
};

let stu = new Student("小红", 90);
stu.sayHi(); // 你好,小红(继承 Person 的方法)
stu.showScore(); // 分数:90
 

四、DOM/BOM 操作(深度版)

4.1 DOM 操作进阶

4.1.1 事件流与事件委托

事件流
事件触发时的传播过程,分为 3 个阶段:
  1. 捕获阶段:从 window → 文档 → 父元素 → 目标元素(默认不触发)。
  2. 目标阶段:事件到达目标元素。
  3. 冒泡阶段:从目标元素 → 父元素 → 文档 → window(默认触发)。
示例:
html
 
预览
 
 
 
 
<div id="parent">
  <button id="child">点击</button>
</div>

<script>
let parent = document.querySelector("#parent");
let child = document.querySelector("#child");

// 捕获阶段触发(第三个参数为 true)
parent.addEventListener("click", () => console.log("父元素捕获"), true);
child.addEventListener("click", () => console.log("子元素捕获"), true);

// 冒泡阶段触发(第三个参数为 false/省略)
child.addEventListener("click", () => console.log("子元素冒泡"));
parent.addEventListener("click", () => console.log("父元素冒泡"));

// 点击按钮,输出顺序:
// 父元素捕获 → 子元素捕获 → 子元素冒泡 → 父元素冒泡
</script>
 
事件委托(Event Delegation)
利用事件冒泡,将子元素的事件绑定到父元素上,减少事件绑定数量,解决动态新增元素无事件的问题。
示例:
html
 
预览
 
 
 
 
<ul id="list">
  <li>选项1</li>
  <li>选项2</li>
  <li>选项3</li>
</ul>
<button id="addBtn">新增选项</button>

<script>
let list = document.querySelector("#list");
let addBtn = document.querySelector("#addBtn");

// 事件委托:绑定到父元素
list.addEventListener("click", (e) => {
  // e.target 是触发事件的具体子元素
  if (e.target.tagName === "LI") {
    console.log("点击了:", e.target.innerText);
  }
});

// 动态新增元素(仍有事件)
addBtn.addEventListener("click", () => {
  let li = document.createElement("li");
  li.innerText = `选项${list.children.length + 1}`;
  list.appendChild(li);
});
</script>
 

4.1.2 DOM 性能优化

  1. 减少 DOM 查询:将查询结果存到变量,避免重复查询。
    javascript
     
    运行
     
     
     
     
    // 差:重复查询 DOM
    for (let i = 0; i < 10; i++) {
      document.querySelector("#box").innerText += i;
    }
    
    // 好:一次查询,多次使用
    let box = document.querySelector("#box");
    let text = "";
    for (let i = 0; i < 10; i++) {
      text += i;
    }
    box.innerText = text;
    
     
     
  2. 文档碎片(DocumentFragment):批量添加 DOM 节点,减少重排 / 重绘。
    javascript
     
    运行
     
     
     
     
    let fragment = document.createDocumentFragment();
    for (let i = 0; i < 100; i++) {
      let li = document.createElement("li");
      li.innerText = `项${i}`;
      fragment.appendChild(li); // 先添加到碎片(无重排)
    }
    list.appendChild(fragment); // 一次性添加到 DOM(仅一次重排)
    
     
     
  3. 隐藏 DOM 后操作:先设置 display: none,操作完成后恢复,减少重排。

4.2 BOM(浏览器对象模型)

BOM 是操作浏览器的接口,核心对象是 window(全局对象)。

4.2.1 核心 BOM 对象

  1. window:全局作用域,包含所有 BOM/DOM 对象,常用方法:
    javascript
     
    运行
     
     
     
     
    // 窗口尺寸
    console.log(window.innerWidth); // 视口宽度(不含滚动条)
    console.log(window.innerHeight); // 视口高度
    
    // 弹窗(不推荐,影响体验)
    window.alert("提示"); // 提示框
    let res = window.confirm("确认?"); // 确认框(返回 true/false)
    let input = window.prompt("输入内容", "默认值"); // 输入框(返回输入值/ null)
    
    // 定时器(前文已讲)
    // 窗口跳转
    window.location.href = "https://www.baidu.com"; // 跳转页面
    window.open("https://www.baidu.com"); // 新开窗口
    window.close(); // 关闭窗口(仅能关闭自己打开的窗口)
    
     
     
  2. location:操作 URL,常用属性 / 方法:
    javascript
     
    运行
     
     
     
     
    console.log(location.href); // 完整 URL
    console.log(location.protocol); // 协议(http/https)
    console.log(location.host); // 域名+端口
    console.log(location.pathname); // 路径
    console.log(location.search); // 查询参数(?a=1&b=2)
    console.log(location.hash); // 哈希(#xxx)
    
    location.reload(); // 刷新页面(true 强制刷新缓存)
    location.assign("https://www.baidu.com"); // 跳转(可回退)
    location.replace("https://www.baidu.com"); // 跳转(不可回退)
    
     
     
  3. history:操作浏览器历史记录:
    javascript
     
    运行
     
     
     
     
    history.back(); // 后退(等价于点击浏览器后退)
    history.forward(); // 前进
    history.go(-1); // 后退1步(go(1) 前进1步)
    history.pushState({}, "", "/new-page"); // 添加历史记录(不跳转)
    history.replaceState({}, "", "/replace-page"); // 替换当前历史记录
    
     
     
  4. navigator:获取浏览器信息:
    javascript
     
    运行
     
     
     
     
    console.log(navigator.userAgent); // 浏览器 UA(判断设备/浏览器)
    console.log(navigator.language); // 语言(zh-CN)
    console.log(navigator.onLine); // 是否联网
    
     
     

五、异步编程(深度版)

5.1 事件循环(Event Loop)—— 异步核心

核心原理

JS 执行时,分为「调用栈(Call Stack)」「微任务队列(Microtasks)」「宏任务队列(Macrotasks)」:
  1. 同步代码进入调用栈,逐行执行。
  2. 异步任务:
    • 宏任务(Macrotasks):setTimeout/setInterval、AJAX、DOM 事件、script 整体代码。
    • 微任务(Microtasks):Promise.then/catch/finally、async/await、queueMicrotask。
  3. 调用栈清空后,先执行所有微任务,再执行一个宏任务,重复此过程(事件循环)。

示例(理解执行顺序)

javascript
 
运行
 
 
 
 
console.log("同步1");

setTimeout(() => {
  console.log("宏任务:setTimeout");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("微任务:Promise1");
    return Promise.resolve();
  })
  .then(() => {
    console.log("微任务:Promise2");
  });

queueMicrotask(() => {
  console.log("微任务:queueMicrotask");
});

console.log("同步2");

// 输出顺序:
// 同步1 → 同步2 → 微任务:Promise1 → 微任务:Promise2 → 微任务:queueMicrotask → 宏任务:setTimeout
 

5.2 网络请求(AJAX/fetch/axios)

5.2.1 原生 AJAX(XMLHttpRequest)

javascript
 
运行
 
 
 
 
function ajax(url, method = "GET", data = null) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.setRequestHeader("Content-Type", "application/json"); // 设置请求头
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText)); // 成功
      } else {
        reject(new Error(`请求失败:${xhr.status}`)); // 失败
      }
    };
    xhr.onerror = function() {
      reject(new Error("网络错误"));
    };
    xhr.send(data ? JSON.stringify(data) : null); // 发送数据
  });
}

// 调用
ajax("https://api.example.com/user", "GET")
  .then(data => console.log(data))
  .catch(err => console.log(err));
 

5.2.2 fetch(ES6+ 原生请求)

javascript
 
运行
 
 
 
 
// GET 请求
fetch("https://api.example.com/user")
  .then(response => {
    if (!response.ok) throw new Error(`HTTP 错误:${response.status}`);
    return response.json(); // 解析 JSON
  })
  .then(data => console.log(data))
  .catch(err => console.log(err));

// POST 请求
fetch("https://api.example.com/user", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "小明", age: 18 })
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.log(err));
 

5.2.3 axios(第三方库,推荐)

5.3 Promise 进阶方法

javascript
 
运行
 
 
 
 
// 1. Promise.all:所有 Promise 成功才成功,返回结果数组;一个失败则整体失败
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
  .then(res => console.log(res)) // [1,2,3]
  .catch(err => console.log(err));

// 2. Promise.race:第一个完成的 Promise 决定结果(无论成功/失败)
let p4 = new Promise(resolve => setTimeout(() => resolve(4), 1000));
let p5 = new Promise((resolve, reject) => setTimeout(() => reject("失败"), 500));
Promise.race([p4, p5])
  .then(res => console.log(res))
  .catch(err => console.log(err)); // 失败(p5 先完成)

// 3. Promise.allSettled:所有 Promise 完成(无论成功/失败),返回每个的结果
let p6 = Promise.resolve(6);
let p7 = Promise.reject("失败");
Promise.allSettled([p6, p7])
  .then(res => console.log(res));
// 输出:
// [
//   { status: "fulfilled", value: 6 },
//   { status: "rejected", reason: "失败" }
// ]

// 4. Promise.any:第一个成功的 Promise 决定结果;所有失败则抛出 AggregateError
let p8 = new Promise((resolve, reject) => setTimeout(() => reject("失败1"), 100));
let p9 = new Promise(resolve => setTimeout(() => resolve(9), 200));
Promise.any([p8, p9])
  .then(res => console.log(res)) // 9
  .catch(err => console.log(err));
 

六、ES6+ 进阶特性

6.1 模块化(ES Module)

模块化解决代码复用、作用域污染问题,浏览器 / Node.js 均支持。

6.1.1 核心语法

6.1.2 浏览器使用模块化

html
 
预览
 
 
 
 
<!-- script 标签添加 type="module" -->
<script type="module">
  import { name } from "./module.js";
  console.log(name);
</script>
 

6.2 Set/Map(ES6+ 新数据结构)

6.2.1 Set(集合,无重复值)

javascript
 
运行
 
 
 
 
let set = new Set([1, 2, 2, 3]);
console.log(set.size); // 3(自动去重)

// 增删查
set.add(4); // 添加
set.delete(2); // 删除
console.log(set.has(3)); // true(是否存在)
set.clear(); // 清空

// 遍历
for (let item of set) {
  console.log(item);
}
set.forEach(item => console.log(item));

// 转换为数组
let arr = [...set];
 

6.2.2 Map(键值对,键可任意类型)

javascript
 
运行
 
 
 
 
let map = new Map();
let obj = { name: "JS" };

// 增删查
map.set(obj, "value"); // 键为对象
map.set("key", 123);
console.log(map.get(obj)); // "value"
console.log(map.has("key")); // true
map.delete("key");
map.clear();

// 遍历
for (let [key, value] of map) {
  console.log(key, value);
}
map.forEach((value, key) => console.log(key, value));
 

6.3 生成器(Generator)与迭代器(Iterator)

6.3.1 迭代器(Iterator)

所有可遍历对象(Array/Set/Map/String)都有 [Symbol.iterator] 方法,返回迭代器,通过 next() 遍历:
javascript
 
运行
 
 
 
 
let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
 

6.3.2 生成器(Generator)

返回迭代器的函数,通过 function* 定义,yield 暂停执行:
javascript
 
运行
 
 
 
 
function* gen() {
  yield 1;
  yield 2;
  return 3;
}

let g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
 

6.4 可选链 + 空值合并(ES2020 实用特性)

javascript
 
运行
 
 
 
 
// 可选链(?.):避免访问嵌套属性时报错
let user = {
  name: "小明",
  address: {
    city: "北京"
  }
};
console.log(user?.address?.street); // undefined(不报错)
console.log(user?.getInfo?.()); // undefined(函数不存在)

// 空值合并(??):仅 null/undefined 触发默认值
let count = 0;
let result = count ?? "默认值"; // 0(区别于 ||,0 不触发)
let name = undefined ?? "游客"; // 游客
 

七、实战工具函数(企业级)

7.1 防抖(Debounce)

触发后延迟执行,若期间再次触发则重置延迟,适用于搜索框输入、窗口 resize:
javascript
 
运行
 
 
 
 
function debounce(fn, delay = 500) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer); // 重置定时器
    timer = setTimeout(() => {
      fn.apply(this, args); // 绑定 this 和参数
    }, delay);
  };
}

// 使用
let input = document.querySelector("input");
input.addEventListener("input", debounce(function(e) {
  console.log("搜索:", e.target.value);
}, 500));
 

7.2 节流(Throttle)

固定时间内仅执行一次,适用于滚动加载、按钮防连点:
javascript
 
运行
 
 
 
 
function throttle(fn, delay = 500) {
  let flag = true; // 开关
  return function(...args) {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
}

// 使用
window.addEventListener("scroll", throttle(function() {
  console.log("滚动中...");
}, 500));
 

7.3 深拷贝(通用版)

见前文「对象 - 深拷贝」部分。

7.4 时间格式化

javascript
 
运行
 
 
 
 
function formatTime(date, format = "YYYY-MM-DD HH:mm:ss") {
  const dt = new Date(date);
  const year = dt.getFullYear();
  const month = String(dt.getMonth() + 1).padStart(2, "0");
  const day = String(dt.getDate()).padStart(2, "0");
  const hour = String(dt.getHours()).padStart(2, "0");
  const minute = String(dt.getMinutes()).padStart(2, "0");
  const second = String(dt.getSeconds()).padStart(2, "0");

  return format
    .replace("YYYY", year)
    .replace("MM", month)
    .replace("DD", day)
    .replace("HH", hour)
    .replace("mm", minute)
    .replace("ss", second);
}

// 使用
console.log(formatTime(new Date())); // 2025-12-26 10:00:00
console.log(formatTime(new Date(), "YYYY/MM/DD")); // 2025/12/26
 

八、错误处理与调试(深度版)

8.1 常见错误类型

错误类型 原因 示例
ReferenceError 引用未定义的变量 / 函数 console.log(undefinedVar)
TypeError 操作类型错误(如调用非函数) let obj = {}; obj.fn()
SyntaxError 语法错误(如括号不匹配) let a = 10; if (a > 5) { console.log(a)
RangeError 数值超出范围(如数组长度为负数) let arr = new Array(-1)
URIError URI 处理错误(如 decodeURI 格式错误) decodeURI("%")

8.2 调试技巧

  1. console 调试:
    javascript
     
    运行
     
     
     
     
    console.log("普通日志");
    console.warn("警告");
    console.error("错误");
    console.table([{ name: "小明" }, { name: "小红" }]); // 表格展示
    console.time("耗时"); // 计时开始
    // 执行代码
    console.timeEnd("耗时"); // 计时结束(输出耗时)
    console.dir(obj); // 展开对象详情
    
     
     
  2. 断点调试(浏览器 F12 → Sources):
    • 点击行号添加断点。
    • 调试按钮:继续(F8)、单步执行(F10)、步入函数(F11)、步出函数(Shift+F11)。
    • 监视变量:在 Watch 面板添加变量名,实时查看值。
  3. try-catch 捕获异步错误:
    javascript
     
    运行
     
     
     
     
    async function fn() {
      try {
        let res = await axios.get("https://api.example.com/error");
      } catch (err) {
        console.error("异步错误:", err);
      }
    }
    
     
     

九、最佳实践(企业级)

9.1 代码规范

  1. 使用 ESLint:强制代码规范,避免低级错误(如未声明变量、多余分号)。
  2. 注释规范:
    • 函数 / 类:JSDoc 注释(说明功能、参数、返回值)。
      javascript
       
      运行
       
       
       
       
      /**
       * 加法函数
       * @param {number} a - 第一个数
       * @param {number} b - 第二个数
       * @returns {number} 两数之和
       */
      function add(a, b) {
        return a + b;
      }
      
       
       
    • 复杂逻辑:添加单行注释,说明为什么这么写(而非写了什么)。
  3. 避免全局变量:使用模块化 / 闭包封装,减少全局污染。

9.2 性能优化

  1. 减少重排 / 重绘:
    • 批量修改样式(先隐藏 DOM,修改后显示)。
    • 使用 transform/opacity 实现动画(GPU 加速,无重排)。
  2. 懒加载:图片 / 组件延迟加载(loading="lazy" 或手动实现)。
  3. 缓存数据:频繁使用的数据缓存到内存 /localStorage。
  4. 避免内存泄漏:
    • 及时解除事件绑定(如组件销毁时)。
    • 闭包使用后解除引用(fn = null)。
    • 避免循环引用(如 obj1.a = obj2; obj2.b = obj1)。

9.3 安全问题

  1. XSS(跨站脚本攻击):
    • 避免使用 innerHTML,优先 innerText。
    • 对用户输入进行转义(如将 < 转义为 &lt;)。
  2. CSRF(跨站请求伪造):
    • 请求添加 Token 验证。
    • 检查 Referer/Origin 头。
  3. 避免 eval:eval 执行字符串代码,易被注入攻击,且性能差。

十、实战案例(待办清单)

html
 
预览
 
 
 
 
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>待办清单</title>
  <style>
    .todo-container { max-width: 600px; margin: 20px auto; }
    .todo-input { display: flex; gap: 10px; margin-bottom: 20px; }
    .todo-input input { flex: 1; padding: 8px; }
    .todo-input button { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
    .todo-list { list-style: none; padding: 0; }
    .todo-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }
    .todo-item.done span { text-decoration: line-through; color: #999; }
    .todo-item button { background: #dc3545; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; }
  </style>
</head>
<body>
  <div class="todo-container">
    <div class="todo-input">
      <input type="text" id="todoInput" placeholder="请输入待办事项">
      <button id="addBtn">添加</button>
    </div>
    <ul id="todoList" class="todo-list"></ul>
  </div>

  <script>
    // 1. 获取 DOM 元素
    const todoInput = document.querySelector("#todoInput");
    const addBtn = document.querySelector("#addBtn");
    const todoList = document.querySelector("#todoList");

    // 2. 初始化数据(从 localStorage 读取)
    let todos = JSON.parse(localStorage.getItem("todos")) || [];

    // 3. 渲染列表
    function renderTodos() {
      todoList.innerHTML = "";
      todos.forEach((todo, index) => {
        const li = document.createElement("li");
        li.className = `todo-item ${todo.done ? "done" : ""}`;
        li.innerHTML = `
          <div>
            <input type="checkbox" ${todo.done ? "checked" : ""} data-index="${index}">
            <span>${todo.content}</span>
          </div>
          <button data-index="${index}">删除</button>
        `;
        todoList.appendChild(li);
      });
      // 保存到 localStorage
      localStorage.setItem("todos", JSON.stringify(todos));
    }

    // 4. 添加待办
    function addTodo() {
      const content = todoInput.value.trim();
      if (!content) return alert("请输入待办事项");
      todos.push({ content, done: false });
      todoInput.value = "";
      renderTodos();
    }

    // 5. 切换完成状态
    function toggleTodo(index) {
      todos[index].done = !todos[index].done;
      renderTodos();
    }

    // 6. 删除待办
    function deleteTodo(index) {
      todos.splice(index, 1);
      renderTodos();
    }

    // 7. 绑定事件
    addBtn.addEventListener("click", addTodo);
    todoInput.addEventListener("keydown", (e) => {
      if (e.key === "Enter") addTodo(); // 回车添加
    });

    // 事件委托:切换/删除
    todoList.addEventListener("click", (e) => {
      const index = e.target.dataset.index;
      if (index === undefined) return;
      if (e.target.type === "checkbox") {
        toggleTodo(index);
      } else if (e.target.tagName === "BUTTON") {
        deleteTodo(index);
      }
    });

    // 8. 初始渲染
    renderTodos();
  </script>
</body>
</html>
 

总结

  1. 核心底层:JS 是单线程异步语言,作用域链、闭包、原型链是核心基础,this 指向由调用方式决定,事件循环是异步执行的核心逻辑。
  2. 实战重点:DOM 操作优先用事件委托和文档碎片优化性能;异步编程优先用 async/await + axios;ES6+ 特性(模块化、Set/Map、可选链)大幅提升开发效率。
  3. 工程化:遵循代码规范(ESLint)、优化性能(减少重排、防抖节流)、注意安全(XSS/CSRF),结合实战案例(如待办清单)巩固知识点,是从新手到进阶的关键。