今天有个朋友跟我说了一下之前发的文章当中统计字符串出现的次数,for in其实可以用for of代替,因为没有使用下标的必要,刚开始的时候,我还真不知道这两个到底有什么区别,于是我便决定去研究了一番。。。结果让我又涨知识了~
谈谈for in
我们平常遍历数组可能最常用的就是for循环,其次就是forEach,map,filter,some,every等等方法,只是这些方法的返回值不一样。forEach更没有返回值,使用break不能中断循环,return也不能返回到外层函数。其实for in也可以用来遍历数组。
let arr = [1,2,3];
for(let i in arr){
console.log(i, '--------', arr[i]);
}
上面可以打印出arr的每一个元素以及索引:perfect!你是不是以为这样挺完美的?那么问题来了,如果有人偷偷在Object的原型对象和Array的原型对象上添加一些方法,或者给数组添加一些其他属性,看看会发生什么情况:
Object.prototype.objMethod = function () {};
Array.prototype.arrMethod = function () {};
let arr = [1,2,3];
arr.attr = 'hi';
for(let i in arr){
console.log(i, '---------', arr[i]);
}
打印结果如下:
MDN对for in的用法定义为:以任意顺序遍历一个对象的除[Symbol]以外的可枚举属性。
我们来简单抓一下重点,理解一下这句话:
1.任意顺序遍历
也就是说for in遍历数组对象是无法保证将按照数组的索引顺序来访问数组的属性的。
举个例子:
let arr = [];
arr[2] = 'c';
arr[1] = 'b';
arr[0] = 'a';
for(let i in arr){
console.log(i, '--------', arr[i]);
}
在IE8及以下版本中,会打印出:'2' -------- 'c', '1' -------- 'b', '0' -------- 'a'。这是不是很扯淡。。。
我再举个例子:
let arr = [];
arr[5] = 10;
for(let i in arr){
console.log(i, '--------', arr[i]);
}
打印结果如下:·
2.可枚举属性
什么是可枚举属性呢?
在js的每个对象的属性都有一个叫enumerable的属性,(什么?你问我属性也有属性吗?当然啦!万物皆对象嘛~ 对象的属性当然也是对象啦~)这个属性的true和false决定了这个属性是否可以枚举(就是让一些方法访问到这个属性),那么常见的有两种,第一就是js基本包装类型自带的原型属性不可枚举,如Object,Array等;第二就是通过Object.defineProperty()方法指定enumeralbe为false的属性不可枚举。
而通过prototype添加的属性和方法默认是可枚举的(ES5之前不能定义为不可枚举),其实这也就能说得通为什么for in能遍历到原型对象上的方法,而不能遍历到Object或Array内部的方法了。
for in由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每一个元素索引都被视为一个属性。
总结一下用for in 遍历数组的缺点:
1.遍历顺序在某些情况下,可能不是按照实际数组的内部顺序。
2.它会遍历数组的所有可枚举属性,包括原型对象上的属性。
3.遍历的索引是字符串型的数字,而不是数字型的,所以不能够直接进行几何运算。(仔细观察上面for in的打印结果,i打印出来的颜色是黑色的,这是字符串型才有的特征,而数字型打印出来的颜色应该是蓝色的。)
看来for in貌似不太适合用来遍历数组啊,那怎么解决呢?for of就诞生了!
谈谈for of
我们使用for of遍历数组则完全避免了上述的一些问题。
简单来说,它可以直接获取到数组的值,只会遍历数组本身的元素,即使你在原型对象上添加了方法或自己添加了属性。
Object.prototype.objMethod = function () {};
Array.prototype.arrMethod = function () {};
let arr = [1,2,3];
arr.attr = 'hi';
for(let val of arr){
console.log(val);
}
打印结果如下:你看,被人偷偷在原型对象上加上的方法和给数组添加的属性都没有显示出来了!这是因为原型链上的对象并没有默认部署遍历器生成方法。
MDN对for of的用法定义为:在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等)上创建一个迭代循环,调用自定义迭代器,并为每一个不同属性的值执行语句
是不是又很抽象?我们再来解释一下这句话:
1.可迭代,迭代器
其实,在js中Array,Map等数据结构都存在着一个叫做迭代器(Iterator)的东西,它是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要有这个接口,就可以完成遍历操作,这个迭代器的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
那么如果我们用for of来遍历对象呢?
let obj = {
uname: 'pink',
age: 18,
sex: '男'
}
for (let val of obj) {
console.log(val);
}
打印一下:What?直接报错了!这是为啥呢?
其实对象(Object)这种数据结构是没有默认的Iterator接口的,需要自己部署这个迭代器,才会被for of循环遍历。对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪一个属性先遍历,哪一个属性后遍历是不确定的。
总结
for of适用于遍历数组,字符串,map,set等拥有迭代器的对象的集合,但是不能遍历对象,因为没有迭代器。
for in主要是为遍历对象而设计的,不适用于遍历数组。它最常用的地方应该是用于调试,可以更方便的去检查对象的属性。
文中如有错误之处欢迎指正~