| ES6 | 异步 | 闭包 | 原型链 | DOM操作 | 事件处理 |

65 阅读10分钟

一、ES6+ 新特性

ES6(ECMAScript 2015)及后续的 ES7-ES14 被统称为 ES6+,是 JavaScript 语言的重大升级,解决了 ES5 时代的语法冗余、作用域混乱、功能缺失等问题,大幅提升了代码的可读性、可维护性和开发效率。

1. 块级作用域与变量声明

ES5 中只有全局作用域和函数作用域,var 声明的变量存在 “变量提升” 和 “作用域穿透” 问题,极易引发 bug。ES6 新增 letconst 关键字,引入块级作用域({} 包裹的区域):

  • let:声明可变变量,仅在当前块级作用域有效,无变量提升,不允许重复声明;
  • const:声明常量,一旦赋值不可修改(引用类型仅保证地址不变),同样遵循块级作用域规则。示例:
// ES5 问题:变量提升+作用域穿透
if (true) {
  var a = 10;
}
console.log(a); // 10(全局作用域可访问)

// ES6 解决
if (true) {
  let b = 20;
  const c = 30;
}
console.log(b); // ReferenceError: b is not defined
console.log(c); // ReferenceError: c is not defined

2. 箭头函数

简化函数声明语法,核心特性:

  • 语法简洁:单参数可省略括号,单返回语句可省略大括号和 return
  • 无独立 this:箭头函数的 this 继承自外层作用域,解决了 ES5 中 this 指向混乱的问题(如回调函数中 this 丢失);
  • 不能作为构造函数:无法使用 new 调用,无 arguments 对象(可改用剩余参数)。示例:
// ES5 函数
const add = function(a, b) {
  return a + b;
};

// ES6 箭头函数
const add = (a, b) => a + b;

// this 指向示例
const obj = {
  name: "张三",
  fn1: function() {
    setTimeout(function() {
      console.log(this.name); // undefined(this 指向全局)
    }, 100);
  },
  fn2: function() {
    setTimeout(() => {
      console.log(this.name); // 张三(this 继承自 fn2 的作用域)
    }, 100);
  }
};
obj.fn1();
obj.fn2();

3. 解构赋值

允许从数组 / 对象中提取值,赋值给变量,简化数据提取逻辑:

  • 数组解构:按索引匹配,支持默认值;
  • 对象解构:按属性名匹配,支持重命名和默认值。示例:
// 数组解构
const [a, b, c = 30] = [10, 20];
console.log(a, b, c); // 10 20 30

// 对象解构
const { name: userName, age = 18 } = { name: "李四" };
console.log(userName, age); // 李四 18

4. 扩展运算符与剩余参数

  • 扩展运算符(...):将数组 / 对象展开为单个元素,用于合并数据、传递参数;
  • 剩余参数(...):收集剩余的参数,转为数组,替代 arguments。示例:

扩展运算符(展开):把 “整体” 拆成 “个体”

1. 展开数组(最常用)
// 场景1:数组拼接
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 扩展运算符:把 arr1 和 arr2 展开成独立元素,再组成新数组
const newArr = [...arr1, ...arr2]; 
console.log(newArr); // [1,2,3,4,5,6]

// 场景2:函数传参
function sum(a, b, c) {
  return a + b + c;
}
const nums = [10, 20, 30];
// 扩展运算符:把数组展开成 3 个独立参数传给 sum
console.log(sum(...nums)); // 60(等价于 sum(10,20,30))

// 场景3:复制数组(浅拷贝)
const original = [7, 8, 9];
const copy = [...original];
copy.push(10);
console.log(original); // [7,8,9](原数组不受影响)
2. 展开对象
const user = { name: "张三", age: 25 };
// 扩展运算符:展开 user 的属性,添加/覆盖新属性
const newUser = { ...user, score: 90, age: 26 };
console.log(newUser); // { name: '张三', age: 26, score: 90 }
3. 展开字符串
const str = "hello";
const charArr = [...str];
console.log(charArr); // ['h','e','l','l','o'](拆成字符数组)

