对象遍历

在数组中常用的遍历方法有for for in for of这三种,前两种是遍历数组的下标,然后再通过数组下标来访问数组的元素。其实对象意识相似的。
让我们来看一下数组以及对象的代码表示。

    // Array
    let arr = ["bob", "18", "man"];

    // Object
    let obj = {
        name: "bob",
        age: "18",
        sex: "man"
    }

可以看到他们其实都是一组数据的集合,并且都是一组数据的集合,且不同元素之间都是使用逗号进行分割,唯一的区别就只是对象是使用花括号包裹着的,然后数组是使用 方括号包裹着的,再就是对象是通过属性名(字符串)进行访问,而对象是通过数组下标(数字)来进行访问的。然而还有一点就是一般我们访问对象属性的方式都是通过object.property然而实际上在javascript中会被转换成为object["property"],所以我们是不是可以这么理解,所谓对象,实际上就上一种特殊的数组。
恩,接下里在继续谈到数组的遍历问题。
实际上对于数组的遍历也可以使用for in for of这两种方法,前者嘛就是通过访问对象的属性名然后来遍历对象,但是这有一种缺陷,就是对于对象的原型他会一直访问下去,一直到object.prototype也就是一直到对象原型的终点。
当然一般我们是不希望他遍历对象的原型的,我们只希望遍历这个对象本身具有的属性,所以这是后就要用到了object的hasOwnProperty方法,任何的对象都会从原型链上继承这个方法,但是这个方法时不受到JavaScript保护的。

所有继承了 Object 的对象都会继承到 hasOwnProperty方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

这是MDN对这个方法的描述,具体可见Object.prototype.hasOwnProperty
可以使用这个方法来过滤原型链上的属性,这是很实用的。

转载于:https://www.cnblogs.com/freesfu/p/10091041.html

