遍历日期可迭代对象结果异常的原因及解决方法问询
这个问题我之前踩过坑,核心根源是引用类型和原始类型的本质差异,咱们一步步说清楚:
为什么会出现全是最后一个日期的情况?
首先你要明白:Date是引用类型,而数字是原始类型。
在你的日期迭代器里,this.current指向的是同一个Date对象。每次调用next()时,你只是修改了这个对象内部的日期值,然后把这个对象的引用返回出去。当Array.from遍历迭代器时,它会把每次next()返回的value都存入数组,但这些value其实都是指向同一个Date对象的指针。等迭代结束时,这个唯一的Date对象已经被修改成了to之后的那一天,所以数组里所有元素看起来都是最后那个日期。
而对比数字迭代器的例子,current是原始类型(number),每次返回this.current++时,返回的是一个新的数值(原始类型的值是不可变的,每次自增都是生成新值),所以数组里的每个元素都是独立的,自然能得到正确的结果。
可行的替代方案
方案1:每次返回新的Date对象副本
修改next()方法,在返回前先创建当前日期的副本,再修改原current:
const dateRange = { from: new Date(2018, 0, 23), to: new Date(2018, 0, 28), [Symbol.iterator]() { this.current = new Date(this.from); // 初始化副本,避免修改原from return this; }, next() { if (this.current <= this.to) { // 创建当前日期的副本,返回这个副本 const currentDate = new Date(this.current); this.current.setDate(this.current.getDate() + 1); return {done: false, value: currentDate}; } return {done: true}; } };
这样数组里的每个元素都是独立的Date对象,不会互相干扰。
方案2:让迭代器状态独立于原对象(推荐)
把迭代器的状态封装在单独的对象里,避免污染原dateRange的属性,同时支持多并发迭代:
const dateRange = { from: new Date(2018, 0, 23), to: new Date(2018, 0, 28), [Symbol.iterator]() { // 初始化独立的迭代状态,不修改原对象 let current = new Date(this.from); const endDate = this.to; return { next() { if (current <= endDate) { const value = new Date(current); current.setDate(current.getDate() + 1); return {done: false, value}; } return {done: true}; } }; } };
这种写法的好处是,你可以同时用多个for...of遍历dateRange,它们的迭代状态不会互相影响,而原来的写法做不到这一点。
方案3:用生成器函数实现(最简洁)
ES6的生成器函数天生适合写迭代器,代码更简洁易懂:
const dateRange = { from: new Date(2018, 0, 23), to: new Date(2018, 0, 28), *[Symbol.iterator]() { let current = new Date(this.from); while (current <= this.to) { yield new Date(current); // 每次yield新的Date对象 current.setDate(current.getDate() + 1); } } };
生成器会自动处理迭代状态,每次yield的都是独立的Date对象,无论是Array.from转换还是for...of遍历,结果都完全符合预期。
内容的提问来源于stack exchange,提问作者Mario

