在vue模板的列表渲染中,有这样的语法:javascript
<li v-for="item in list"/> <li v-for="(item,idx) in list"/>
在迭代中获取一个idx很方便,可是在js的for ... of...循环中,咱们能够拿到数组中的一项,可是获得索引却要费一番功夫。而传统的基于索引的循环,则要多写一些代码。那么有没有方法,让咱们在for... of 循环中,既能拿到数组的项,又能拿到索引呢?以下所示:html
for (let [item, idx] of list) { ... }
答案是确定的,就是要借助今天的主角,迭代协议了。vue
迭代器协议(iterator protocol) 定义了一种标准的方式来产生一个有限或无限序列的值,而且当全部的值都已经被迭代后,就会有一个默认的返回值。当一个对象只有知足下述条件才会被认为是一个迭代器,它实现了一个 next() 的方法,而next方法会返回这样一个对象:{value, done},value表示本次迭代的值,done表示迭代是否结束。根据这个协议,咱们能够写一个迭代器:java
let iterator = { from: 1, to: 10, next() { if (this.from < 10) { return {done: false, value: this.from++} } return {done: true} } }
因而咱们能够手动调用next()来迭代:数组
iterator.next() // {done: false, value: 1} iterator.next() // {done: false, value: 2} iterator.next() // {done: false, value: 3}
或者使用while循环来迭代:函数
while(true) { let next = iterator.next() if(next.done) break // 迭代结束 console.log(next.value) }
上面的代码尽管实现了迭代,却不能使用for .. of .. 或者 [...]
这样的内置语法来迭代。要实现更天然的迭代方式,咱们还须要了解可迭代对象协议:为了变成可迭代对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性this
这个也很好理解,由于for...of这样的语法是为可迭代对象设计的。那么什么是可迭代对象呢?打个比方,假如你想35岁之后跑滴滴,那么首先你必须有一辆车,这时你就被称为可跑滴滴的人
。同理,若是一个对象有了Symbol.iterator 的方法,并且这个方法调用后会返回一个迭代器,这时,这个对象就成了一个可迭代对象。下面咱们来看看怎么使一个普通对象变成可迭代的对象。prototype
// 普通对象 let obj = { a: 'x', b: 'y', c: 'z' }
实现Symbol.iterator就能够将普通对象变成可迭代对象:设计
let obj = { a: 'x', b: 'y', c: 'z' } obj[Symbol.iterator] = function() { let _keys = Object.keys(this) let _idx = 0 return { next() { let key = _keys[_idx++] if (!key) { return {done: true} } return { value: [key, this[key]], done: false } } } }
这时就可使用 for...of... 来遍历obj了😄:code
for (let [k, v] of obj) {console.log(k, v)}
若是你以为为了要使一个对象变的可迭代,要本身去理清楚什么next,done很麻烦,那么生成器函数就是为你准备的。由于它会返回一个迭代器:
function* generator() { yield 1; } let iter = generator(); iter.next() // {value: 1, done: false} iter.next() // {value: undefined, done: true}
而后咱们把obj的Symbol.iterator
替换成一个生成器函数:
obj[Symbol.iterator] = function*() { let _keys = Object.keys(this) while(_keys.length > 0) { let key = _keys.shift() yield [key, this[key]] } }
这时,你同样可使用 for...of...来迭代obj了:
for (let [k, v] of obj) { console.log(k, v) }
咱们知道可使用for...of来迭代数组,字符串这些对象,那是由于String, Array, TypedArray, Map, Set 都是可迭代对象,他们的原型都实现了 Symbol.iterator 方法。文章一开始,咱们但愿对一个数组迭代时,除了拿到它的值之外,还能够拿到他的索引,那咱们就须要对[Symbol.iterator]方法进行重载修改,咱们能够实现一个reload方法来修改数组的Symbol.iterator方法:
function reload(arr) { if (Array.isArray(arr)) { arr[Symbol.iterator] = function*() { for (let i = 0; i < this.length; i++) { yield [this[i], i] } } } } var arr = ['x', 'y', 'z'] reload(arr) for (let [item, idx] of arr) { console.log(item, idx) }
最后一个问题:为何直接修改Array.prototype[Symbol.iterator]方法呢?由于解构也用到了迭代协议,那不是咱们想要的结果😊。有兴趣的同窗能够试试直接修改原型上的方法
武汉加油!本文完。