剩余参数(收集):把 “个体” 聚成 “整体”

1. 函数参数中收集多余参数
// 场景1:不定参数求和
function sumAll(...numbers) {
  // ...numbers 把所有传入的参数收集成一个数组
  return numbers.reduce((total, num) => total + num, 0);
}
console.log(sumAll(1, 2)); // 3(numbers = [1,2])
console.log(sumAll(1, 2, 3, 4)); // 10(numbers = [1,2,3,4])

// 场景2:固定参数 + 剩余参数
function printUser(name, age, ...hobbies) {
  console.log(`姓名:${name},年龄:${age}`);
  console.log("爱好:", hobbies); // 收集除 name/age 外的所有参数
}
printUser("李四", 30, "读书", "跑步", "打球");
// 输出:
// 姓名:李四,年龄:30
// 爱好:['读书','跑步','打球']
2. 解构赋值中收集剩余元素
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3,4,5](收集剩余元素)

// 对象解构
const { name, ...otherInfo } = { name: "王五", age: 28, city: "北京" };
console.log(name); // 王五
console.log(otherInfo); // { age: 28, city: '北京' }(收集剩余属性)
易错点提醒
  1. 剩余参数在函数中必须是最后一个参数,否则报错:

    // ❌ 错误:剩余参数不能在中间
    function fn(...a, b) {} 
    // ✅ 正确:剩余参数在最后
    function fn(a, ...b) {}
    
  2. 扩展运算符展开对象是浅拷贝,嵌套对象仍会共享引用:

    // 浅拷贝:两个数组在内存中是完全独立的 , 不会共享引用 
    const original = [7, 8, 9]; 
    const copy = [...original]; copy.push(10);  
    console.log(original); // [7,8,9](原数组不受影响)
    
    // 浅拷贝只会拷贝一层 , 所以嵌套对象仍会共享引用
    const user = { name: "张三", info: { age: 25 } };
    const copy = { ...user };
    copy.info.age = 26;
    console.log(user.info.age); // 26(原对象的嵌套属性被修改)
    

5. 模板字符串

模板字符串用 反引号 `` 包裹,主要特性:

  1. 支持多行字符串,无需手动拼接换行符 \n
  2. 支持变量插值,用 ${变量/表达式} 直接嵌入内容,无需拼接 + 号;
  3. 支持表达式运算${} 内可以写任意 JS 表达式(比如算术运算、函数调用)。
const name = "王五";
const age = 20;
// ES5 拼接
const str1 = "姓名:" + name + ",年龄:" + age + "岁";
// ES6 模板字符串
const str2 = `姓名:${name},年龄:${age}岁`;

6. 其他核心特性

  • Set/Map 数据结构:Set 用于存储唯一值(数组去重),Map 键值对集合(键可为任意类型,替代对象);
  • Class 类:语法糖,简化原型链继承,提供 constructorextendssuper 等关键字;
  • 模块化(import/export):替代 CommonJS/AMD,实现按需加载,提升代码模块化程度;
  • 可选链(?.)、空值合并(??):ES2020 特性,简化空值判断,避免 Cannot read property 'xxx' of undefined 错误。

ES6+ 新特性的核心价值在于 “语法简化” 和 “功能补全”,让 JavaScript 从 “脚本语言” 向 “工程化语言” 迈进,是现代前端开发(React/Vue/TypeScript)的基础。

二、异步(Promise, async/await)

JavaScript 是单线程语言,默认同步执行代码,但网络请求、定时器、文件操作等场景需要异步处理,否则会阻塞主线程。异步编程经历了 “回调函数 → Promise → async/await” 的演进,核心目标是解决 “回调地狱”,让异步代码更易读、易维护。

1. 异步编程的核心问题:回调地狱

ES5 中异步操作依赖回调函数,多个异步嵌套时会出现 “回调地狱”(代码层级深、可读性差、错误处理繁琐):

