文章目录
-
- 一、对象遍历新方式:Object.keys()
-
- [1.1 传统方式:for...in](#1.1 传统方式:for...in)
- [1.2 现代方式:Object.keys() + 数组方法](#1.2 现代方式:Object.keys() + 数组方法)
- [1.3 实战:按条件过滤后遍历](#1.3 实战:按条件过滤后遍历)
- 二、展开运算符:拷贝与合并的利器
-
- [2.1 核心概念:引用 vs 拷贝](#2.1 核心概念:引用 vs 拷贝)
- [2.2 拷贝数组与对象](#2.2 拷贝数组与对象)
- [2.3 合并数组与对象](#2.3 合并数组与对象)
- [2.4 浅拷贝注意事项](#2.4 浅拷贝注意事项)
- [三、JSON 序列化与反序列化](#三、JSON 序列化与反序列化)
-
- [3.1 普通对象 vs JSON 格式](#3.1 普通对象 vs JSON 格式)
- [3.2 序列化:JSON.stringify()](#3.2 序列化:JSON.stringify())
- [3.3 反序列化:JSON.parse()](#3.3 反序列化:JSON.parse())
- [四、Web 存储:localStorage 与 sessionStorage](#四、Web 存储:localStorage 与 sessionStorage)
-
- [4.1 为什么需要 Web 存储?](#4.1 为什么需要 Web 存储?)
- [4.2 两大存储机制对比](#4.2 两大存储机制对比)
- [4.3 基本使用](#4.3 基本使用)
- [4.4 适用场景](#4.4 适用场景)
- 五、Promise:告别回调地狱
-
- [5.1 问题起源:回调地狱](#5.1 问题起源:回调地狱)
- [5.2 同步 vs 异步](#5.2 同步 vs 异步)
- [5.3 Promise 基础用法](#5.3 Promise 基础用法)
- [5.4 链式调用:消灭回调地狱](#5.4 链式调用:消灭回调地狱)
- 六、Async/Await:异步编程的终极方案
-
- [6.1 从 Promise 到 Async/Await](#6.1 从 Promise 到 Async/Await)
- [6.2 Async/Await 三条核心规则](#6.2 Async/Await 三条核心规则)
- [6.3 Async/Await 错误处理](#6.3 Async/Await 错误处理)
- [七、ES Module:模块化导入导出](#七、ES Module:模块化导入导出)
-
- [7.1 按需导出与导入(Named Export)](#7.1 按需导出与导入(Named Export))
- [7.2 默认导出与导入(Default Export)](#7.2 默认导出与导入(Default Export))
- [7.3 默认导出与按需导出对比](#7.3 默认导出与按需导出对比)
- [7.4 混合使用](#7.4 混合使用)
- 全文总结
- 核心知识点复盘
- 常见问题与避坑指南
学习 Vue 之前,这些 JavaScript 现代特性你必须先掌握 ------ 从对象遍历到异步编程,一篇文章系统梳理。
一、对象遍历新方式:Object.keys()
1.1 传统方式:for...in
过去遍历一个对象,只能使用 for...in 循环:
js
const obj = {
id: 100001,
name: 'Bit',
age: 9,
address: '陕西省西安市高新区沣惠南路34号'
}
// 传统方式:for-in 遍历对象
for (let key in obj) {
console.log(key, obj[key])
}
这种方式虽然能用,但存在两个问题:
- 会遍历原型链上的属性 :
for...in不仅遍历自身属性,还会遍历继承来的可枚举属性,需要用hasOwnProperty过滤。 - 无法利用数组方法:遍历过程中无法对 key 做过滤、映射等操作,灵活性差。
1.2 现代方式:Object.keys() + 数组方法
Object.keys() 返回对象自身可枚举属性 组成的数组,拿到数组后我们就可以使用 forEach、filter、map 等数组方法,灵活性大幅提升:
js
const obj = {
id: 100001,
name: 'Bit',
age: 9,
address: '陕西省西安市高新区沣惠南路34号'
}
// Object.keys() 返回的是数组:['id', 'name', 'age', 'address']
Object.keys(obj).forEach((key) => {
console.log(key, obj[key])
})
核心思路 :
Object.keys()把对象的 key 变成了数组,数组具备丰富的链式操作方法,让对象遍历从"只能傻傻遍历"升级为"可以灵活加工后再遍历"。
1.3 实战:按条件过滤后遍历
假设有一个需求:只遍历对象中以字母 a 开头的属性。
js
const obj = {
id: 100001,
name: 'Bit',
age: 9,
address: '陕西省西安市高新区沣惠南路34号'
}
// 链式调用:先过滤,再遍历
Object
.keys(obj) // ['id', 'name', 'age', 'address']
.filter((key) => key.startsWith('a')) // ['age', 'address']
.forEach(key => {
console.log(key, obj[key]) // age 9, address ...
})
代码思路解析:
| 步骤 | 方法 | 结果 |
|---|---|---|
| ① 获取 key 数组 | Object.keys(obj) |
['id', 'name', 'age', 'address'] |
| ② 过滤以 a 开头 | .filter(...) |
['age', 'address'] |
| ③ 遍历输出 | .forEach(...) |
输出对应 value |
面试重点 :
Object.keys()、Object.values()、Object.entries()三者对比 ------keys返回键数组,values返回值数组,entries返回键值对二维数组。它们都只返回自身可枚举属性,不会拿到原型链上的属性。
二、展开运算符:拷贝与合并的利器
展开运算符 ... 是 ES6 中最常用的语法之一,核心功能就两个:拷贝 和 合并。
2.1 核心概念:引用 vs 拷贝
在讲展开运算符之前,必须先理解一个关键区分:
js
const arr1 = [11, 22, 33]
// ❌ 这是"引用",不是"拷贝"------两者指向同一块内存,会相互影响
const arr2 = arr1
arr2.push(44)
console.log(arr1) // [11, 22, 33, 44] → arr1 也被改了!
// ✅ 这才是"拷贝"------值一样,但地址不同,彼此独立
const arr3 = [...arr1]
arr3.push(55)
console.log(arr1) // [11, 22, 33, 44] → arr1 不受影响
底层原理 :JavaScript 中,对象和数组是引用类型 。直接赋值
arr2 = arr1只是把内存地址复制了一份,两个变量指向同一个数据。展开运算符...创建的是浅拷贝,会开辟新内存存放数据,新旧数据互不影响。
2.2 拷贝数组与对象
js
// 拷贝数组
const arr1 = [11, 22, 33]
const arr2 = [...arr1] // 展开 arr1 的所有元素,包进新数组
arr2.push(44)
console.log(arr1) // [11, 22, 33] ------ 原数组不变
// 拷贝对象
const obj1 = {
id: 10001,
name: 'Bit',
age: 9
}
const obj2 = {
...obj1 // 展开 obj1 的所有属性,包进新对象
}
obj2.age = 10
console.log(obj1) // { id: 10001, name: 'Bit', age: 9 } ------ 原对象不变
2.3 合并数组与对象
合并是展开运算符的另一个高频使用场景:
js
// 合并数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr = [...arr1, ...arr2] // 两个数组的元素平铺进新数组
console.log(arr) // [1, 2, 3, 4, 5, 6]
// 合并对象
const obj1 = {
name: 'Jack',
height: 176
}
const obj2 = {
height: 180, // 注意:此属性与 obj1 重名
age: 18
}
const obj = {
...obj1,
...obj2 // 同名属性,后面的会覆盖前面的
}
console.log(obj) // { name: 'Jack', height: 180, age: 18 }
易错点 :合并对象时如果存在同名属性 ,后面展开的会覆盖前面的。这和
Object.assign()的行为一致,实际开发中常用于合并默认配置和用户配置。
2.4 浅拷贝注意事项
展开运算符执行的是浅拷贝(Shallow Copy),只复制一层。如果对象内部还嵌套了对象,内层仍然是引用:
js
const obj1 = {
name: 'Bit',
info: { phone: '138xxxx' } // 嵌套对象
}
const obj2 = { ...obj1 }
obj2.info.phone = '139xxxx' // 修改内层对象
console.log(obj1.info.phone) // '139xxxx' ------ 内层依然是引用,被影响了!
避坑 :需要完全独立的深拷贝时,可以用
JSON.parse(JSON.stringify(obj))或structuredClone(),但要注意前者的局限性(无法处理函数、undefined、循环引用等)。
三、JSON 序列化与反序列化
JSON(JavaScript Object Notation)是前后端数据交互的事实标准。掌握序列化和反序列化是每个前端工程师的基本功。
3.1 普通对象 vs JSON 格式
js
// 普通 JavaScript 对象 ------ 属性名可以不加引号
const obj = {
id: 10001,
name: 'Bit',
age: 9
}
// JSON 格式 ------ 属性名必须用双引号包裹(这是规范,不是可选项)
// {
// "id": 10001,
// "name": "Bit",
// "age": 9
// }
两者的核心区别:
| 对比维度 | JavaScript 对象 | JSON |
|---|---|---|
| 属性名引号 | 可以不写(标识符规则) | 必须双引号 |
| 值类型 | 任意 JS 类型 | 仅 String/Number/Boolean/Array/Object/null |
| 函数 | 可以包含 | ❌ 不支持 |
| undefined | 可以包含 | ❌ 不支持 |
| 用途 | 程序内使用 | 数据传输与存储 |
3.2 序列化:JSON.stringify()
把 JavaScript 对象转为 JSON 字符串,用于发送给后端或存入存储:
js
const obj = {
id: 10001,
name: 'Bit',
age: 9
}
// 序列化 → JSON 字符串
console.log(JSON.stringify(obj))
// 输出:'{"id":10001,"name":"Bit","age":9}'
3.3 反序列化:JSON.parse()
把 JSON 字符串还原为 JavaScript 对象,用于解析后端返回的数据:
js
const jsonStr = '{"id": 10001, "name": "Bit", "age": 9}'
// 反序列化 → JavaScript 对象
console.log(JSON.parse(jsonStr))
// 输出:{ id: 10001, name: 'Bit', age: 9 }
面试重点 :
JSON.parse()遇到不合法的 JSON 字符串会抛出异常 ,实际开发中务必用try...catch包裹,防止程序崩溃。
四、Web 存储:localStorage 与 sessionStorage
4.1 为什么需要 Web 存储?
浏览器端的存储需求无处不在:用户登录状态、表单草稿、主题偏好、购物车数据......Web Storage API 提供了比 Cookie 更现代、更便捷的客户端存储方案。

4.2 两大存储机制对比
| 特性 | localStorage | sessionStorage |
|---|---|---|
| 生命周期 | 永久存储,除非手动删除 | 会话级别,关闭标签页即清除 |
| 存储容量 | 约 5MB | 约 5MB |
| 作用域 | 同源所有标签页共享 | 仅当前标签页 |
| 与服务器通信 | 不自动发送 | 不自动发送 |
| API | 相同(setItem/getItem/removeItem) |
相同 |
4.3 基本使用
js
// ========== 存储数据 ==========
// localStorage 和 sessionStorage 的 API 完全一致
// 存 ------ 值必须是字符串
localStorage.setItem('token', 'abc123')
localStorage.setItem('theme', 'dark')
// 存对象需要先序列化
const userInfo = { id: 10001, name: 'Bit', age: 9 }
localStorage.setItem('user', JSON.stringify(userInfo))
// ========== 读取数据 ==========
const token = localStorage.getItem('token') // 'abc123'
const theme = localStorage.getItem('theme') // 'dark'
// 取对象需要反序列化
const userStr = localStorage.getItem('user')
const user = JSON.parse(userStr) // { id: 10001, name: 'Bit', age: 9 }
// ========== 删除数据 ==========
localStorage.removeItem('token') // 删除单个
localStorage.clear() // 清空全部
关键步骤 :存储对象时必须
JSON.stringify(),取出时用JSON.parse()还原。忘记序列化会导致对象被存为"[object Object]"字符串,这是高频踩坑点。
4.4 适用场景
- localStorage:记住登录状态、用户偏好设置、主题选择、草稿箱等需要长期保留的数据。
- sessionStorage:表单多步骤填写时的临时数据、页面刷新恢复状态等仅当前会话需要的数据。
五、Promise:告别回调地狱
异步编程是 JavaScript 的核心特性之一。从回调函数到 Promise,再到 async/await,这是前端开发者必须跨越的认知阶梯。
5.1 问题起源:回调地狱
先看一个经典需求:延迟 2 秒输出 1,再延迟 1 秒输出 2,再延迟 1 秒输出 3。
js
// ❌ 回调地狱 ------ 层层嵌套,难以阅读和维护
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 1000)
}, 1000)
}, 2000)
这就是回调地狱(Callback Hell):多个异步操作需要按顺序执行时,回调函数层层嵌套,代码呈倒金字塔形状,可读性极差。
5.2 同步 vs 异步
在深入 Promise 之前,先厘清两个核心概念:
- 同步代码:串行执行,前面的代码执行完毕,后面的才会开始。会阻塞后续代码。
- 异步代码:不会阻塞后续代码执行,异步操作在后台运行,完成后通过回调通知结果。
js
// 同步示例
console.log('A')
console.log('B') // 一定在 A 之后输出
// 异步示例
console.log('A')
setTimeout(() => console.log('B'), 1000) // B 在 1 秒后才输出
console.log('C') // C 不会等 B,会立刻输出
// 输出顺序:A → C → B
5.3 Promise 基础用法
Promise 是一个容器,包裹着未来才会完成(或失败)的异步操作:
js
// 创建一个 Promise 实例
const p = new Promise((resolve, reject) => {
// 在这里包装异步代码:定时器、Ajax 请求等
// 成功 → 调用 resolve(成功的数据)
// 失败 → 调用 reject(失败的原因)
setTimeout(() => {
// resolve('ok') // 模拟成功
reject('error') // 模拟失败
}, 2000)
})
// 消费 Promise
p.then(
(msg) => {
console.log('成功了', msg) // resolve 走这里
},
(err) => {
console.log('失败了', err) // reject 走这里
}
)
Promise 三种状态 :
pending(进行中)→fulfilled(已成功)→rejected(已失败)。状态一旦改变就不可逆转,这是 Promise 的核心特性。
5.4 链式调用:消灭回调地狱
Promise 的真正威力在于链式调用 ------ 每个 .then() 都可以返回一个新的 Promise,让异步任务像同步代码一样从上往下写:
js
/**
* 封装延迟输出函数
* @param {number} duration 延迟的毫秒数
* @param {*} n 延迟后输出的值
* @returns {Promise} 返回 Promise 实例
*/
function delay(duration, n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(n) // 延迟结束后,将 n 作为成功结果传递出去
}, duration)
})
}
// 链式调用:扁平化写法,彻底消灭嵌套
delay(2000, 1)
.then((n1) => {
console.log(n1) // 2秒后输出 1
return delay(1000, 2) // 返回新的 Promise,交给下一个 then
})
.then((n2) => {
console.log(n2) // 再过1秒后输出 2
return delay(1000, 3) // 继续返回 Promise
})
.then((n3) => {
console.log(n3) // 再过1秒后输出 3
})
关键步骤解析:
delay(2000, 1)返回一个 Promise,2 秒后 resolve 出1。- 第一个
.then()拿到n1(值为1),输出后 return 一个新的 Promise。 - 第二个
.then()等待这个新 Promise resolve 后拿到n2,依此类推。 - 每个
.then()中都 return 一个 Promise,这是链式调用得以串联的关键。
核心思路:把异步操作的"嵌套"变成了"平铺",代码从上到下线性阅读,逻辑一目了然。这就是 Promise 解决回调地狱的本质。
六、Async/Await:异步编程的终极方案
Async/Await 是 Promise 的语法糖,它让异步代码写起来像同步代码,可读性达到最优。
6.1 从 Promise 到 Async/Await
还是同一个需求,对比三种写法的差异:
js
// 封装延迟输出函数(和上一节相同)
function delay(duration, n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(n)
}, duration)
})
}
// ========== 写法对比 ==========
// ① 回调地狱 ------ 嵌套地狱
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => console.log(3), 1000)
}, 1000)
}, 2000)
// ② Promise 链式调用 ------ 扁平但仍有 .then 链
delay(2000, 1)
.then(n1 => { console.log(n1); return delay(1000, 2) })
.then(n2 => { console.log(n2); return delay(1000, 3) })
.then(n3 => { console.log(n3) })
// ③ Async/Await ------ 像写同步代码一样写异步 ✅ 最优解
async function log() {
const n1 = await delay(2000, 1)
console.log(n1)
const n2 = await delay(1000, 2)
console.log(n2)
const n3 = await delay(1000, 3)
console.log(n3)
}
log()
6.2 Async/Await 三条核心规则
这是理解 Async/Await 最关键的三个知识点:
await放在 Promise 实例前 ,它的返回值就是 Promise 的resolve参数值。await所在函数必须用async修饰,否则语法报错。await后面的代码会等待 ,也就是前面的await执行完毕了,才会执行后续的await------让异步代码按同步方式串行执行。
6.3 Async/Await 错误处理
js
async function fetchData() {
try {
const n1 = await delay(2000, 1)
console.log(n1)
// 如果某个 await 的 Promise 被 reject,会抛出异常
} catch (err) {
console.log('出错了:', err)
}
}
面试重点 :
async函数本身返回的是一个 Promise 。await只能在async函数内部使用(ES2022 之后在模块顶层也可以使用 top-level await)。
七、ES Module:模块化导入导出
模块化是工程化的基石。Vue 项目中的 import / export 语法,来自 ES6 官方模块化标准。
7.1 按需导出与导入(Named Export)
一个模块可以有多个按需导出:
js
// ========== math.js ------ 定义并导出 ==========
// 定义求和函数并按需导出
export function add(x, y) {
return x + y
}
// 定义求差函数并按需导出
export function sub(x, y) {
return x - y
}
// ========== 其他文件中导入 ==========
// 按需导入 ------ 变量名必须与导出时一致,用花括号包裹
import { add, sub } from './utils/math.js'
console.log(add(33, 18)) // 51
console.log(sub(33, 18)) // 15
易错点 :按需导入的变量名必须和导出时的名字完全一致 ,并且用花括号
{}包起来。
7.2 默认导出与导入(Default Export)
每个模块最多只有一个默认导出:
js
// ========== min.js ------ 默认导出 ==========
// 定义求最小值的函数并默认导出
export default function min(m, n) {
return m > n ? n : m
}
// 注意:export default 在一个模块中最多出现一次!
// ========== 使用方导入 ==========
// 默认导入 ------ 变量名可以自定义,但建议与模块功能保持一致
import min from './utils/min.js'
console.log(min(12, 45)) // 12
7.3 默认导出与按需导出对比
| 对比维度 | 默认导出 export default |
按需导出 export |
|---|---|---|
| 每个模块数量 | 最多 1 个 | 可以有多个 |
| 导入时变量名 | 可以自定义 | 必须和导出时一致 |
| 导入语法 | import xxx from |
import { xxx } from |
| 典型场景 | 模块主体功能(如一个 Vue 组件) | 工具函数集合(如多个辅助函数) |
7.4 混合使用
一个模块可以同时包含默认导出和按需导出:
js
// utils.js
export default function main() { /* ... */ } // 默认导出
export function helper1() { /* ... */ } // 按需导出
export function helper2() { /* ... */ } // 按需导出
// 导入时
import main, { helper1, helper2 } from './utils.js'
全文总结
本文系统梳理了学习 Vue.js 之前必须掌握的 7 个 ES6+ 核心特性 ,按照"先概念 → 再原理 → 再实战"的逻辑层层递进:
- Object.keys() 将对象 key 转为数组,结合数组方法实现灵活遍历;
- 展开运算符
...实现数组/对象的浅拷贝与合并,理解引用 vs 拷贝的本质区别; - JSON 序列化/反序列化 是前后端数据交互的桥梁,
JSON.stringify()和JSON.parse()必须熟练; - Web 存储(localStorage / sessionStorage)提供浏览器端持久化能力,配合 JSON 实现对象存取;
- Promise 解决回调地狱,通过链式调用将异步嵌套变为扁平化代码;
- Async/Await 在 Promise 基础上进一步简化,让异步代码写起来像同步代码;
- ES Module 模块化系统,是 Vue 组件化开发的基础语法。
这些知识点不是孤立的,它们在实际开发中环环相扣------你用 import 引入模块,用 async/await 发请求,用 JSON.parse 解析数据,用展开运算符合并配置,用 Object.keys 处理数据遍历。掌握它们,学习 Vue 的开发体验会顺畅很多。
核心知识点复盘
| 序号 | 知识点 | 核心要点 | 一句话总结 |
|---|---|---|---|
| 1 | Object.keys() | 返回自身可枚举属性名数组 | 把对象 key 变数组,解锁数组方法链 |
| 2 | 展开运算符 | 浅拷贝 + 合并 | ... 展开内容,包进新容器 |
| 3 | JSON.stringify/parse | 序列化/反序列化 | 对象 ↔ 字符串互转,前后端数据桥梁 |
| 4 | Web Storage | setItem/getItem/removeItem | 存对象需序列化,取对象需反序列化 |
| 5 | Promise | pending → fulfilled/rejected | 包裹异步操作,链式调用消除嵌套 |
| 6 | Async/Await | async 函数 + await Promise | Promise 语法糖,让异步代码同步写 |
| 7 | ES Module | export/export default + import | 按需导入名字必须一致,默认导入可自定义 |
常见问题与避坑指南
❓ Q1:Object.keys() 和 for...in 到底有什么区别?
答 :Object.keys() 只返回自身可枚举属性 ,不会遍历原型链;for...in 会遍历原型链上的可枚举属性,需要用 hasOwnProperty 过滤。实际开发中优先用 Object.keys()。
❓ Q2:展开运算符是深拷贝还是浅拷贝?
答 :浅拷贝 。只复制第一层,内层嵌套对象仍是引用。需要深拷贝时,可以用 JSON.parse(JSON.stringify(obj)) 或 structuredClone(),但要注意前者的局限性(无法处理 function、undefined、Date、循环引用等)。
❓ Q3:localStorage 存对象为什么是 "object Object"?
答 :因为 localStorage.setItem() 的值必须是字符串,直接存对象会调用 .toString(),结果就是 "[object Object]"。正确做法是先用 JSON.stringify() 序列化后再存,取出时用 JSON.parse() 还原。
❓ Q4:Promise 的 .then() 中 return 和不 return 有什么区别?
答 :return 一个 Promise(或普通值)会传递给下一个 .then(),形成链式传递;不 return 则下一个 .then() 收到的值是 undefined。链式调用的关键在于每个 .then() 必须 return 数据。
❓ Q5:async/await 报错怎么处理?
答 :用 try...catch 包裹 await 调用。如果 Promise 被 reject,异常会被 catch 捕获。另外,async 函数本身返回 Promise,也可以在调用方用 .catch() 处理。
❓ Q6:按需导入能不能给变量起别名?
答 :可以,使用 import { add as sum } from './math.js' 语法。这样 add 函数就能以 sum 的名字在当前文件中使用。但默认导入不需要别名语法,直接改名字即可。
❓ Q7:这些前置知识和 Vue 有什么关系?
答:
- Vue 组件用
export default导出,用import引入。 - Vue 的
data()、methods返回对象,经常用到展开运算符合并。 - 调用后端 API 用
axios/fetch,请求响应处理依赖 Promise 和 async/await。 - 数据格式化、配置合并、对象遍历贯穿 Vue 开发的日常编码。
📝 本文基于 Vue.js 前置知识体系整理,适合 Vue 初学者系统学习 ES6+ 核心特性,也可作为复习参考。