面试题:for-in和for-of的区别?

485 阅读4分钟

1.定义

for-in语句用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),返回的是索引值,如:数组返回下标,对象返回key值。

for-of循环是ES6的新的语法,用来遍历集合,返回的集合本身的元素,如:数组返回元素。

2.代码解析

我们用一段代码展示一些细节不同,如下面代码可以清楚的看到: for-in遍历的是数组下标,for-of遍历的是数组元素(这是本质区别);但是for-in却可以遍历出往原型上添加的属性,for-of却不行,这一点请注意!for-of遍历的是数组只具有数字索引的属性,这一点跟for-in也不一样。

 Array.prototype.d='d'
 const arr = ['a','b','c']
 arr.Name = 'yj'
  for(let i in arr){
     console.log(i) // 0 1 2 Name d
 }
 for(let i of arr ){
    console.log(i) //a b c
 }

还有一点对于for-in语句需要补充的,就是关于key值是数子属性的对象,我们来看一下下面的代码就能看出规律: key值不是字符串会转化为字符串,对象中数字属性会被优先遍历,且按照顺序遍历,这是因为ECMAScript 规范定义了:数字属性应按照索引值的大小升序排列,字符串属性根据创建时的顺序排列

function Foo () {
    this[100] = 'test-100' 
    this['b'] = 'test-b'
    this[2] = 'test-2'
    this['A'] = 'test-A'
    this[1] = 'test-1'
    this['a'] = 'test-a'
    this['B'] = 'test-B'
}
let bar = new Foo()
console.log(bar) 

QQ图片20220204135547.png

下面我们研究一下为什么for-in可以遍历对象而for-of却不可以? 原因就是对象里面不具备Symbol.iterator(迭代器)属性,而只有具备该属性的数据结构才能被for-of遍历。所以我们可以自己手写一个迭代器属性,添加到对象中,看看这样对象能不能被遍历呢?

下面的代码就是原生迭代器的原理:iteration为迭代器,又叫遍历器。它的作用是给不同的数据结构提供统一的遍历访问机制,这种机制可以使得数据结构里的成员按照顺序依次被遍历访问。其实iteration遍历的时候会先生成一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。这个对象里面有一个next方法,然后调用该next方法,移动指针就会使得指针指向数据结构中的下一个元素。判断数据结构是否遍历完是有done属性决定,遍历的数据结构的当前成员保存在value中。这样就可以完成每一次遍历。

//原生的迭代器原理
function createIterator(items){
    var i=-1;
    return {
        next:function(){
            i++
            var done = i >=items.length
            return {
                done:done,
                value:items[i]
            }
        }
    }
}
  var myintertor =createIterator(['a','b','c'])
    console.log(myintertor.next()); //{ done: false, value: 'a' }
    console.log(myintertor.next()); //{ done: false, value: 'b' }
    console.log(myintertor.next()); //{ done: false, value: 'c' }
    console.log(myintertor.next()); //{ done: true, value: undefined }

但是下面代码还是会报错,因为该myintertor变量还是不具备迭代器属性,只是具备next方法而已。

for(let value of intertor ){
    console.log(value); //TypeError: myintertor is not iterable
}

下面我们给一个对象增添一个自己写的迭代器属性:如下面代码中让obj具备这个属性,这个属性就可以被遍历,但是看到输出结果就发现,遍历的却是对象里面的这个迭代器属性,自己的属性(如:value:1)还是无法遍历到,所以我们可以得出结论:for-of遍历的是Symbol.iterator这个属性,其实看到手写的迭代器createIterator就能发现,里面有个重要的属性length,这是决定是否可以遍历与是否结束遍历的关键属性,而对象却没有这个属性,所以无法遍历,就算添加了也只是遍历的是createIterator([1,2,3])里面的数组值,和对象自己完全没有关系。

var obj = {
    value:1
}
obj[Symbol.iterator] = function () {
    return createIterator([1,2,3])
}
for(let value of obj){
    console.log(value);//1 2 3
}

既然原生迭代器原理都已经知道了,下面我们来手写一个for-of原理的代码吧:最重要的一点就是判断是否遍历的数据结构是否具有Symbol.iterator属性就行!

function forOf(obj , cb){
    let iterable,result;
    if(typeof obj[Symbol.iterator] !=="function") throw new TypeError (result + 'is not iterable')
    iterable = obj[Symbol.iterator]()
    result = iterable.next() 
    while(!result.done){
        cb(result.value)
        result = iterable.next()
    }
}
forOf([1,2,3],(item)=>{
    console.log(item); //1 2 3
})

3.总结

for in:

  1. 循环返回的是数据结构的键名
  2. 遍历数组返回的是数组下标
  3. 不仅可以遍历已有的值,还可以遍历原型上的键,以及手动添加的键名
  4. 特殊情况下,遍历的顺序不会按照对象属性的顺序进行

for of:

  1. 不能遍历对象(没有Symbol.iterator属性的数据结构)
  2. 遍历数组返回具有数字索引的属性