JavaScript中for...of与for...in的区别

转载请标明出处

前言

今天在学习Vue中列表渲染时,遇见了一个有趣的描述,

你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法

然后我就使用of替代了in,接着观察到Vue列表的显示没有任何变化,于是得到结论:Vue中ofin是没有区别的,只是of更符合迭代器的语法习惯。

可是我隐约的记得for...offor...in是存在区别的,于是秉承着将问题探究到底的原则,开始了对for...offor...in区别的研究。下面就是学习过程的记录,其中带着自己的思考和实践,希望能够帮助到大家。

for…of 与 for…in

想必大家都清楚,for...of是对value的遍历而for...in是对key的遍历。在此附上在Chrome Console中对该结论的实践结果

在这里插入图片描述

通过上述结果,更加确定了在使用数组for...in遍历时返回的是index,而使用for...of时返回的是value

紧接着,我又使用了对象来做遍历:

在这里插入图片描述
在使用对象for...of遍历时报了一个意料之外的错误,并且提示items is not iterable,由于此处主要是比较for...infor...of的区别,所以对于iterable的解释将在后续章节继续。

现在我得到一个结论:

for...of不能做关于对象的遍历,如果需要做对象的遍历则使用for...in来实现

紧接着我在Object的原型(Object.Prototype)上声明了一个新的叫做clone的方法,然后使用for...in尝试再次遍历:

在这里插入图片描述

通过上述截图,可以清楚的看到刚才声明的clone方法被遍历出来了,为了探明为什么会出现这中情况我使用Object.getOwnPropertyDescriptor()来查看clone与其他属性的区别:

在这里插入图片描述

区别显而易见 - cloneenumerablefalsetoStringenumerabletrue, 接下来就介绍Enumerable究竟是什么,以及为什么能够影响到for...in循环的遍历结果

Enumerable

enumerable是对象属性特征。
除了[[enumerable]],JavaScript中还存在[[Writable]][[Configurable]][[Value]][[Set]][[Get]]这些特征。

我们可以使用Object.defineProperties()或者Object.defineProperty()来定义对象属性特征。

在JavaScript中一个对象的属性分为两种,一种是数据属性另外一种是访问器属性,具体细节如下

数据(数据描述符)属性

数据属性有4个描述内部属性的特性

[[Configurable]]

表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true

[[Enumerable]]

表示该属性是否可枚举,即是否通过for...in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值true

[[Writable]]

能否修改属性的值,如果直接使用字面量定义对象,默认值为true

[[Value]]

该属性对应的值,默认为undefined

访问器(存取描述符)属性

访问器属性也有4个描述内部属性的特性

[[Configurable]]

和数据属性的[[Configurable]]一样,表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true

[[Enumerable]]

和数据属性的[[Enumerable]]一样,表示该属性是否可枚举,即是否通过for...in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true

[[Get]]

一个给属性提供 getter的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter则为 undefined。该方法返回值被用作属性值。默认为 undefined

[[Set]]

一个给属性提供 setter的方法(给对象属性设置值时调用的函数),如果没有 setter则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined

此处关于数据属性和访问器属性的描述摘自JavaScript中的Object.defineProperty()和defineProperties(), 如果希望进一步了解对象属性特征的读者可以阅读上述文章。

有了理论的支撑,我接着尝试着使用Object.defineProperty()来改变clone方法的特征,然后尝试for...in遍历:

在这里插入图片描述

可以看到随着enumerable变为false,clone方法无法再通过for...in遍历,并得到结论:
for...in遍历对象属性的成败,直接受到enumerable可枚举特征的影响

Iterator

在前面的for...of遍历对象的试验中,我们得到了items is not iterable这个错误。那么iterator是什么?对for...of遍历有何影响,将在下文中讲解

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

上述段落摘自ECMAScript 6 入门(阮一峰),通过上面的描述可知for...of无法遍历并且报错的原因就是新创建的对象上缺少了iterator接口

对于iterator接口主要通过一个next方法遍历对象,每次调用next方法就遍历对象中的下一个成员,直到所有的成员都遍历完成,然后返回结束标识符。

iterator的模拟可以参考如下代码:

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

返回的是包含valuedone两个属性的对象,其中value是当前遍历的成员值,而done则是整个遍历的标识符,当遍历完成后done就会被设置成true,整个for...of遍历就是通过判断done属性是否为true来进行的,如果为true则结束遍历,否则再调用一次next方法。

再JavaScript数据结构中,原生具备Iterator的如下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象

上述数据结构可以直径使用for...of而不需要做任何的额外工作。

如果希望让其他的数据结构也支持for...of,那么就需要定义Symbol.iterator接口,因为ES6 规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性上,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。

我添加了Symbol.iterator之后再使用for...of进行遍历:

/****** Symbol.iterator代码 ******/
items[Symbol.iterator] = function(){
	var nextIndex = 0;
	var current = this;
	var indexArr = Object.keys(this);
	return{
    	next:function(){
			if(nextIndex < indexArr.length){
				return {
					value:current[indexArr[nextIndex++]],
					done:false
                }
            }
			else{
				return {
					value:undefined,
					done:true
                }
            }
		}
	}
}

在这里插入图片描述

从我的测试结果截图可以看出Symbol.iterator确实直接影响了for...of能否正常运行。