// 回调地狱:获取用户信息 → 获取用户订单 → 获取订单详情
$.get("/api/user", (user) => {
  $.get(`/api/order?userId=${user.id}`, (order) => {
    $.get(`/api/orderDetail?orderId=${order.id}`, (detail) => {
      console.log(detail);
    }, (err) => {
      console.error("获取订单详情失败", err);
    });
  }, (err) => {
    console.error("获取订单失败", err);
  });
}, (err) => {
  console.error("获取用户失败", err);
});

问题:层级嵌套过深,错误处理分散,代码难以调试和维护。

2. Promise:异步操作的标准化封装

Promise 是 ES6 引入的异步编程解决方案,本质是一个对象,代表异步操作的 “未完成 / 成功 / 失败” 状态,核心特性:

  • 三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),状态一旦改变不可逆转;
  • 两个回调:then() 处理成功结果,catch() 处理失败结果,支持链式调用;
  • 解决回调地狱:通过链式调用替代嵌套,错误可统一捕获。

(1)Promise 基本用法

// 创建 Promise 对象
const getPromise = (url) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(JSON.parse(xhr.responseText)); // 成功:调用 resolve
      } else {
        reject(new Error(xhr.statusText)); // 失败:调用 reject
      }
    };
    xhr.onerror = () => {
      reject(new Error("网络请求失败"));
    };
    xhr.send();
  });
};

// 链式调用:解决回调地狱
getPromise("/api/user")
  .then((user) => getPromise(`/api/order?userId=${user.id}`))
  .then((order) => getPromise(`/api/orderDetail?orderId=${order.id}`))
  .then((detail) => console.log(detail))
  .catch((err) => console.error("请求失败", err)); // 统一捕获所有错误

(2)Promise 常用方法

  • Promise.all():接收多个 Promise 数组,全部成功才返回结果数组,一个失败则立即失败;
  • Promise.race():接收多个 Promise 数组,返回第一个完成的 Promise 结果(无论成功 / 失败);
  • Promise.resolve()/Promise.reject():快速创建成功 / 失败的 Promise 对象;
  • Promise.allSettled():等待所有 Promise 完成(无论成功 / 失败),返回所有结果(包含状态和值)。

示例(Promise.all):

// 同时请求多个接口,全部完成后处理
const promise1 = getPromise("/api/user");
const promise2 = getPromise("/api/goods");
Promise.all([promise1, promise2])
  .then(([user, goods]) => {
    console.log("用户信息", user);
    console.log("商品信息", goods);
  })
  .catch((err) => console.error("某个请求失败", err));

3. async/await:异步代码同步化

ES2017 引入的 async/await 是 Promise 的语法糖,允许用 “同步代码的写法” 处理异步操作,核心规则:

  • async 修饰函数:使函数返回一个 Promise 对象;
  • await 修饰 Promise:暂停函数执行,直到 Promise 状态变为成功,返回结果;若 Promise 失败,需用 try/catch 捕获错误。

(1)基本用法(解决回调地狱的终极方案)

// 封装异步请求函数(返回 Promise)
const getUser = () => getPromise("/api/user");
const getOrder = (userId) => getPromise(`/api/order?userId=${userId}`);
const getOrderDetail = (orderId) => getPromise(`/api/orderDetail?orderId=${orderId}`);

// async/await 写法:同步风格的异步代码
const getOrderInfo = async () => {
  try {
    const user = await getUser(); // 等待 getUser 完成
    const order = await getOrder(user.id); // 等待 getOrder 完成
    const detail = await getOrderDetail(order.id); // 等待 getOrderDetail 完成
    console.log(detail);
  } catch (err) {
    console.error("请求失败", err); // 统一捕获所有错误
  }
};

getOrderInfo();

(2)async/await 优势

  • 代码扁平化:无嵌套,可读性接近同步代码;
  • 错误处理统一:通过 try/catch 捕获所有异步错误,替代 Promise 的 catch()
  • 调试友好:可在 await 处打断点,调试流程与同步代码一致。

