Как перейти к предыдущему/следующему элементу массива

Вопрос:Скажем, у нас есть список целых чисел: var fibonacci = [1,1,2,3,5,8,13,21]; Я хочу иметь возможность получить следующий и предыдущий элемент (просто для перемещения указателя элемента без изменения массива) следующим образом (например, может пройти без прототипа, чтобы переопределить интерфейс Array, но почему бы и нет): fibonacci.prev(); // returns false fibonacci.next(); // returns 1 fibonacci.next(); // returns

Вопрос:

Скажем, у нас есть список целых чисел:

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 Лучший ответ:

Если вы хотите сохранить список как Array, вам придется изменить его [[prototype]], чтобы он выглядел как итеративная коллекция:

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

Теперь каждый Array будет иметь методы prev и next и свойство current, которое указывает на “текущие” элементы. Оговорка: свойство current может быть изменено, что приводит к непредсказуемым результатам.

Post scriptum: я не рекомендую делать prev и next return 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, поскольку у нас уже есть 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]; };

В ES6, кроме того, проще расширить родные классы:

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

Увы, у транспилеров трудное время с расширениями родного класса, и Babel удалил его поддержку. Но это потому, что они не могут точно воспроизвести некоторые виды поведения, которые не имеют никакого влияния в нашем случае, поэтому вы можете придерживаться вышеуказанного старого кода ES3.

Ответ №1

Обычно я рекомендую не добавлять вещи в 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]);

Это может сделать вещи немного чище для вас. Вы также можете reset итератор путем повторного вызова iterifyArr в вашем массиве, или вы можете легко написать метод для reset (просто установите cur на 0).

Ответ №2

Аспект next теперь встроен в массивы, потому что с 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); }

Вы можете легко дать себе двухсторонний итератор:

  • Выполняя автономную функцию, которая принимает массив и возвращает итератор, или

  • Подклассификация 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()));.as-console-wrapper { max-height: 100% !important; }

Оцените статью
Добавить комментарий