Reputation: 684
I have this object:
const iterable = {
items: [1,2,3,4],
currentIndex: 0,
[Symbol.iterator]() {
const self = this;
return {
next() {
return {
done: self.currentIndex === self.items.length,
value: self.items[self.currentIndex++]
}
}
}
}
}
I can loop using for of
:
for(let i of iterable){
console.log(i)
}
Now, I need iterate using v-for
<p v-for="item of iterable" :key="item">{{item}}</p>
But Vuejs crashes and it seems to enter in a infinite loop, because browser crashes with the message: Paused before potential out-of-memory crash
This pull request, Add iterable supports for v-for they say this feature was merged and used in vue 2.6.x. Im using Vuejs 2.6.11.
Upvotes: 3
Views: 711
Reputation: 29092
Here's an example of using v-for
with an iterable:
const iterable = {
items: [1, 2, 3, 4],
[Symbol.iterator] () {
const items = this.items
const length = items.length
let currentIndex = -1
return {
next () {
currentIndex++
return {
done: currentIndex >= length,
value: items[currentIndex]
}
}
}
}
}
new Vue({
el: '#app',
data () {
return { iterable }
},
methods: {
onAddClick () {
this.iterable.items.push(Date.now())
},
onRemoveClick () {
this.iterable.items.pop()
}
}
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
<p v-for="item of iterable">{{ item }}</p>
<button @click="onAddClick">Add item</button>
<button @click="onRemoveClick">Remove item</button>
</div>
The main problem with your original code is that it doesn't reset the counter when it creates a new iterator. So the first time you loop over the iterable everything will work fine. But the second time the counter is already at the end of the array. The check for self.currentIndex === self.items.length
will always be false
as currentIndex
will be greater than the length
.
You'd be able to see the same problem just using a for
/of
loop. e.g.:
for(let i of iterable){
console.log(i)
}
for(let i of iterable){
console.log(i)
}
The first loop will work no problem but the second loop will never end.
As for why Vue tries to loop through the iterable twice...
My assumption would be that you're exposing iterable
via a data
property. The currentIndex
will be reactive and it will be registered as a rendering dependency. As it is then incremented it will trigger a re-render. In theory you'd get into an infinite rendering recursion but in practice it won't get beyond the second rendering due to the never-ending iterable loop.
The key to fixing this is to keep currentIndex
scoped to the iterator and not the iterable. There are various ways to implement this but here's the one I used in my earlier example:
const iterable = {
items: [1, 2, 3, 4],
[Symbol.iterator] () {
const items = this.items
const length = items.length
let currentIndex = -1
return {
next () {
currentIndex++
return {
done: currentIndex >= length,
value: items[currentIndex]
}
}
}
}
}
Here currentIndex
is just held in the closure of the next
method. Alternatively it could be added as a property of the iterator object, alongside next
.
Upvotes: 3