如何移动到数组的prev / next元素

时间:2012-09-12 14:36:02

标签: javascript arrays

假设我们有一个整数列表:

var fibonacci = [1,1,2,3,5,8,13,21];

我希望能够以下列方式获取下一个和前一个元素(只是为了移动元素指针,而不是修改数组)(例如,可能没有原型来重新定义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

5 个答案:

答案 0 :(得分:36)

如果您希望将列表保留为Array,则必须更改其[[prototype]]以使其看起来像可迭代的集合:

Array.prototype.next = function() {
    return this[++this.current];
};
Array.prototype.prev = function() {
    return this[--this.current];
};
Array.prototype.current = 0;

现在每个Array都有方法prevnext,以及current属性,它指向“当前”元素。需要注意的是:current属性可以修改,从而导致不可预测的结果。

post scriptum:当索引超出范围时,我不建议prevnext返回false。如果您真的想要,可以将方法更改为:

Array.prototype.next = function() {
    if (!((this.current + 1) in this)) return false;
    return this[++this.current];
};

2016年年中更新

我正在更新这个答案,因为它似乎仍在接收观点和投票。我应该澄清一下,给出的答案是一个概念证明,并且一般来说扩展原生类的原型是一种不好的做法,在生产项目中应该避免。< / p>

特别是,它并不多,因为它会混淆for...in周期 - 对于数组应始终避免这种情况,并且肯定是迭代元素的不良做法 - 而且因为自IE9以来我们可以做到这一点:

Object.defineProperty(Array.prototype, "next", {
    value: function() { return this[++this.current]; },
    enumerable: false
});

主要问题是扩展本机类不是面向未来的,即ECMA可能会为可能与您的实现不兼容的数组引入next方法。甚至在非常常见的JS框架中也已经发生了 - 最后一个案例是MooTools' contains array extension导致了ECMA to change the name to includes(不好的举动,IMO,因为我们已经DOMTokenLista hard time with native class extensions对象contains {1}})。

话虽如此,并不是说必须不能扩展原生原型,但你应该知道你在做什么。我可以给你的第一个建议是选择不会与未来标准扩展冲突的名称,例如: Element.classList而非myCompanyNext。这将花费你一些优雅的代码,但会让你睡觉。

更好的是,在这种情况下,您可以有效地扩展next类:

Array

此外,在ES6中,扩展本机类更容易:

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];
};

唉,转发者有{{3}},巴贝尔取消了它的支持。但这是因为他们不能完全复制一些在我们的案例中没有影响的行为,所以你可以坚持使用上面的旧ES3代码。

答案 1 :(得分: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"

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

一对夫妇注意到:

首先,如果你走到最后,你可能希望让它返回undefined而不是false。其次,因为此方法使用闭包隐藏cur,所以您无法在阵列上访问它。因此,您可能希望使用cur()方法来获取当前值:

//Inside the iterifyArr function:
    //...
    arr.cur = (function () { return this[cur]; });
    //...

最后,你的要求还不清楚“指针”的结束距离是多久。以下面的代码为例(假设fibonacci设置如上):

fibonacci.prev(); //false
fibonacci.prev(); //false
fibonacci.next(); //Should this be false or 1?

在我的代码中,它会是false,但您可能希望它为1,在这种情况下,您必须对我的代码进行一些简单的更改。

哦,因为函数返回arr,你可以在你定义它的同一行“iterify”一个数组,如下所示:

var fibonacci = iterifyArr([1, 1, 2, 3, 5, 8, 13]);

这可能会让事情变得更清洁。您也可以通过重新调用数组上的iterifyArr来重置迭代器,或者您可以编写一个方法来轻松地重置它(只需将cur设置为0)。

答案 2 :(得分:3)

下一个方面现在内置于数组中,因为从ES2015开始,数组是 iterables ,这意味着你可以为它们获得一个具有{的迭代器{1}}方法(但请继续阅读&#34; prev&#34;部分):

&#13;
&#13;
next
&#13;
&#13;
&#13;

迭代器只能前进,而不是两种方式。当然,您通常不会明确地使用迭代器,通常将它用作某些迭代构造的一部分,例如const a = [1, 2, 3, 4, 5]; const iter = a[Symbol.iterator](); let result; while (!(result = iter.next()).done) { console.log(result.value); }

&#13;
&#13;
for-of
&#13;
&#13;
&#13;

你可以很容易地给自己一个双向迭代器:

  1. 通过创建一个接受数组并返回迭代器的独立函数,或者

  2. 通过继承const a = [1, 2, 3, 4, 5]; for (const value of a) { console.log(value); }并覆盖子类中的迭代器,或

  3. 用你自己的迭代器替换默认的Array迭代器(只是确保它在前进时与默认的完全相同!)

  4. 这是一个带子类的例子:

    &#13;
    &#13;
    Array
    &#13;
    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()));
    &#13;
    &#13;
    &#13;

答案 3 :(得分:2)

ES6为我们提供了生成器函数,可以让我们打印出一个非常简单的数组,如下面的

function* data() {
  yield* [1, 1, 2, 3, 5, 8, 13, 21];
}

var fibonnacci = data();

fibonnacci.next()
> {value: 1, done: false}

fibonnacci.next()
> {value: 1, done: false}

fibonnacci.next()
> {value: 2, done: false}

fibonnacci.next()
> {value: 3, done: false}

fibonnacci.next()
> {value: 5, done: false}

fibonnacci.next()
> {value: 8, done: false}

fibonnacci.next()
> {value: 13, done: false}

fibonnacci.next()
> {value: 21, done: false}

fibonnacci.next()
> {value: undefined, done: true}

但是,MDN docs上存在一个示例程序,它可以帮助将fibonnacci系列打印到我们喜欢的元素上。

function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {  
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

答案 4 :(得分:0)

比修改本地对象的原型更安全的做法是为其他所需的数组函数创建工厂函数:

const moreArrayFunctions = arr => ({
    current: 0,
    arr,
    next(){

        if( this.current >= ( this.arr.length - 1 ) ){
             this.current = this.arr.length - 1;
        }else{
            this.current++;
        }

        return this.arr[this.current];
    },
    prev(){

        if( this.current <= 0 ){
            this.current = 0;
        }else{
            this.current--;
        }

        return this.arr[this.current];
    }
});

const fibonacci = moreArrayFunctions([1,1,2,3,5,8,13,21]);

fibonacci.next();
fibonacci.prev();
fibonacci.current