可以用 for of 遍历 Object 吗
什么是迭代器模式
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。 ——《设计模式:可复用面向对象软件的基础》
可以说迭代器模式就是为了遍历存在的。提到遍历,大家都对那些手段耳熟能详了,下面先简单列一下各种数据类型的遍历:
遍历数组
for 循环
forEach
map
reduce
keys
values
for of
......
其中 keys、values、for of 需要 Iterator 支持,后面会介绍 Iterator
遍历 Map/Set
keys
entries
forEach
......
遍历 Object
- for in
- 先 Object.keys(obj)得到对象每个属性的数组, 然后使用数组的遍历方法遍历每个 key,就能获取 每个 key 对应的 value
Iterator 和 for of
Iterator 是 ES6 提出的一个接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
Iterator 的作用
为各种数据结构,提供一个统一的、简便的访问接口。
ES6 提出了新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
Iterator 的遍历过程
既然数组是支持 for...of 循环的,那数组肯定部署了 Iterator 接口,通过它来看看 Iterator 的遍历过程。
从图中能看出:
- Iterator 接口返回了一个有 next 方法的对象。
- 每调用一次 next,依次返回了数组中的项,直到它指向数据结构的结束位置。
- 返回的结果是一个对象,对象中包含了当前值 value 和 当前是否结束 done
用 for of 遍历 Object
回到标题中的问题,现在如何去让一个对象也可以用 for of 来遍历它呢?
根据上面讲到的内容,需要给对象也部署 Iterator 接口(其实就是在 Object.prototype 上实现一个以 Symbol.iterator 为名的 function,这个 function 返回一个有 next 方法的对象,每调用一次 next, 能够依次返回数组中的项,直到它指向数据结构的结束位置 )
function objectIterator() {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
const done = index >= keys.length;
const value = done ? undefined : this[keys[index]];
index++;
return {
done,
value,
};
},
};
}
Object.prototype[Symbol.iterator] = objectIterator;
const obj = {
key: "1",
value: "2",
};
for (const iterator of obj) {
console.log(iterator);
}
既然说对象没有 Symbol.iterator 方法,那么为什么对象也能实现解构呢?
解构的本质
在这里就不再对解构的一些语法进行讲解了,如果有需要就自行去 MDN 进行查阅吧,那里讲得更详细。
先来看一个 demo,定义有如下的对象,代码如下所示:
const obj = {
nickname: 7,
age: 18,
address: "西安",
};
let { nickname, ...reset } = obj;
通过控制台输出 nicknane 的值为 7,而 reset 的值为一个对象:
{
"age": 18,
"address": "西安"
}
通过断点调试去查看变量的值也正是发现它会复制给一个新变量,这个变量名也就是左边解构出来的字段,如下图所示:
又因为这是一个新的赋值,所以你修改解构出来的值也并不会改变源对象的值,因为这已经是一个全新的值,请看代码:
const obj = {
nickname: 7,
age: 18,
address: "西安",
};
let { nickname, ...rest } = obj;
console.log(obj);
console.log(nickname);
nickname = 777;
console.log(obj);
console.log(nickname);
看到上图的输出了吧,对象原样输出,修改 nickname 只会改变外部变量 nickname 的值,并不会改变源对象的值。
相面的对象解构,实际上是经过了这样的过程:
let nickname = obj.nickname;
剩余参数解构原理
在上面中讲到对象解构的原理确实很简单,但是剩余参数这里做的事情比较多,但是并不是说很难,通过 Babel 可以查看到这个事情的本质,先来看一个 demo:
const obj = {
nickname: 7,
age: 18,
address: "西安",
[Symbol("test")]: "111",
};
const { nickname, ...rest } = obj;
将这些代码将去放去 Babel 进行代码编译,最终代码会被编译成以下代码:
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
console.log(sourceSymbolKeys);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
console.log(target);
return target;
}
var obj = {
nickname: 7,
age: 18,
address: "西安",
[Symbol("test")]: "111",
};
var nickname = obj.nickname,
rest = _objectWithoutProperties(obj, ["nickname"]);
上面这段代码主要做的事情是有如下几点:
- 调用 _objectWithoutProperties 函数,并传入 obj 对象和前面已经解构过的值;
- 在该函数中调用 _objectWithoutPropertiesLoose 函数获取到剩余的属性或者方法;
- 因为 Object.keys 方法无法遍历 Symbol 属性,所以又继续调用 Object.getOwnPropertySymbols 方法获取剩下的 Symbol 属性;
- 最后生成一个新的对象并返回;
所以最终总结成一句话就是,对象的结构就是: 创建新变量 -> 枚举属性 -> 复制属性并赋值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南