ECMAScript 6: дыры в массивах
1 Дыры в объектах Array
Дыры – это индексы внутри объекта Array
, у которых нет связанного с ними элемента. Другими словами: у массива arr
дыра в индексе i
если:
0 ≤ i < arr.length
!(i in arr)
Например, следующий массив имеет дыру в индексе 1:
> let arr = ['a',,'b']
'use strict'
> 0 in arr
true
> 1 in arr
false
> 2 in arr
true
> arr[1]
undefined
Для более подробной информации посмотрите раздел “Holes in Arrays” в “Speaking JavaScript”.
2 ECMAScript 6: дыры обрабатываются как элементы undefined
Общее правило для методов Array, появившихся в ES6: каждая дыра обрабатывается, как если бы она была элементом типа undefined
. Например:
> Array.from(['a',,'b'])
[ 'a', undefined, 'b' ]
> [,'a'].findIndex(x => true)
0
> [...[,'a'].entries()]
[ [ 0, undefined ], [ 1, 'a' ] ]
Идея состоит в том, чтобы отучить людей от использования дыр и упростить жизнь. К сожалению, это означает несовместимость с текущим кодом.
3 Методы объекта Array и дыры
3.1 Array.from()
Array.from()
преобразует дыры в undefined
:
> Array.from(['a',,'b'])
[ 'a', undefined, 'b' ]
Со вторым аргументом, он работает как map()
, но не игнорирует дыры:
> Array.from(new Array(3), (x,i) => i)
[ 0, 1, 2 ]
3.2 Оператор распространения (...)
Внутри объектов Array оператор распространения (...)
работает так же, как Array.from()
(но его операнд должен быть итерируемым, в то время как Array.from()
может обрабатывать всё, что подобно массиву).
> [...['a',,'b']]
[ 'a', undefined, 'b' ]
3.3 Методы Array.prototype
В ECMAScript 5 поведение варьируется уже незначительно. Например:
forEach()
,filter()
,every()
иsome()
игнорируют дыры;map()
пропускает, но сохраняет дыры;join()
иtoString()
обрабатывают дыры, как если бы они были элементамиundefined
, но интерпретируютnull
иundefined
, как пустые строки.
ECMAScript 6 добавляет новые типы поведения:
copyWithin()
создает дыры при их копировании (т.е. он удаляет элементы, если это необходимо);entries()
,keys()
,values()
обрабатывают каждую дыру, как если бы она была элементомundefined
;find()
иfindIndex()
делают то же самое;fill()
не заботится, есть ли элементы в индексах или нет.
Следующая таблица описывает, как методы Array.prototype обрабатывают дыры.
Метод | Дыры | |
---|---|---|
concat | сохраняются | ['a',,'b'].concat(['c',,'d']) → ['a',,'b','c',,'d'] |
copyWithin ✓ | сохраняются | [,'a','b',,].copyWithin(2,0) → [,'a',,'a'] |
entries ✓ | элементы | [...[,'a'].entries()] → [[0,undefined], [1,'a']] |
every | игнорируются | [,'a'].every(x => x==='a') → true |
fill ✓ | заполняются | new Array(3).fill('a') → ['a','a','a'] |
filter | удаляются | ['a',,'b'].filter(x => true) → ['a','b'] |
find ✓ | элементы | [,'a'].find(x => true) → undefined |
findIndex ✓ | элементы | [,'a'].findIndex(x => true) → 0 |
forEach | игнорируются | [,'a'].forEach((x,i) => log(i)); → 1 |
indexOf | игнорируются | [,'a'].indexOf(undefined) → -1 |
join | элементы | [,'a',undefined,null].join('#') → '#a##' |
keys ✓ | элементы | [...[,'a'].keys()] → [0,1] |
lastIndexOf | игнорируются | [,'a'].lastIndexOf(undefined) → -1 |
map | сохраняются | [,'a'].map(x => 1) → [,1] |
pop | элементы | ['a',,].pop() → undefined |
push | сохраняются | new Array(1).push('a') → 2 |
reduce | игнорируются | ['#',,undefined].reduce((x,y)=>x+y) → '#undefined' |
reduceRight | игнорируются | ['#',,undefined].reduceRight((x,y)=>x+y) → 'undefined#' |
reverse | сохраняются | ['a',,'b'].reverse() → ['b',,'a'] |
shift | элементы | [,'a'].shift() → undefined |
slice | сохраняются | [,'a'].slice(0,1) → [,] |
some | игнорируются | [,'a'].some(x => x !== 'a') → false |
sort | сохраняются | [,undefined,'a'].sort() → ['a',undefined,,] |
splice | сохраняются | ['a',,].splice(1,1) → [,] |
toString | элементы | [,'a',undefined,null].toString() → ',a,,' |
unshift | сохраняются | [,'a'].unshift('b') → 3 |
values ✓ | элементы | [...[,'a'].values()] → [undefined,'a'] |
Примечания:
- методы ES6 помечены галочкой (✓)
- JavaScript игнорирует замыкающие запятые:
['a',,].length → 2
- в таблице использовалась вспомогательная функция:
const log = console.log.bind(console);
4 Рекомендации
Что касается дыр в объектах Array
, сейчас существует только одно правило – никаких правил. Поэтому вы должны по возможности избегать дыр (на производительность они также влияют отрицательно). Если вы не можете избежать их использования, то таблица в предыдущем разделе поможет вам.
5 Дополнительные источники
- ECMAScript 5: глава “Arrays” в “Speaking JavaScript”
- ECMAScript 6: глава “New Array features” в “Exploring ES6”