4. 异步编程的核心原则

  • 避免同步阻塞:异步操作始终不阻塞主线程(如定时器、网络请求由浏览器内核的线程处理);
  • 错误处理全覆盖:Promise 需加 catch(),async/await 需包 try/catch,避免未捕获的异步错误;
  • 并行处理优化:多个无依赖的异步操作,用 Promise.all() 替代串行 await,提升执行效率。

异步编程是前端开发的核心难点,Promise 解决了 “回调地狱” 的结构问题,async/await 则让异步代码的可读性达到了同步代码的水平,是现代前端处理网络请求、异步数据加载的标配。

三、闭包和原型链

闭包和原型链是 JavaScript 的两大核心特性,也是面试高频考点。闭包关乎作用域和变量生命周期,原型链则是 JavaScript 实现继承的底层机制,理解这两个概念能帮你突破 “语法使用” 到 “原理理解” 的瓶颈。

1. 闭包(Closure)

(1)闭包的定义

闭包是指 “有权访问另一个函数作用域中变量的函数”,本质是函数作用域链的保留:当内部函数被外部引用时,其所在的作用域不会被垃圾回收机制销毁,从而可以持续访问外层函数的变量。

(2)闭包的形成条件

  1. 存在嵌套函数(内部函数 + 外部函数);
  2. 内部函数引用外部函数的变量 / 参数;
  3. 外部函数执行后,内部函数被外部环境引用(如返回、赋值给全局变量)。

(3)基本用法与示例

// 基础闭包:外部函数执行后,内部函数仍能访问其变量
function outer() {
  const num = 10; // 外部函数的变量
  // 内部函数引用外部变量
  function inner() {
    console.log(num);
  }
  return inner; // 返回内部函数,使其被外部引用
}

const fn = outer(); // outer 执行完毕,但其作用域未被销毁
fn(); // 10(inner 仍能访问 num)

(4)闭包的核心应用场景

  • 封装私有变量:模拟 “私有属性 / 方法”,避免全局变量污染;

    // 封装计数器:count 是私有变量,只能通过方法修改
    function createCounter() {
      let count = 0;
      return {
        increment: () => count++,
        decrement: () => count--,
        getCount: () => count
      };
    }
    
    const counter = createCounter();
    counter.increment();
    counter.increment();
    console.log(counter.getCount()); // 2
    console.log(counter.count); // undefined(无法直接访问)
    
  • 防抖 / 节流函数:利用闭包保存定时器 ID、上次执行时间等状态;

// 防抖函数(闭包保存 timer 变量)
function debounce(fn, delay) {
  // 1. 定义 timer 变量,初始值为 null
  let timer = null; // 闭包保存 timer,多次调用共享同一个 timer
  
  // 2. 返回一个新的匿名函数(防抖的核心逻辑都在这个返回的函数里)
  return (...args) => {
        //`(...args)`:使用剩余参数语法,接收调用防抖函数时传入的所有参数
        //(比如事件对象 `e`),保证目标函数 `fn` 能拿到完整参数。
    // 3. 清除上一次未执行的定时器
    clearTimeout(timer);
    // 每次调用防抖后的函数时,先清除上一次设置的定时器(如果定时器还没到时间)。

    比如:输入框每输入一个字符就触发一次,前几次触发的定时器都会被清除
    只有最后一次输入的定时器会生效。
    // 4. 重新设置新的定时器
    timer = setTimeout(() => {
      // 5. 延迟时间到后,执行传入的目标函数 fn
      fn.apply(this, args);
    }, delay);
    // `fn.apply(this, args)`:用 `apply` 调用目标函数,保证:
    -   `this` 指向正确(继承防抖函数的上下文);
    -   `args` 把接收的所有参数传递给 `fn`(比如把输入框的事件对象传给搜索函数)。
  };
}
  • 柯里化函数:将多参数函数转为单参数函数,利用闭包缓存已传入的参数。