除了for...of之外,JavaScript以下场景依旧是使用iterator来进行的遍历:

  • 解构赋值
  • 扩展运算符
  • yield*
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
  • Promise.all()
  • Promise.race()

具体细节可以参考ECMAScript 6 入门(阮一峰)

结束

本片文章旨在从更深层次理解for...infor...of的区别,希望能够给大家提供帮助。

如有错误还望斧正

<think>我们正在讨论JavaScript中的循环:for...infor...of。根据引用[1]中提到的“5.2Loops5.2.1For-In”,我们可以知道for...in循环是JavaScript中的一种循环结构。但是引用中并没有详细说明for...of,因此我们需要结合其他知识。另外,引用[5]提到了回调函数的使用,但我们当前的问题无关。引用[2]提到了一些高级特性,如生成器和符号,但也没有直接涉及循环。引用[3]是关于数据获取的副作用处理,引用[4]是JavaScript的总体介绍。因此,我们主要依靠自己的知识来回答,但注意引用[1]中提到了for-in循环,所以我们可以从那里开始。目标:解释for...infor...of循环的区别,并给出使用场景和示例。步骤:1.定义for...in循环:用于遍历对象的可枚举属性(包括继承的属性)。2.定义for...of循环:用于遍历可迭代对象(如数组、字符串、Map、Set等)的元素。3.比较两者:主要区别在于它们遍历的内容不同(属性vs元素)。4.使用场景:for...in适合遍历对象属性,for...of适合遍历数组等可迭代对象。5.注意事项:for...in会遍历原型链上的可枚举属性,通常我们使用hasOwnProperty来过滤;for...ofES6引入的,不能直接用于普通对象(除非对象实现了可迭代协议)。6.示例:分别给出两个循环的代码示例。根据引用[1]中提到的“For-In”,我们可以引用该部分作为for...in的参考。回答结构:-首先介绍两种循环。-分别详细说明。-比较差异。-示例代码。-使用场景。最后生成相关问题。注意:在回答中,如果引用了给定的引用,需要在引用的段落末尾添加引用标识。我们只引用了[1]中关于for-in循环的部分,所以可以在提到for-in循环时添加引用标识。由于用户要求使用中文,所以整个回答用中文。</think>### JavaScript 中 `for...in` `for...of` 循环的区别使用场景 #### 1. **核心区别** | **特性** | `for...in` 循环 | `for...of` 循环 | |----------------|----------------------------------|----------------------------------| | **遍历对象** | 对象的 **可枚举属性**(包括继承属性) | 可迭代对象的 **元素值**(如数组、Map) | | **返回值** | 属性名(键名) | 元素值(键值) | | **适用对象** | 普通对象、数组(不推荐) | 数组、字符串、Map、Set 等可迭代对象 | | **原型链属性** | 默认遍历(需用 `hasOwnProperty` 过滤) | 不遍历原型链属性 | | **引入版本** | ECMAScript 1 | ECMAScript 6 (ES6) | #### 2. **使用场景示例** **场景 1:遍历对象属性(`for...in`)** 适用于获取对象的键名(如配置项、键值对数据): ```javascript const user = { name: "Alice", age: 30, role: "Admin" }; for (const key in user) { if (user.hasOwnProperty(key)) { // 过滤继承属性 console.log(`属性名: ${key}, 值: ${user[key]}`); } } // 输出: // 属性名: name, 值: Alice // 属性名: age, 值: 30 // 属性名: role, 值: Admin ``` **场景 2:遍历数组元素(`for...of`)** 适用于直接获取数组、字符串等可迭代对象的值: ```javascript const colors = ["red", "green", "blue"]; // 直接获取元素值 for (const color of colors) { console.log(color); } // 输出:red, green, blue // 字符串遍历 for (const char of "Hello") { console.log(char); } // 输出:H, e, l, l, o ``` **场景 3:遍历 Map/Set(`for...of`)** 适用于 ES6 新增数据结构: ```javascript const map = new Map([["a", 1], ["b", 2]]); for (const [key, value] of map) { console.log(`${key} => ${value}`); } // 输出:a => 1, b => 2 const set = new Set([10, 20, 30]); for (const num of set) { console.log(num); } // 输出:10, 20, 30 ``` #### 3. **常见误区** - **数组遍历避免用 `for...in`**: 会遍历数组的**非数字属性**(如添加的自定义属性)和**原型链方法**: ```javascript Array.prototype.customMethod = () => {}; const arr = [1, 2, 3]; arr.foo = "bar"; for (const key in arr) { console.log(key); // 输出:0, 1, 2, foo, customMethod(意外结果!) } ``` - **普通对象不能用 `for...of`**: 除非对象实现迭代器协议(如添加 `[Symbol.iterator]` 方法)。 #### 4. **性能兼容性** - **性能**:`for...of` 通常比 `for...in` 更快(因无需检查原型链)。 - **兼容性**:`for...in` 支持所有浏览器;`for...of` 需 ES6+ 环境(IE 不支持)。 --- ### 相关问题 1. 如何用 `for...in` 安全遍历对象属性而不包含继承属性? 2. 普通对象如何实现迭代器协议以支持 `for...of` 循环? 3. 在遍历数组时,`for...of` 传统的 `for` 循环有何性能差异? 4. `for...of` 循环能否遍历类数组对象(如 `arguments` 或 DOM 集合)? 5. 如何用 `for...of` 同时获取数组的索引和值? [^1] [^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值