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

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都会有方法prevnextcurrent财产,指向"当前"的元素.警告:current可以修改属性,从而导致不可预测的结果.

后脚本:当索引超出范围时,我不建议制作prevnext返回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,因为我们已经containsDOMTokenList对象中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).


T.J*_*der 6

接下来的这方面现在内置阵列,因为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)

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

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

  2. 通过子类化Array和覆盖子类中的迭代器,或

  3. 通过用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)