(5)闭包的注意事项

  • 内存泄漏风险:闭包会保留外层作用域,若长期引用未释放(如赋值给全局变量),会导致变量无法被垃圾回收,占用内存;
  • 解决:使用完闭包后,手动解除引用(如 fn = null),让作用域可以被回收。

2. 原型链(Prototype Chain)

JavaScript 是 “基于原型的面向对象语言”,没有类(ES6 Class 是语法糖),所有对象都通过 “原型” 实现属性和方法的继承,原型链是实现继承的核心机制。

(1)核心概念

  • 原型(prototype):函数特有的属性,指向一个对象,该对象是当前函数创建的所有实例的原型;
  • 隐式原型__proto__):所有对象(包括函数)都有的属性,指向其构造函数的 prototype
  • 原型链:当访问对象的属性 / 方法时,先在自身查找,找不到则通过 __proto__ 向上查找,直到 Object.prototype,这个查找链条就是原型链 , 原型链的终点是:null , Object.prototype.__proto__ 的值就是 null

(2)原型链的基本结构

// 构造函数
function Person(name) {
  this.name = name;
}
// 给原型添加方法
Person.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

// 创建实例
const p1 = new Person("张三");

// 原型链查找:p1 → Person.prototype → Object.prototype → null
console.log(p1.name); // 自身属性,直接返回
p1.sayHello(); // p1 自身无 sayHello,查找 p1.__proto__(Person.prototype)找到
console.log(p1.toString()); // p1 和 Person.prototype 无 toString,查找 Object.prototype 找到
console.log(p1.xxx); // 原型链末端为 null,返回 undefined
  • 构造函数 Person:相当于一个 “模板”,定义了实例应该有什么属性(name)和方法(sayHello)。

  • new 关键字的作用:执行 new Person("张三") 时,JS 会做这几件事:

    1. 创建一个空的新对象 {}
    2. 让这个新对象的 __proto__ 指向 Person.prototype
    3. 调用 Person 函数,把 this 绑定到这个新对象上,给它添加 name: "张三" 属性;
    4. 返回这个新对象。
  • p1:变量 p1 接收了 new Person("张三") 返回的这个新对象,所以 p1 就是 Person 的实例

(3)原型链的核心应用:继承

ES5 中通过修改原型链实现继承(ES6 Class 的 extends 底层仍是原型链):

// 父类
function Parent(name) {
  this.name = name;
}
Parent.prototype.eat = function() {
  console.log(`${this.name} 吃饭`);
};

// 子类
function Child(name, age) {
  Parent.call(this, name); // 继承父类实例属性
  this.age = age;
}
// 继承父类原型方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 修正构造函数指向

// 子类添加自有方法
Child.prototype.run = function() {
  console.log(`${this.name} 跑步,年龄 ${this.age}`);
};

const child = new Child("李四", 10);
child.eat(); // 继承父类方法
child.run(); // 子类自有方法

(4)原型链的关键规则

  • 所有对象的最终原型是 Object.prototype,其 __proto__null
  • 函数的 prototype 是普通对象,Function.prototype 是函数(特殊);
  • 修改原型会影响所有实例(原型共享特性)。

3. 闭包与原型链的关联

闭包关注 “作用域和变量保留”,原型链关注 “对象属性继承”,二者共同构成 JavaScript 的核心底层逻辑:闭包让函数可以突破作用域限制 , 去访问变量,原型链让对象可以突破自身结构 , 去继承方法,这些是理解 JavaScript 设计思想的关键。

四、DOM 操作和事件处理

DOM(文档对象模型)是浏览器将 HTML 文档解析成的树形结构,前端开发的核心是通过 JavaScript 操作 DOM 实现页面交互,事件处理则是响应用户操作(点击、输入、滚动等)的核心机制。

1. DOM 操作

DOM 操作分为 “查找节点”“创建 / 插入节点”“修改节点”“删除节点” 四类,核心是操作 DOM 树的节点(元素节点、文本节点、属性节点)。

(1)查找 DOM 节点(核心)

