ddi*_*hev 16 javascript arrays
假设我们有一个整数列表:
var fibonacci = [1,1,2,3,5,8,13,21];
Run Code Online (Sandbox Code Playgroud)
我希望能够以下面的方式获取下一个和前一个元素(只是为了移动元素指针而不修改数组)(例如,可能没有原型来重新定义Array接口,但为什么不呢):
fibonacci.prev(); // returns false
fibonacci.next(); // returns 1
fibonacci.next(); // returns 1
fibonacci.next(); // returns 2
fibonacci.next(); // returns 3
fibonacci.next(); // returns 5
fibonacci.next(); // returns 8
fibonacci.prev(); // returns 5
fibonacci.next(); // returns 8
fibonacci.next(); // returns 13
fibonacci.next(); // returns false
Run Code Online (Sandbox Code Playgroud)
Max*_*Art 37
如果要将列表保留为Array,则必须更改它[[prototype]]以使其看起来像可迭代集合:
Array.prototype.next = function() {
return this[++this.current];
};
Array.prototype.prev = function() {
return this[--this.current];
};
Array.prototype.current = 0;
Run Code Online (Sandbox Code Playgroud)
现在每一个Array都会有方法prev和next和current财产,指向"当前"的元素.警告:current可以修改属性,从而导致不可预测的结果.
后脚本:当索引超出范围时,我不建议制作prev和next返回false.如果你真的想,你可以将方法更改为:
Array.prototype.next = function() {
if (!((this.current + 1) in this)) return false;
return this[++this.current];
};
Run Code Online (Sandbox Code Playgroud)
2016年中期更新
我正在更新这个答案,因为它似乎仍在接收意见和投票.我应该澄清一下,给出的答案是一个概念证明,一般来说,扩展本机类的原型是一种不好的做法,在生产项目中应该避免.
特别是它并不多,因为它会混乱for...in循环 - 对于数组来说应该总是避免它,这对于迭代它们的元素来说绝对是一个坏习惯 - 而且因为IE9我们可以可靠地做到这一点:
Object.defineProperty(Array.prototype, "next", {
value: function() { return this[++this.current]; },
enumerable: false
});
Run Code Online (Sandbox Code Playgroud)
主要问题是扩展本机类不是面向未来的,即ECMA可能会引入一种next可能与您的实现不兼容的数组方法.它已经发生了,即使是非常常见的JS框架 - 最后一种情况是MooTools的contains数组扩展,导致ECMA将名称更改为includes(坏动作,IMO,因为我们已经contains在DOMTokenList对象中Element.classList).
话虽这么说,这并不是说你一定不会延长本机的原型,但你应该知道你在做什么.我可以给你的第一个建议是选择不会与未来标准扩展冲突的名称,例如,myCompanyNext而不仅仅是next.这将花费你一些优雅的代码,但会让你睡得很香.
更好的是,在这种情况下,您可以有效地扩展Array类:
function MyTraversableArray() {
if (typeof arguments[0] === "number")
this.length = arguments[0];
else this.push.apply(this, arguments);
this.current = 0;
}
MyTraversableArray.prototype = [];
MyTraversableArray.prototype.constructor = MyTraversableArray;
MyTraversableArray.prototype.next = function() {
return this[++this.current];
};
MyTraversableArray.prototype.prev = function() {
return this[--this.current];
};
Run Code Online (Sandbox Code Playgroud)
此外,在ES6中,扩展本机类更容易:
class MyTraversableArray extends Array {
next() {
return this[++this.current];
}
}
Run Code Online (Sandbox Code Playgroud)
唉,转发器在本机类扩展方面很难,Babel取消了它的支持.但这是因为他们不能完全复制一些在我们的案例中没有影响的行为,所以你可以坚持使用上面的旧ES3代码.
Pet*_*ete 21
我通常建议不要添加内容,Array.prototype因为那里有非常糟糕的JavaScript.例如,如果您设置Array.protoype.next = function () {}并且有人拥有以下代码,那么就会出现问题:
var total = 0, i, myArr = [0,1,2];
for(i in myArr) {
total += myArr[i];
}
total; //is probably "3next"
Run Code Online (Sandbox Code Playgroud)
for-in循环的这种糟糕使用令人不安地普遍存在.所以你要通过添加Array原型来解决问题.但是,构建一个包装器以执行您要执行的操作非常容易:
var iterifyArr = function (arr) {
var cur = 0;
arr.next = (function () { return (++cur >= this.length) ? false : this[cur]; });
arr.prev = (function () { return (--cur < 0) ? false : this[cur]; });
return arr;
};
var fibonacci = [1, 1, 2, 3, 5, 8, 13];
iterifyArr(fibonacci);
fibonacci.prev(); // returns false
fibonacci.next(); // returns 1
fibonacci.next(); // returns 1
fibonacci.next(); // returns 2
fibonacci.next(); // returns 3
fibonacci.next(); // returns 5
fibonacci.next(); // returns 8
fibonacci.prev(); // returns 5
fibonacci.next(); // returns 8
fibonacci.next(); // returns 13
fibonacci.next(); // returns false
Run Code Online (Sandbox Code Playgroud)
几个笔记:
首先,你可能希望让它返回undefined而不是false如果你超越结束.其次,因为此方法隐藏cur使用闭包,所以您无法在阵列上访问它.所以你可能想要一个cur()方法来获取当前值:
//Inside the iterifyArr function:
//...
arr.cur = (function () { return this[cur]; });
//...
Run Code Online (Sandbox Code Playgroud)
最后,你的要求还不清楚"指针"在结束的过程中保持多久.以下面的代码为例(假设fibonacci设置如上):
fibonacci.prev(); //false
fibonacci.prev(); //false
fibonacci.next(); //Should this be false or 1?
Run Code Online (Sandbox Code Playgroud)
在我的代码中,它可能是false,但您可能希望它1,在这种情况下,您必须对我的代码进行一些简单的更改.
哦,因为它返回了函数arr,你可以在定义它的同一行上"iterify"一个数组,如下所示:
var fibonacci = iterifyArr([1, 1, 2, 3, 5, 8, 13]);
Run Code Online (Sandbox Code Playgroud)
这可能会让事情变得更清洁.您也可以通过重新调用iterifyArr数组来重置迭代器,或者您可以编写一个方法来轻松地重置它(只需设置cur为0).
在接下来的这方面现在内置阵列,因为ES2015的,阵列iterables,这意味着你可以得到他们的迭代器具有next方法(但请继续阅读的“上一页”部分):
const a = [1, 2, 3, 4, 5];
const iter = a[Symbol.iterator]();
let result;
while (!(result = iter.next()).done) {
console.log(result.value);
}Run Code Online (Sandbox Code Playgroud)
迭代器只会前进,而不是双向。当然,您通常不会显式使用迭代器,您通常将其用作某些迭代构造的一部分,例如for-of:
const a = [1, 2, 3, 4, 5];
for (const value of a) {
console.log(value);
}Run Code Online (Sandbox Code Playgroud)
你可以很容易地给自己一个双向迭代器:
通过创建一个接受数组并返回迭代器的独立函数,或
通过子类化Array和覆盖子类中的迭代器,或
通过用Array您自己的迭代器替换默认迭代器(只需确保它在前进时与默认迭代器完全一样!)
这是一个带有子类的示例:
class MyArray extends Array {
// Define the iterator function for this class
[Symbol.iterator]() {
// `index` points at the next value we'll return
let index = 0;
// Return the iterator
return {
// `next` returns the next
next: () => {
const done = index >= this.length;
const value = done ? undefined : this[index++];
return { value, done };
},
// `prev` returns the previous
prev: () => {
const done = index == 0;
const value = done ? undefined : this[--index];
return { value, done };
}
};
}
}
// Demonstrate usage:
const a = new MyArray("a", "b");
const i = a[Symbol.iterator]();
console.log("next", JSON.stringify(i.next()));
console.log("next", JSON.stringify(i.next()));
console.log("next", JSON.stringify(i.next()));
console.log("prev", JSON.stringify(i.prev()));
console.log("prev", JSON.stringify(i.prev()));
console.log("prev", JSON.stringify(i.prev()));
console.log("next", JSON.stringify(i.next()));Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper {
max-height: 100% !important;
}Run Code Online (Sandbox Code Playgroud)