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的区别,希望能够给大家提供帮助。

如有错误还望斧正

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值