查找是 DOM 操作的第一步,常用方法:

  • 按 ID 查找:document.getElementById("id") → 返回单个元素(效率最高);
  • 按类名查找:document.getElementsByClassName("className") → 返回 HTMLCollection(动态集合);
  • 按标签名查找:document.getElementsByTagName("tagName") → 返回 HTMLCollection;
  • 按选择器查找:document.querySelector("selector")(返回第一个匹配元素)、document.querySelectorAll("selector")(返回 NodeList,静态集合)→ 最灵活,支持 CSS 选择器。

示例:

// 按 ID 查找
const box = document.getElementById("box");

// 按选择器查找
const item = document.querySelector(".list .item");
const items = document.querySelectorAll(".list .item"); // NodeList 可通过 forEach 遍历

(2)创建与插入节点

动态生成页面内容的核心,常用方法:

  • 创建元素:document.createElement("tagName")

  • 创建文本节点:document.createTextNode("text")

  • 插入节点:

    • parent.appendChild(child):将子节点插入父节点末尾;
    • parent.insertBefore(newNode, referenceNode):将新节点插入参考节点之前;
    • element.innerHTML:直接通过 HTML 字符串插入节点(简洁但有 XSS 风险)。

示例:

// 创建元素并插入
const ul = document.querySelector("ul");
const li = document.createElement("li");
li.textContent = "新列表项"; // 设置文本内容(安全,无 XSS)
ul.appendChild(li);

// innerHTML 方式(慎用,避免用户输入内容)
ul.innerHTML += "<li>新列表项</li>";

(3)修改 DOM 节点

  • 修改属性:element.setAttribute("attr", "value")(设置属性)、element.getAttribute("attr")(获取属性)、element.removeAttribute("attr")(移除属性);

    const img = document.querySelector("img");
    img.setAttribute("src", "new.jpg");
    console.log(img.getAttribute("src")); // new.jpg
    
  • 修改样式:

    • 行内样式:element.style.cssProperty = "value"(驼峰命名,如 backgroundColor);
    • 类名样式:element.classList.add("className")element.classList.remove("className")element.classList.toggle("className")(推荐,分离样式和逻辑)。
    const div = document.querySelector(".box");
    div.style.width = "200px";
    div.classList.add("active"); // 添加类名
    div.classList.toggle("show"); // 切换类名
    
  • 修改文本 / HTML:element.textContent(纯文本,安全)、element.innerHTML(HTML 字符串,有 XSS 风险)。

(4)删除 DOM 节点

  • parent.removeChild(child):父节点移除子节点;
  • element.remove():元素自身移除(ES6+ 方法,更简洁)。

示例:

const li = document.querySelector("li");
li.parentElement.removeChild(li); // 传统方式
// 或
li.remove(); // 简洁方式

(5)DOM 操作的性能优化

DOM 操作是 “重操作”,频繁修改会触发浏览器重排(Reflow)/ 重绘(Repaint),导致页面卡顿,优化手段:

  • 批量操作:先将节点脱离文档流(如隐藏父节点),操作完成后再恢复;

  • 使用文档碎片:document.createDocumentFragment(),批量插入节点仅触发一次重排;

    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 1000; i++) {
      const li = document.createElement("li");
      li.textContent = `项 ${i}`;
      fragment.appendChild(li); // 先插入碎片,无重排
    }
    document.querySelector("ul").appendChild(fragment); // 仅一次重排
    
  • 避免频繁查询 DOM:将查询结果缓存到变量,减少 DOM 遍历。

2. 事件处理

事件是浏览器触发的 “信号”(如点击、输入、加载),事件处理是 JavaScript 响应用户操作的核心,分为 “事件绑定”“事件流”“事件对象”“事件优化” 四部分。

