21

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

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
4

5 回答 5

40

如果要将列表保留为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 年年中更新

我正在更新这个答案,因为它似乎仍在收到意见和投票。我应该澄清一下,给定的答案是概念证明,通常扩展本机类的原型是一种不好的做法,应该在生产项目中避免。

特别是,这并不多,因为它会弄乱for...in循环 - 对于数组应该始终避免这种做法,而且对于遍历它们的元素绝对是一种不好的做法 - 而且因为从 IE9 开始,我们可以可靠地做到这一点:

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

主要问题是扩展本机类不是面向未来的,即 ECMA 可能会next为数组引入一种可能与您的实现不兼容的方法。即使是非常常见的 JS 框架,它也已经发生了——最后一种情况是MooTools 的contains数组扩展,它导致ECMA 将名称更改为includes(糟糕的举动,IMO,因为我们已经containsDOMTokenListElement.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];
};

此外,在 ES6 中,扩展原生类更容易:

class MyTraversableArray extends Array {
    next() {
        return this[++this.current];
    }
}

唉,转译器很难处理原生类扩展,并且 Babel 删除了它的支持。但这是因为它们不能完全复制对我们的案例没有影响的一些行为,所以你可以坚持使用上面的旧 ES3 代码。

于 2012-09-12T14:39:26.797 回答
23

我通常建议不要添加东西,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,所以你可以在定义它的同一行上“迭代”一个数组,如下所示:

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

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

于 2012-09-12T15:16:58.807 回答
6

一个方面现在内置到数组中,因为从 ES2015 开始,数组是可迭代的,这意味着您可以获得具有next方法的迭代器(但请继续阅读“prev”部分):

const a = [1, 2, 3, 4, 5];
const iter = a[Symbol.iterator]();
let result;
while (!(result = iter.next()).done) {
  console.log(result.value);
}

迭代器只能前进,而不是双向。当然,您通常不会显式使用迭代器,通常会将其用作某些迭代构造的一部分,例如for-of

const a = [1, 2, 3, 4, 5];
for (const value of a) {
  console.log(value);
}

您可以轻松地给自己一个双向迭代器:

  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()));
.as-console-wrapper {
  max-height: 100% !important;
}

于 2018-01-22T09:23:40.070 回答
3

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 文档中确实存在一个示例程序,它可以帮助将斐波那契数列打印到我们喜欢的元素。

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
于 2018-02-04T21:28:38.310 回答
1

比修改原生对象的原型更安全的是为额外需要的数组函数创建工厂函数:

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
于 2019-03-20T06:16:00.347 回答