Есть ли обозначения для динамических индексов в литералах массива?

#javascript #ecmascript-6

#javascript #ecmascript-6

Вопрос:

При объявлении объектных литералов можно объявлять динамические ключи следующим образом:

 const foo = 'foo';

const obj = {
  [foo]: 'bar'
}

console.log(obj); 

Этот синтаксический сбой для массивов, потому что квадратная скобка интерпретируется как объявление массива, тогда синтаксический анализатор завершается ошибкой при достижении двоеточия. Пример варианта использования: клонирование массива при изменении последнего элемента:

 const arr = [1, 2, 3];

const modifiedClone = [...arr, [arr.length - 1]: 4]; // Fails 

Существует ли синтаксис для объявления динамических индексов для литералов массива?

Комментарии:

1. С литералом массива вы можете объявить массив только с его элементами. Вы не можете сделать то же самое, что и распространение объекта, потому что принципиально синтаксис массива работает не так — вы не определяете элементы по их позиции, вы просто определяете последовательность, которая превращается в массив.

2. Просто написать const modifiedClone = [...arr]; modifiedClone[arr.length - 1] = 4; ?

3. @Bergi да, очевидно, но мой вопрос касается одной инструкции для достижения этой цели

4. Вы можете поместить все в IIFE, если хотите сделать это одним выражением 🙂

5. Есть предложение, которое позволит вам пропускать и перематывать итераторы . С этим можно было бы быть более гибким и / или с помощниками итератора . Если вы хотите как распространять, так и перезаписывать, вы можете использовать для этого генераторы. Стоит ли это того, зависит, но это вариант.

Ответ №1:

Вы могли бы использовать Object.assign пустой массив в качестве целевого объекта, а также данный массив и объект со свойством index.

 const arr = [1, 2, 3];

const modifiedClone = Object.assign([], arr, { [arr.length - 1]: 4 }); 

console.log(modifiedClone); 

Ответ №2:

То, что вы запрашиваете, в настоящее время невозможно с использованием литерала массива и синтаксиса распространения. Это потому, что, в отличие от объектных литералов, массив определяется не конкретными индексами, а последовательностью, которая становится его членами.

Самое большее, вы можете использовать elision, чтобы оставить индекс пустым, например, [1, , 3] будет производить 1, <empty>, 3 , но вы все равно должны четко указать это, вы не можете «пропустить», это означает, что создание массива только с первым и десятым элементом становится очень сложным [1,,,,,,,,,10] . Наличие большего количества определенно не рекомендуется в качестве литерала массива.

С учетом сказанного синтаксис распространения ... с массивами принимает значения из итератора, поэтому [...arr] он похож на:

 let result = [];
let it = arr[Symbol.iterator]();
let next = it.next();

while (!next.done)) {
    result.push(next.value);
    next = it.next();
}
 

Обладая этими знаниями, мы можем использовать функции генератора и создать несколько простых универсальных вспомогательных генераторов, которые преобразуют итерацию и распространяют свои результаты. Например

Получить все, кроме последнего элемента, из итерируемого:

 /** 
 * 
 * @generator
 * @param {Iterable}
 * @yields {*} every value from the iterable but the last one
 */
function* initial(iterable) {
  const it = iterable[Symbol.iterator]();
  let last = it.next();
  let next = it.next();
  
  while(!next.done){
    yield last.value;
    last = next;
    next = it.next();
  }
}


const arr = [1, 2, 3];
const modifiedClone = [...initial(arr), 4];

console.log(modifiedClone); 

Возьмите первые num значения из итерируемого:

 /**
 *
 * @generator
 * @param {number} num - how many items to extract
 * @param {Iterable} itearable - iterable from where the results should be extracted
 * @yields {*} - at most `num` amount of results from `iterable`
 */
function* take(num, iterable) {
  const it = iterable[Symbol.iterator]();
  
  let {value, done} = it.next();
  for (let i = 0; i < num amp;amp; !done; i  , {value, done} = it.next()) {
    yield value;
  }
}

const arr = [1, 2, 3];
const modifiedClone = [...take(arr.length - 1, arr), 4];

console.log(modifiedClone); 

Перейдите к итерируемому и преобразуйте значение там, где это необходимо:

 /**
 * transform an iterable of key-value pairs by their key
 * 
 * @generator
 * @param {Map} replacements - new values for each of the keys
 * @param {Iterable} keyValueIterable - iterable which produces `[key, value]` pairs
 * @yields {*} - the values of the iterable transformed where needed
 */
function* replaceByKey(replacements, keyValueIterable) {
  const it = keyValueIterable[Symbol.iterator]();

  for (let {value: pair, done} = it.next(); !done; {value: pair, done} = it.next()) {
    let [key, value] = pair;
    if (replacements.has(key))
      yield replacements.get(key);
    else
      yield value;
  }
}

const arr = ["a", "b", "c", "d", "e"];

const replace = new Map()
  .set(1, "foo")
  .set(arr.length-1, "bar");
const modifiedClone = [...replaceByKey(replace, arr.entries())];

console.log(modifiedClone); 

Доступно больше возможностей в зависимости от того, что необходимо.