(1)事件绑定方式

  • 行内绑定(不推荐):<button onclick="handleClick()">点击</button> → 耦合度高,不利于维护;

  • DOM0 级绑定:element.onclick = function() {} → 简单,但一个事件只能绑定一个处理函数;

  • DOM2 级绑定:element.addEventListener("eventName", handler, useCapture) → 推荐,支持绑定多个处理函数,可控制事件阶段;

  • DOM0 级:浏览器原生支持 element.onclick = function(){}
  • DOM1 级:仅规范 DOM 结构
  • DOM2 级:新增 addEventListener → 支持多绑定、事件阶段
  • DOM3 级:新增了更多事件类型(如键盘、鼠标滚轮事件)
const btn = document.querySelector("button");
// DOM0 级
btn.onclick = function() {
  console.log("点击1");
};
btn.onclick = function() {
  console.log("点击2"); // 覆盖上一个处理函数
};

// DOM2 级
const handleClick = () => console.log("点击1");
btn.addEventListener("click", handleClick);
btn.addEventListener("click", () => console.log("点击2")); // 可绑定多个
btn.removeEventListener("click", handleClick); // 可移除

(2)事件流(事件传播机制)

事件流分为三个阶段:

  1. 捕获阶段:事件从 document 向下传播到目标元素;
  2. 目标阶段:事件到达目标元素;
  3. 冒泡阶段:事件从目标元素向上传播到 document

addEventListener 的第三个参数 useCapturetrue 表示在捕获阶段触发,false(默认)表示在冒泡阶段触发。

(3)事件对象(Event)

事件处理函数的第一个参数是事件对象,包含事件的核心信息:

  • event.target:触发事件的原始元素(事件源);
  • event.currentTarget:绑定事件的元素;
  • event.preventDefault():阻止默认行为(如表单提交、链接跳转);
  • event.stopPropagation():阻止事件传播(冒泡 / 捕获);
  • event.stopImmediatePropagation():阻止事件传播,且阻止当前元素后续的事件处理函数执行。

示例:

// 阻止链接跳转
const a = document.querySelector("a");
a.addEventListener("click", (e) => {
  e.preventDefault(); // 阻止默认跳转
  console.log("点击链接,不跳转");
});

// 事件委托(利用事件冒泡)
const ul = document.querySelector("ul");
ul.addEventListener("click", (e) => {
  if (e.target.tagName === "LI") { // 判断点击的是 li 元素
    console.log("点击了列表项", e.target.textContent);
  }
});

(4)核心优化:事件委托

利用事件冒泡,将子元素的事件绑定到父元素,减少事件绑定数量,优化性能(尤其适合动态生成的元素):

// 动态生成的 li 无需单独绑定事件,父元素 ul 委托处理
const ul = document.querySelector("ul");
ul.addEventListener("click", (e) => {
  if (e.target.classList.contains("item")) {
    console.log("点击了动态生成的列表项");
  }
});

// 动态添加 li
const li = document.createElement("li");
li.classList.add("item");
li.textContent = "动态项";
ul.appendChild(li);

(5)常见事件类型

  • 鼠标事件:clickdblclickmouseovermouseoutmousedownmouseup
  • 键盘事件:keydownkeyupkeypress
  • 表单事件:inputchangesubmitfocusblur
  • 页面事件:loadDOMContentLoaded(DOM 解析完成)、scrollresize

DOM 操作和事件处理是前端交互的基础,核心原则是 “减少 DOM 操作次数”“合理利用事件机制”,既保证交互的流畅性,又避免性能问题。

总结

  1. ES6+ 新特性核心是简化语法、补全功能,是现代前端开发的基础,重点掌握块级作用域、箭头函数、解构、async/await 等高频用法;
  2. 异步编程从回调地狱演进到 Promise/async/await,核心是让异步代码更易读、易维护,async/await 是当前最优写法;
  3. 闭包是作用域链的保留,用于封装私有变量、实现防抖节流,需注意内存泄漏;原型链是 JS 继承的底层机制,所有对象通过 __proto__ 形成继承链条;
  4. DOM 操作需注重性能(批量操作、文档碎片),事件处理核心是事件委托,利用冒泡减少绑定数量,提升页面性能。