<think>我们有一个对象obj,它的每个属性值都是一个数组(数组里是对象)。现在需要遍历这个对象的所有属性(即每个键)以及每个属性对应的数组中的每个元素(包括元素内部的setting和isUserFriendDTO对象)。 根据用户的问题,他想遍历对象中的所有属性和值。我们可以使用多种方法。 考虑到对象的结构: obj = { key1: [ {...}, ... ], // 注意这里每个key对应的值是一个数组,数组元素是对象 key2: [ {...} ], ... } 我们需要遍历: 1. 对象的每个键(即每个选项名称,如“衔接教材快吃饭康复科”) 2. 每个键对应的数组(虽然这里每个数组只有一个元素,但结构上是数组) 3. 数组中的每个元素(对象),然后遍历这个对象的所有属性(如setting和isUserFriendDTO)以及这些属性的值(包括进一步遍历setting对象和isUserFriendDTO对象) 但是,用户的问题没有特别说明需要递归遍历嵌套对象,所以我们可以分两层: 第一层:遍历对象obj的每个键(即每个选项名称) 第二层:遍历该键对应的数组(每个数组元素是一个对象),然后遍历这个数组元素对象的所有直接属性(即setting和isUserFriendDTO)以及这些属性的值。 然而,如果用户希望遍历到最深层次(即setting对象内部的属性,以及isUserFriendDTO对象内部的属性),那么我们需要递归遍历。 根据引用[2]和引用[3]提供的递归遍历方法,我们可以实现一个递归函数来遍历任意嵌套的对象。 但是,用户的问题只是“遍历对象中的所有属性和值”,并没有明确要求递归遍历。不过,为了展示完整遍历,我们可以提供两种方式: 1. 只遍历第一层(即每个选项名称对应的数组,以及数组中的每个元素对象,但不再深入元素对象的内部属性) 2. 递归遍历所有层(包括setting对象和isUserFriendDTO对象内部的属性) 下面我们分别给出两种方案。 方案一:非递归(仅遍历到数组元素对象的直接属性) 方案二:递归(遍历所有嵌套属性) 另外,我们还需要注意,对象属性遍历的顺序问题:在JavaScript中,普通对象的属性顺序在ES6之后是有规则的(字符串键按照创建顺序,数字键按升序排列,然后按创建顺序排列符号键)。但为了安全,我们可以按照需求选择遍历方法。 根据引用[4]和[5],我们可以使用: - for...in 循环(注意会遍历原型链上的可枚举属性,所以通常配合hasOwnProperty) - Object.keys() 只遍历自身可枚举属性(不包括Symbol属性) - Object.getOwnPropertyNames() 遍历自身所有属性(包括不可枚举,但不包括Symbol) - Reflect.ownKeys() 遍历所有自身属性(包括不可枚举和Symbol) 但是,在用户提供的对象中,属性都是普通的字符串键,且没有不可枚举属性或Symbol属性,所以使用Object.keys()或for...in(配合hasOwnProperty)即可。 由于用户对象中每个键对应的值是一个数组,数组中的每个元素是一个对象,我们需要遍历这些元素对象的属性。而元素对象(如第一个键对应的数组元素)有两个属性:setting和isUserFriendDTO,这两个属性都是对象。 如果只遍历第一层,我们可以这样: 方案一(非递归): for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log("选项名称: ", key); // 获取数组 let arr = obj[key]; // 遍历数组 arr.forEach((item, index) => { console.log(` 第${index+1}个选项对象:`); // 遍历item对象的直接属性 for (let prop in item) { if (item.hasOwnProperty(prop)) { // 这里prop是'setting'或'isUserFriendDTO' console.log(` 属性: ${prop}`); // 注意:这里我们只输出整个setting对象或isUserFriendDTO对象,不深入它们的内部 console.log(` 值: `, item[prop]); } } }); } } 方案二(递归遍历所有属性): 我们可以写一个递归函数,遍历一个对象的所有属性(包括嵌套对象)。注意避免循环引用(这里没有,所以不考虑)。 递归函数: function traverseObject(subObj, indent = '') { for (let key in subObj) { if (subObj.hasOwnProperty(key)) { let value = subObj[key]; console.log(`${indent}属性: ${key}`); if (value && typeof value === 'object') { console.log(`${indent}值: [对象]`); // 递归 traverseObject(value, indent + ' '); } else { console.log(`${indent}值: ${value}`); } } } } 然后遍历obj: for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log("选项名称: ", key); let arr = obj[key]; arr.forEach((item, index) => { console.log(` 第${index+1}个选项对象:`); traverseObject(item, ' '); }); } } 但是,递归函数中,如果遇到数组,我们可能希望将数组当作对象遍历(数组也是对象),但这样会遍历数组的索引(数字键)和数组的其他属性(如果有的话)。但通常我们只关心数组元素(索引属性),并且数组的非索引属性很少。不过,在用户提供的对象中,数组里只有一个元素(对象),所以遍历数组时,我们会遍历到索引0,然后0对应的值是一个对象,再递归这个对象。 然而,我们也可以调整递归函数,使其对数组进行特殊处理:遍历数组的每个元素(使用数字索引)而不是遍历数组的所有属性(因为数组可能有length等属性,但我们通常不关心)。 但为了简单,我们这里不对数组做特殊处理,因为用户对象中的数组没有额外属性,而且我们只关心元素(索引0)。所以直接遍历数组的所有可枚举自身属性(包括索引)也可以。 不过,如果数组有多个元素,我们希望遍历每个元素,那么递归函数在遇到数组时,我们可以按数组索引顺序遍历,但注意数组也是对象,所以用for...in遍历数组会得到索引(字符串形式的数字),但顺序可能不按数字顺序?实际上,数组的索引在for...in中会按数字顺序(因为规范要求按数字顺序,但也会遍历其他非数字属性)。所以,为了安全,我们也可以使用for循环遍历数组(但递归函数是通用的,所以这里我们仍然用for...in,并配合hasOwnProperty,因为数组的索引也是自身属性)。 另外,递归函数中,我们可能会遇到null,因为typeof null也是'object',所以需要排除。 改进递归函数: function traverseObject(subObj, indent = '') { if (subObj === null) { console.log(`${indent}值: null`); return; } if (typeof subObj !== 'object') { // 非对象,直接输出(实际上这个函数应该只用于对象,但为了安全) console.log(`${indent}值: ${subObj}`); return; } for (let key in subObj) { if (subObj.hasOwnProperty(key)) { let value = subObj[key]; console.log(`${indent}属性: ${key}`); if (value && typeof value === 'object') { // 递归遍历 traverseObject(value, indent + ' '); } else { console.log(`${indent}值: ${value}`); } } } } 但是,上面的递归函数在处理基本类型值时,是在循环内部判断的。实际上,我们可以在循环内部判断值的类型。 另一种写法: function traverseObject(subObj, indent = '') { for (let key in subObj) { if (subObj.hasOwnProperty(key)) { let value = subObj[key]; console.log(`${indent}属性: ${key}`); if (value !== null && typeof value === 'object') { traverseObject(value, indent + ' '); } else { console.log(`${indent}值: ${value}`); } } } } 注意:这个函数会遍历对象的所有自身可枚举属性(不包括Symbol属性)。如果属性值是对象(非null),则递归。 但是,用户对象中可能有函数吗?目前没有,但如果有函数,typeof function 是 'function',所以不会递归,会直接输出。而函数在转换成字符串时可能输出整个函数体,这可能会很长。所以我们可以对函数特殊处理:如果是函数,只输出'[Function]'。 我们可以进一步改进: function traverseObject(subObj, indent = '') { for (let key in subObj) { if (subObj.hasOwnProperty(key)) { let value = subObj[key]; console.log(`${indent}属性: ${key}`); if (value === null) { console.log(`${indent}值: null`); } else if (typeof value === 'object') { traverseObject(value, indent + ' '); } else if (typeof value === 'function') { console.log(`${indent}值: [Function]`); } else { console.log(`${indent}值: ${value}`); } } } } 现在,我们使用这个递归函数来遍历整个obj。 但是,用户对象中,最外层的obj的属性值是一个数组,数组里面是对象。所以递归函数会先遍历数组(数组也是对象),然后遍历数组的索引(0,1,...),然后每个索引对应的值是一个对象,再遍历这个对象。 示例:对于键"衔接教材快吃饭康复科",其值是一个数组,数组有一个元素(索引0)。递归函数会这样输出: 属性: 0 -> 然后递归这个元素对象(即{setting:{...}, isUserFriendDTO:{...}}) 在元素对象中,会遍历两个属性:setting和isUserFriendDTO,然后递归这两个属性(因为它们都是对象)。 这样输出可能会很长,而且对于数组,我们更习惯看到数组索引作为属性名,但输出的是数字(字符串形式的数字)。 如果用户希望更友好的显示,可以在递归函数中对数组做特殊处理:当遇到数组时,遍历数组的每个元素(使用for循环,而不是for...in),因为for...in会遍历所有可枚举属性(包括非数字属性,而数组的非数字属性我们通常不关心)。但这样就需要在递归函数中判断是否是数组。 我们可以这样修改: function traverseObject(subObj, indent = '') { if (Array.isArray(subObj)) { // 如果是数组,则遍历数组元素 for (let i = 0; i < subObj.length; i++) { console.log(`${indent}索引: ${i}`); let value = subObj[i]; if (value === null) { console.log(`${indent}值: null`); } else if (typeof value === 'object') { traverseObject(value, indent + ' '); } else if (typeof value === 'function') { console.log(`${indent}值: [Function]`); } else { console.log(`${indent}值: ${value}`); } } } else if (subObj !== null && typeof subObj === 'object') { // 普通对象 for (let key in subObj) { if (subObj.hasOwnProperty(key)) { let value = subObj[key]; console.log(`${indent}属性: ${key}`); if (value === null) { console.log(`${indent}值: null`); } else if (typeof value === 'object') { traverseObject(value, indent + ' '); } else if (typeof value === 'function') { console.log(`${indent}值: [Function]`); } else { console.log(`${indent}值: ${value}`); } } } } else { // 基本类型 console.log(`${indent}值: ${subObj}`); } } 这样,在遇到数组时,我们使用索引遍历,输出更符合习惯。 但是,最外层的obj是一个普通对象,不是数组,所以会走普通对象的分支。然后它的每个属性值是一个数组,所以当遍历到属性值(数组)时,会进入数组分支。 然后,在数组分支中,我们遍历每个元素(通过索引),对于每个元素,如果是对象,则再次进入普通对象分支。 这样,我们就可以完整遍历了。 现在,我们使用这个递归函数来遍历obj: for (let key in obj) { if (obj.hasOwnProperty(key)) { console.log("选项名称: ", key); traverseObject(obj[key], ' '); // 注意,obj[key]是一个数组 } } 但是,这样会先输出选项名称,然后递归遍历这个数组(包括数组的每个元素)。 由于用户对象中每个数组只有一个元素,所以输出中会显示“索引:0”,然后遍历这个元素对象。 示例输出(片段): 选项名称: 衔接教材快吃饭康复科 索引: 0 属性: setting 属性: selectNo 值: XZSZ2025070909510844357 属性: selectContent 值: 衔接教材快吃饭康复科 属性: selectionFrequency 值: 3 属性: isUserFriendDTO 属性: participationName 值: 蜡笔小新 ... 这样看起来比较清晰。 但是,用户可能不需要这么详细的输出,或者只需要特定的信息。所以,我们根据需求选择。 由于用户问题没有明确,我们提供两种方法,并说明使用场景。 下面,我们给出完整的代码示例(方案二,递归遍历所有属性,并处理数组和函数)。</think>### JavaScript 遍历对象属性的完整指南 在 JavaScript遍历对象的所有属性和值(包括嵌套属性)有多种方法,以下是常用的几种实现方式: #### 1. **基本遍历:`for...in` 循环** ```javascript const obj = { name: 'John', address: { city: 'New York', zip: 10001 } }; for (let key in obj) { if (obj.hasOwnProperty(key)) { // 排除原型属性 console.log(`属性: ${key}, 值: ${obj[key]}`); } } // 输出: // 属性: name, 值: John // 属性: address, 值: [object Object] ``` > 💡 **注意**:`for...in` 会遍历所有可枚举属性(包括原型链),需配合 `hasOwnProperty` 过滤[^2][^4] #### 2. **获取自有属性:`Object.keys()`** ```javascript Object.keys(obj).forEach(key => { console.log(`${key}: ${JSON.stringify(obj[key])}`); }); // 输出: // name: "John" // address: {"city":"New York","zip":10001} ``` > ✅ 只遍历对象自身属性(不包括原型链)[^4][^5] #### 3. **深度遍历嵌套对象(递归)** ```javascript function deepTraverse(obj, indent = '') { for (let key in obj) { if (!obj.hasOwnProperty(key)) continue; const value = obj[key]; console.log(`${indent}${key}:`); if (value && typeof value === 'object') { deepTraverse(value, indent + ' '); // 递归处理子对象 } else { console.log(`${indent} ${value}`); } } } deepTraverse(obj); /* 输出: address: city: New York zip: 10001 name: John */ ``` > 🚨 重要:此方法遍历所有嵌套对象属性[^2] #### 4. **完整属性遍历(包括不可枚举和Symbol)** ```javascript const symbolKey = Symbol('id'); const obj = { [symbolKey]: 'S001', name: '张三' }; // 添加不可枚举属性 Object.defineProperty(obj, 'password', { value: '123456', enumerable: false }); // 获取所有属性(包括Symbol和不可枚举) Reflect.ownKeys(obj).forEach(key => { const keyName = typeof key === 'symbol' ? key.description : key; console.log(`${keyName}: ${obj[key]}`); }); // 输出: // password: 123456 // name: 张三 // id: S001 ``` > ⚡ 使用 `Reflect.ownKeys()` 获取最完整的属性列表[^3] #### 5. **处理特殊对象(如DOM元素)** ```javascript const element = document.getElementById('myElement'); Object.keys(element).forEach(prop => { console.log(`${prop}: ${element[prop]}`); }); ``` > 📌 适用于遍历 DOM 对象的属性[^1] ### 关键方法对比 | **方法** | 遍历范围 | 是否包含原型属性 | 是否包含Symbol | 是否包含不可枚举属性 | |------------------------|-----------------------------|------------------|----------------|----------------------| | `for...in` | 可枚举属性 | ✅ | ❌ | ❌ | | `Object.keys()` | 自身可枚举属性 | ❌ | ❌ | ❌ | | `Object.getOwnPropertyNames()` | 自身所有属性(除Symbol) | ❌ | ❌ | ✅ | | `Reflect.ownKeys()` | 自身所有属性 | ❌ | ✅ | ✅ | ### 注意事项 1. **循环引用问题**:递归遍历时需处理循环引用(如使用 `WeakSet` 记录已访问对象) 2. **性能考虑**:深度遍历大型对象可能影响性能 3. **函数处理**:`typeof function === 'function'`,需特殊处理 4. **数组处理**:数组也是对象遍历时会包含数字索引和自定义属性 > 示例中所有方法都遵循 JavaScript遍历顺序:数字键升序 → 字符串键创建顺序 → Symbol键创建顺序[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值