文章目录▼CloseOpen
- 从基础到灵活:Flex遍历Object的3种常用方法
- 避坑指南:遍历Object时最容易踩的3个雷
- 雷区1:忘记过滤继承属性,拿到“脏数据”
- 雷区2:没搞懂“可枚举属性”,导致遍历不到
- 雷区3:处理嵌套对象时,只遍历到第一层
- 最后:按场景选方法,效率翻倍
- 为什么用for…in遍历Object时会出现“toString”“constructor”这样的属性?
- Object.keys()和for…in有什么区别?
- 如何遍历包含深层嵌套的Object?
- 为什么用Object.keys()遍历不到用defineProperty定义的属性?
- 递归遍历嵌套Object会不会导致栈溢出?
- for…in循环:最基础但要“防漏网之鱼”
从基础到灵活:Flex遍历Object的3种常用方法
遍历Object的核心就是“拿到键,找到对应的值”,但不同场景需要不同的方法——有的要基础稳定,有的要代码清爽,有的要处理深层嵌套。我把最常用的3种方法按“基础→灵活”排好,每步都有代码和避坑提示。
for…in是Flex里最老牌的遍历方法,几乎所有场景都能用,但必须加“hasOwnProperty”判断——不然会把对象继承来的属性(比如Object.prototype里的“toString”)也遍历进来。
我去年帮小吴改的就是这个问题:他写了个遍历用户信息的代码,结果输出里混进了“constructor”“__proto__”这些奇怪的键。我告诉他,加一行if (obj.hasOwnProperty(key))
就行,原理很简单:hasOwnProperty会检查这个属性是不是对象自己的,不是从原型链上继承的。
比如遍历一个用户对象:
// 定义测试对象:自身属性+继承属性(来自Object.prototype)
var userObj = {
username: 'web_dev',
email: 'dev@example.com',
age: 28
};
// for...in遍历+hasOwnProperty过滤
for (var key in userObj) {
if (userObj.hasOwnProperty(key)) { // 关键!过滤继承属性
trace(键:${key} → 值:${userObj[key]}
);
// 输出:键:username→值:web_dev;键:email→值:dev@example.com;键:age→值:28
}
}
你看,加了hasOwnProperty后,继承的属性全被过滤了,输出的都是你要的“干净数据”。
经验提醒:如果你的对象没有继承任何自定义属性,或者你明确要遍历继承属性,那可以不用加——但90%的业务场景里,我们只需要自身属性,所以这行判断 “刻进DNA”。
如果不想写for…in的循环结构,或者想更简洁地处理键数组,Object.keys()绝对是更好的选择。它会直接返回对象自身可枚举属性的键名数组,然后你用forEach遍历这个数组就行——不用再写hasOwnProperty,因为Object.keys()本来就只返回自身属性。
我之前做电商项目时,需要把商品的“规格属性”(比如颜色、尺寸)的键名列出来做筛选器,用Object.keys()+forEach的写法比for…in简洁太多:
// 商品规格对象
var productSpec = {
color: ['红', '蓝', '黑'],
size: ['M', 'L', 'XL'],
material: '棉'
};
//
用Object.keys()拿到键名数组:["color", "size", "material"]
var specKeys = Object.keys(productSpec);
//
forEach遍历数组,拿到键和值
specKeys.forEach(function(key) {
var value = productSpec[key];
trace(规格:${key} → 选项:${value}
);
// 输出:规格:color→选项:红,蓝,黑;规格:size→选项:M,L,XL;规格:material→选项:棉
});
是不是比for…in清爽?而且Object.keys()返回的是数组,你还能做排序、过滤这些数组操作——比如我想把规格按“color→size→material”排序,直接在specKeys后面加.sort((a,b) => ['color','size','material'].indexOf(a)
就行,灵活度高多了。
和for…in的区别:for…in会遍历继承的可枚举属性,而Object.keys()不会;如果你的对象有继承来的属性,且不需要它们,选Object.keys()更省心。
如果你的Object里又套了Object(比如配置项、嵌套的用户信息),前面两种方法只能拿到第一层,这时候就得用递归——简单说就是“自己调用自己,深入每一层”。
我去年做过一个“系统配置”的功能,配置对象长这样:
var systemConfig = {
appName: 'FlexAdmin',
theme: 'dark',
settings: { // 嵌套对象
layout: 'flex',
sidebar: { // 深层嵌套
visible: true,
width: '200px'
}
}
};
要拿到“settings.sidebar.width”这种深层属性,递归是唯一的办法。我写的递归函数是这样的:
/
递归遍历嵌套对象
@param obj 要遍历的对象
@param parentKey 父级键名(用来拼接深层键,比如settings.sidebar)
/
function traverseNestedObj(obj, parentKey = '') {
// 遍历当前层的键
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// 拼接完整的键名:如果有父级,就用“父级.当前键”,比如settings.sidebar
var fullKey = parentKey ? ${parentKey}.${key}
key;
var value = obj[key];
// 判断值是不是对象(且不是null,因为typeof null === 'object')
if (typeof value === 'object' && value !== null) {
// 是对象→递归调用,处理深层
traverseNestedObj(value, fullKey);
} else {
// 不是对象→输出键值对
trace(键:${fullKey} → 值:${value}
);
}
}
}
}
// 调用函数,遍历systemConfig
traverseNestedObj(systemConfig);
输出结果会是这样:
完美拿到所有深层属性!递归的逻辑其实很简单:遍历当前层→如果是对象就再拆→直到不是对象为止。但要注意两个点:
value !== null
:因为typeof null
会返回“object”,如果不排除,递归会报错; 避坑指南:遍历Object时最容易踩的3个雷
我做Flex开发5年,见过太多人在遍历Object时掉坑——有的是“不知道要过滤继承属性”,有的是“没搞懂可枚举属性”,有的是“嵌套对象处理不全”。我把这些坑整理成了“避坑清单”,你看完就能绕过去。
雷区1:忘记过滤继承属性,拿到“脏数据”
这是最常见的坑,比如用for…in遍历的时候,没加hasOwnProperty,结果输出里混进“toString”“constructor”。我之前做用户信息卡的时候就踩过——把“constructor”显示在页面上,用户以为是bug,我查了半天才发现是没过滤。
解决办法:
if (obj.hasOwnProperty(key))
; 雷区2:没搞懂“可枚举属性”,导致遍历不到
你可能不知道:用Object.defineProperty
定义的属性,
默认是不可枚举的——也就是说,for…in和Object.keys()都拿不到它。我之前做权限系统时,用Object.defineProperty
定义了一个“isAdmin”属性,结果遍历的时候根本找不到,查了Adobe文档才知道是enumerable
默认是false。
比如这个例子:
var user = {};
// 用defineProperty定义不可枚举属性
Object.defineProperty(user, 'isAdmin', {
value: true,
enumerable: false // 默认就是false,可枚举属性设为true才会被遍历到
});
// 用for...in遍历:拿不到isAdmin
for (var key in user) {
trace(key); // 无输出
}
// 用Object.keys():也拿不到
var keys = Object.keys(user);
trace(keys); // 输出空数组
解决办法:
enumerable: true
; Object.getOwnPropertyNames()
——它会返回对象所有自身属性(不管可枚举不可枚举),比如:
actionscript
var allKeys = Object.getOwnPropertyNames(user);
trace(allKeys); // 输出[“isAdmin”]
雷区3:处理嵌套对象时,只遍历到第一层
很多人遇到嵌套对象就慌,其实递归真的不难——我之前教小吴的时候,他说“递归听起来好复杂”,结果我给他写了个简单的例子,他跟着改了两行就会了。
解决办法**:
传进去,拼接深层键名;
最后:按场景选方法,效率翻倍
我把这3种方法的适用场景做成了表格,你可以直接对着选:
方法 | 适用场景 | 优点 | 注意点 |
---|---|---|---|
for…in + hasOwnProperty | 需要兼容所有场景,包括继承属性 | 基础稳定,兼容所有Flex版本 | 必须加hasOwnProperty |
Object.keys() + forEach | 只需自身属性,代码要简洁 | 不用写判断,代码清爽 | 不支持继承属性 |
递归遍历 | 处理嵌套对象 | 能深入所有层级 | 避免嵌套过深(会栈溢出) |
这些方法我都在项目里用过无数次,不管是简单的用户信息,还是复杂的嵌套配置,都能搞定。你要是跟着试了,不管是解决了问题还是遇到新情况,都可以回来告诉我——比如你有没有遇到过更复杂的遍历场景?咱们一起聊聊怎么解决!
你平时做Flex开发,肯定碰到过那种“套娃”式的Object——比如系统配置里的settings
,里面嵌套着sidebar
,sidebar
里又有visible
和width
;或者用户信息里的address
,里面叠着province
、city
、street
。想把这些深层的键值都“抠”出来,用普通的for...in
或Object.keys()
根本不够——它们只能扒第一层的settings
或address
,里面的city
、visible
根本碰不着。这时候就得靠递归遍历了——说白点就是“自己调用自己,一层一层往下钻”,不管嵌套多少层,都能把键值对逐个“揪”出来。
我举个具体例子你马上就懂。比如有个systemConfig
对象:appName
是'FlexAdmin'
,theme
是'dark'
,settings
里有layout:'flex'
,sidebar
里又藏着visible:true
和width:'200px'
。递归函数的逻辑其实特简单,就两步:遍历当前层→钻深层。 函数得接收两个参数:一个是要遍历的obj
(比如systemConfig
),一个是parentKey
(父级键名,用来拼深层键,比如settings.sidebar
)。第一次调用时,parentKey
是空的,先遍历systemConfig
的每个键:
appName
:它不是对象(是字符串),直接输出完整键名(因为parentKey
空,所以就是appName
)和对应值'FlexAdmin'
; theme
:同理,输出theme
和'dark'
; settings
:先判断它是不是对象(而且得排除null
——因为typeof null
会返回object
,不排除会报错),确认是对象后,把parentKey
(空)和当前键settings
拼起来,得到fullKey='settings'
,然后自己调用自己——用settings
作为新的obj
,fullKey
作为新的parentKey
,再去遍历settings
里面的内容。 接下来遍历settings
里的layout
和sidebar
:
layout
不是对象,输出settings.layout
和'flex'
; sidebar
是对象,拼出fullKey='settings.sidebar'
,再调用函数处理sidebar
——这时候遍历sidebar
里的visible
和width
: visible
是布尔值(不是对象),输出settings.sidebar.visible
和true
; width
是字符串,输出settings.sidebar.width
和'200px'
。 你看,就这么“自己调自己”,从最外层到最深层的键值对,全被扒出来了——appName
、theme
、settings.layout
、settings.sidebar.visible
、settings.sidebar.width
,一个都没漏。我平时做项目时,处理嵌套的配置项或用户信息,全靠这招——比如上次做后台管理系统的“主题配置”,嵌套了5层的theme
对象,用递归遍历一遍就把所有配置项都列出来了,比手动写多层循环清爽多了。
其实递归的本质就是“把大问题拆成小问题”——遍历嵌套对象的大问题,拆成“遍历当前层”+“遍历深层对象”的小问题,而“遍历深层对象”又和“遍历当前层”是同一个逻辑,所以直接自己调自己就行。你试的时候记住两个小技巧:一定要排除null
(不然碰到null
会无限递归报错),拼接键名时要保留父级路径(不然深层键会丢上下文,比如只输出visible
而不是settings.sidebar.visible
)。掌握这两点,不管多深的嵌套对象,你都能“扒”得干干净净。
为什么用for…in遍历Object时会出现“toString”“constructor”这样的属性?
因为for…in会遍历对象的所有可枚举属性,包括从原型链(比如Object.prototype)继承来的属性(如“toString”“constructor”)。要过滤这些继承属性,需要在遍历中加obj.hasOwnProperty(key)判断,它会只保留对象自身的属性。
Object.keys()和for…in有什么区别?
Object.keys()会返回对象自身的可枚举属性键名数组,自动过滤继承属性,代码更简洁;而for…in默认遍历所有可枚举属性(包括继承的),需要手动加hasOwnProperty判断。如果只需处理对象自身属性,优先用Object.keys();若需兼容继承属性场景,用for…in更灵活。
如何遍历包含深层嵌套的Object?
需用递归遍历:遍历当前层键值对时,若值是对象(且非null),就再次调用自身函数处理深层对象,并拼接父级键名(如“settings.sidebar”)。这样能深入所有嵌套层级,拿到完整的键值对。
为什么用Object.keys()遍历不到用defineProperty定义的属性?
因为用Object.defineProperty定义属性时,enumerable参数默认是false(不可枚举),而Object.keys()只返回可枚举的自身属性。若需遍历这类属性,可将enumerable设为true,或用Object.getOwnPropertyNames()(返回所有自身属性,不管是否可枚举)。
递归遍历嵌套Object会不会导致栈溢出?
理论上如果对象嵌套过深(比如超过1000层),会触发栈溢出,但常规业务场景中几乎不会遇到。实际开发中,嵌套层级一般在5层以内,递归是安全的。若需处理极深嵌套,可改用迭代(循环)方式替代递归,避免栈溢出风险。