Javascript 如何移动到数组的上一个/下一个元素

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12390626/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 07:52:28  来源:igfitidea点击:

How to move to prev/next element of an array

javascriptarrays

提问by ddinchev

Let's say we have a list of integers:

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

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

I want to be able to get the next and previous element (just to move the element pointer, without modifying the array) in following manner (example, might go without prototype to redefine the Array interface but why not):

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

回答by MaxArt

If you want to keep the list as an Array, you'll have to change its [[prototype]]to make it look like an iterable collection:

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

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

Now every Arraywill have the methods prevand next, and the currentproperty, which points to the "current" elements. A caveat: the currentproperty can be modified, thus leading to impredictable results.

现在每个都Array将拥有方法prevnext,以及current指向“当前”元素的属性。警告:current可以修改属性,从而导致不可预测的结果。

Post scriptum: I don't recommend to make prevand nextreturn falsewhen the index is out of range. If you really want to, you can change the methods to something like:

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

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

UPDATE mid-2016

2016 年年中更新

I'm updating this answer because it seems it's still receiving views and votes. I should have clarified that the given answer is a proof of conceptand in general extending the prototype of native classes is a bad practice, and should be avoided in production projects.

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

In particular, it's not much because it's going to mess with for...incycles - which should always be avoided for arrays and it's definitelya bad practice for iterating through their elements - and also because since IE9 we can reliably do this instead:

特别是,这并不多,因为它会干扰for...in循环——对于数组应该始终避免这种情况,并且迭代它们的元素绝对是一个不好的做法——而且因为从 IE9 开始,我们可以可靠地做到这一点:

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

The main problem is that extending native classes is not future-proof, i.e. it may happen that ECMA will introduce a nextmethod for arrays that will probably be incompatible with your implementation. It already happened even with very common JS frameworks - the last case was MooTools' containsarray extensionwhich led ECMA to change the name to includes(bad move, IMO, since we already have containsin DOMTokenListobjects like Element.classList).

主要问题是扩展本地类不是面向未来的,即 ECMA 可能会引入一种next可能与您的实现不兼容的数组方法。即使使用非常常见的 JS 框架,它也已经发生了——最后一种情况是MooTools 的contains数组扩展,这导致ECMA 将名称更改为includes(糟糕的举动,IMO,因为我们已经containsDOMTokenList像 那样的对象中Element.classList)。

That being said, it's not that you mustnot extend native prototypes, but you should be aware of what you're doing. The first advice I can give you is to choose names that won't clash with future standard extensions, e.g. myCompanyNextinstead of just next. This will cost you some code elegance but will make you sleep sound.

话虽这么说,这并不是说你一定不会延长本机的原型,但你应该知道你在做什么。我可以给您的第一个建议是选择不会与未来标准扩展名冲突的名称,例如,myCompanyNext而不仅仅是next. 这将花费你一些代码优雅,但会让你睡个好觉。

Even better, in this case you can effectively extend the Arrayclass:

更好的是,在这种情况下,您可以有效地扩展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];
};

In ES6, moreover, it's easier to extend native classes:

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

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

Alas, transpilers have a hard time with native class extensions, and Babel removed its support. But it's because they can't exactlyreplicate some behaviours which have no influence in our case, so you can stick with the above old ES3 code.

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

回答by Pete

I generally recommend against adding things to Array.prototypebecause of the amount of really bad JavaScript out there. For instance, if you set Array.protoype.next = function () {}and someone has the following code, then there's a problem:

我通常建议不要添加东西,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"

This bad use of for-inloops is disturbingly common out there. So you're asking for trouble by adding to Array's prototype. However, it's pretty easy to build a wrapper to do what you're looking to do:

这种对for-in循环的不良使用在那里非常普遍。因此,您通过添加 toArray的原型来自找麻烦。但是,构建一个包装器来执行您要执行的操作非常容易:

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

A couple notes:

一些注意事项:

First of all, you probably want to have it return undefinedinstead of falseif you go past the end. Secondly, because this method hides curusing a closure, you don't have access to it on your array. So you might want to have a cur()method to grab the current value:

首先,您可能希望它返回undefined而不是false结束。其次,由于此方法cur使用闭包隐藏,因此您无法在数组上访问它。所以你可能想要一种cur()方法来获取当前值:

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

Finally, your requirements are unclear on how far past the end the "pointer" is maintained. Take the following code for example (assuming fibonacciis set as above):

最后,您的要求不清楚“指针”保持多远。以下面的代码为例(假设fibonacci设置如上):

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

In my code, it would be false, but you might want it to be 1, in which case you'd have to make a couple simple changes to my code.

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

Oh, and because it the function returns arr, you can "iterify" an array on the same line as you define it, like so:

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

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

That might make things a bit cleaner for you. You can also reset the iterator by re-calling iterifyArron your array, or you could write a method to reset it pretty easily (just set curto 0).

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

回答by T.J. Crowder

The nextaspect of this is now built into arrays, because as of ES2015, arrays are iterables, which means you can get an iterator for them which has a nextmethod (but keep reading for the "prev" part):

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

Iterators only go forward though, not both ways. And of course, you normally don't use an iterator explicitly, you normally use it as part of some iteration construct, such as for-of:

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

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

You can easily give yourself a two-way iterator:

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

  1. By making a standalone function that accepts the array and returns the iterator, or

  2. By subclassing Arrayand overriding the iterator in the subclass, or

  3. By replacing the default Arrayiterator with your own (just make sure it works exactly like the default one when going forward!)

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

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

  3. 通过用Array您自己的迭代器替换默认迭代器(只需确保它在前进时与默认迭代器完全一样!)

Here's an example with a subclass:

这是一个带有子类的示例:

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

回答by aelor

ES6 provides us with generator functions which can allow us to print out an array quite simply like below

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}

However, an example program does exist on the MDN docswhich can help print the fibonnacci series upto the element we like to.

但是,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

回答by wLc

Safer than modifying the native object's prototype is creating a factory function for additional needed array functions:

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

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