#delete #undefined #array #empty
Да, есть.
Думаю, всем известно, что для удаления элемента, в JS, используется undefined:
lеt variable = 'string'
variable = undefined
Им не часто пользуются, ведь в JS работает автоматический сборщик мусора, и мало
кто заботится об удалении элементов. Ещё реже встречаются люди, которые знают о существовании
метода delete, а тем более те, кто замечал различия в использовании undefined и delete.
let o = {
A: 9,
B: 9,
}
delete o['A']
console.log(
Object.keys(o)//массив//["B"]//length: 1
);
Далее в моём вопросе, и следующем за ним ответе, будет рассмотрен частный, но очень
важный, случай различия в использовании undefined и delete в контексте массивов.
Итак (барабанная дробь) внимание, вопрос…
В чём различия при удалении элемента массива методами undefined и delete и какие
серьёзные и не очень последствия за этим следуют?
let arr = ['A','B','C','D','E','F']
delete arr[0]
arr[1] = undefined
arr[2] = void 0
Ответы
Ответ 1
Ещё реже встречаются люди, которые знают о существовании метода delete
delete - оператор, а не метод!
В чём различия при удалении элемента массива методами undefined и delete
Присвоение undefined и оператор delete не удаляют элемент массива. Они "удаляют"/устанавливают
значение, содержащееся в массиве. Сам элемент массива, после такого "удаления" остается
- он становится не определенным, но позволяет выполнять операции с ним (смысл термина
элемент !== смыслу термина содержимое элемента. Хотя, безусловно, ==).
А различие в том, что присвоение undefined - это присвоение значения, со всеми вытекающими
последствиями... тогда как delete, именно удаляет содержимое элемента.
Для удаления элементов массива, в JavaScript существует Array.splice(). Прямое назначение
метода как раз в этом (и в добавлении элементов). А delete - предназначен для удаления
свойств объектов.
Ответ 2
Главное различие - что оператор delete удаляет элемент массива. Хоть при этом длинна
массива не изменится и адресовать удалённый элемент вы по прежнему сможете. Но оператор
in не обнаружит элемент удалённый таким способом:
let arr = ['A','B','C','D','E','F']
arr[2] = undefined
console.log(2 in arr) // ===> true
delete arr[3]
console.log(3 in arr) // ===> false
Подробнее тут
Ответ 3
В: В чём различия при удалении элемента массива методами undefined и delete ?
О: оператор delete удаляет пару «ключ-значение».
В: Какие серьёзные и не очень последствия за этим следуют?
О: так как на месте удалённого элемента с помощью delete остаётся empty (дырка) -
весь второй раздел будет посвящён сравнению поведения
операторов:
for
for in
for of
и некоторых методов:
Array.forEach
Array.map
Array.filter
Object.keys
Object.values
при работе с массивами, содержащими empty или undefined.
Сразу хочу сделать заявление:
Здесь будут рассмотрены только массивы Array!
Здесь не будут рассматриваться типизированные массивы вроде: Int16Array; и всякие:
Set, NodeList, ArrayBuffer (да-да, кое что из этого списка не массив).
И ещё, небольшое замечание: иной раз не помешает убедиться что вы работаете с массивом
(если собираетесь его модифицировать) а не с [Symbol.iterator]: function*, например.
структура:
немного теории
как оно выглядит в консоле chrome , filefox и stackoverflow
последняя запятая игнорируется
функции, используемые мной здесь
для тех, кто не хочет использовать empty и вообще не хочет читать многабукаф ибо
где ещё почитать
ищу информацию
много практики
для иллюстрации "проблемы"
count
замеры времени
персонально для @yar85
оператор расширения (spread)
1. немного теории
https://learn.javascript.ru/array-methods#удаление-из-массива
оператор delete удаляет пару «ключ-значение».
да, и в этом месте, в массиве, образуется "дырка" (empty), если попробовать прочитать
значение из этой "дырки", то оно будет undefined
Это – все, что он делает.
на самом деле, оператор delete сразу освобождает память, а ещё возвращает true/false
но в контексте элементов массива нас это не интересует, т.к. он всегда будет возвращать
true (даже в случае с empty и undefined)
Обычно же при удалении из массива мы хотим, чтобы оставшиеся элементы сдвинулись
и заполнили образовавшийся промежуток.
Поэтому для удаления используются специальные методы: из начала – shift, с конца
– pop, а из середины – splice
с shift и pop всё как всегда.
а вот при работе со splice есть небольшой нюанс и, на самом деле, связан он с оператором
расширения ( ... spread ) - с помощью splice не получится вставить empty элемент/ы,
а spread превращает empty в undefined.
1.1 как оно выглядит в консоле chrome , filefox и stackoverflow
```js
let arr = [0,1,2,3,4,5]
delete arr[1]
delete arr[2]
arr[3] = undefined
arr[4] = undefined
console.log(arr)
// эквивалентно
// console.log([0,,,undefined,undefined,5])
```
в консоле chrome :
[ 0, empty × 2 , undefined, undefined, 5 ]
в консоле filefox :
[ 0, <2 empty slots> , undefined, undefined, 5 ]
в консоле stackoverflow :
[ 0, undefined, undefined, undefined, undefined, 5 ]
как вывести в stackoverflow :
```js
log(() => // "[ 0, empty, empty, undefined, undefined, 1 ]"
arrToString([0,,,undefined,undefined,1,])) // <- запятая в конце
```
1.2 последняя запятая игнорируется
относительно недавно появилась возможность писать в конце запятую, которая игнорируется
(что при создании объектов, что при создании массивов)
```js
let a = ['A','B']
let b = ['A','B',] // <- запятая в конце
log(() => Object.keys(a)) // ["0", "1"]
log(() => Object.keys(b)) // ["0", "1"]
log(() => a.length) // 2
log(() => b.length) // 2
```
1.3 функции, используемые мной здесь
let group = _f => {
console.group(_f.name || ((_f.prototype) ? _f : 'anonym'))
try { _f() } catch (_e) { console.log(_e.stack) }
console.groupEnd()
}
let log = (_f) => _f instanceof Function
? console.log(_f,_f())
: console.log(_f)
group(() => {
let hw = () => 'Hello World'
// --> // вывод в консоле хрома
log(hw) // () => 'Hello World' "Hello World"
log(() => 'Hello World 2') // () => 'Hello World 2' "Hello
World 2"
// <--
})
group(() => {
// --> // я же буду писать в комментариях
только правую часть
log(() => arrToString(['hello','world'])) // "[ hello, world ]"
log(() => arrToString([1,,,undefined,undefined,2])) // "[ 1, empty, empty, undefined,
undefined, 2 ]"
// -----------
// ОСТОРОЖНО !
// -----------
// // вывод в консоле хрома
log(() => arrToString([1,[11,,22],,undefined,undefined,2])) // "[ 1, 11,,22,
empty, undefined, undefined, 2 ]"
// <--
})
group(() => {
throw new Error('qwa')
})
function arrToString(_arr) {
if (Object.prototype.arrToString.call(_arr) !== "[object Array]") throw new TypeError('_arr
!== "[object Array]"')
let res = []
let val = null;
for (var i = 0; i < _arr.length; i++) {
val = _arr[i]
// проверка на `empty`
// эквивалентно if (Object.keys(_arr).indexOf(''+i) !== -1) {
if (i in _arr) {
// проверка на `undefined`
if (val === undefined) {
res.push('undefined')
} else {
// res.push(String(val))
res.push('' + val)
}
} else {
res.push('empty')
}
}
//---
return `[ ${res.join(', ')} ]`
}
1.4. для тех, кто не хочет использовать empty и вообще не хочет читать многабукаф ибо
неожиданная встрече с empty в чужом коде может вызвать головную боль
; ) поэтому делаем заметку в голове и кликаем по звёздочке вверху
пример создания массива заданной длинны с помощью конструктора Array :
```js
let arr1 = Array(5) // == [,,,,,]
log(() => arr1) // [empty × 5]
let arr2 = Array.from({
length: 5
},(_key,_index) => undefined)
log(() => arr2) // [undefined, undefined, undefined, undefined, undefined]
```
для перебора используйте только for (var _i=0; i функция) // результат в консоле
он не охватывает все возможные методы и ситуации, а лишь демонстрирует ключевые моменты
2.1 для иллюстрации "проблемы"
JQuery
http://api.jquery.com/jquery.each/
let arr = [,,,]
let count = null
console.log(`arr.length: ${arr.length}`)
{
console.log('$.each')
count = 0
$.each(arr,_k=>{
count++
})
console.log(`count: ${count}`) // count: 3
}
{
console.log('arr.forEach')
count = 0
arr.forEach(_k=>{
count++
})
console.log(`count: ${count}`) // count: 0
}
underscorejs
http://underscorejs.ru/
Кроме того, Underscore умеет делегировать вызовы, т.е. если код выполняется в современном
браузере, который имеет нативные реализации таких методов, как: forEach, map, reduce,
filter, every, some и indexOf, то будут вызваны именно они.
ничего не предвещало проблемы - простая функция сложения
дана функция
let sum = (arrOfNumbers) => {
let res = 0
arrOfNumbers.forEach(_number => { res += _number })
return res
}
и массив
let arr = [ 10, 3, 5 ]
проверка с применением delete arr[1]
() => sum(arr) 15
проверка с применением arr[1] = undefined
() => sum(arr) NaN
исходники:
// /*
group(function sum() {
let sum = (arrOfNumbers) => {
let res = 0
arrOfNumbers.forEach(_number => { res += _number })
return res
}
let a = 10,b = 3,c = 5,Arr = () => [a,b,c]
group(function testDelete() {
let arr = Arr(); log(() => arrToString(arr)) // "[ 10, 3, 5 ]"
delete arr[1]
log(() => sum(arr)) // 15
})
group(function testUndefined() {
let arr = Arr(); log(() => arrToString(arr)) // "[ 10, 3, 5 ]"
arr[1] = undefined
log(() => sum(arr)) // NaN
})
})
// */
2.2 count
дано два массива
let a = [,,,'k3']
let b = [undefined,undefined,undefined,'k3']
суть теста - перебрать элементы массива и посчитать "тики"
результат:
count
() => Object.keys(a) ["3"]
() => Object.values(a) ["k3"]
() => Object.keys(b) (4) ["0", "1", "2", "3"]
() => Object.values(b) (4) [undefined, undefined, undefined, "k3"]
test [,,,'k3']
for() => count: 4
for in() => count: 1
for of() => count: 4
_arr.forEach() => count: 1
_arr.map() => count: 1
_arr.filter(_k => true)() => count: 1 перед фильтрацией [ empty, empty, empty,
k3 ] после [ k3 ]
test [undefined,undefined,undefined,'k3']
for() => count: 4
for in() => count: 4
for of() => count: 4
_arr.forEach() => count: 4
_arr.map() => count: 4
_arr.filter(_k => true)() => count: 4 перед фильтрацией [ undefined, undefined,
undefined, k3 ] после [ undefined, undefined, undefined, k3 ]
исходники:
group(function count() {
let a = [,,,'k3']
let b = [undefined,undefined,undefined,'k3']
log(() => Object.keys(a))
log(() => Object.values(a))
log(() => Object.keys(b))
log(() => Object.values(b))
let o = {
'for'(_arr) {
let count = 0
for (var i = 0; i < _arr.length; i++) {
count++
}
return(`count: ${count}`)
},
'for in'(_arr) {
let count = 0
for (var i in _arr) {
count++
}
return(`count: ${count}`)
},
'for of'(_arr) {
let count = 0
for (var i of _arr) {
count++
}
return(`count: ${count}`)
},
'_arr.forEach'(_arr) {
let count = 0
_arr.forEach(_k => {
count++
});
return(`count: ${count}`)
},
'_arr.map'(_arr) {
let count = 0
_arr.map(_k => {
count++
});
return(`count: ${count}`)
},
'_arr.filter(_k => true)'(_arr) {
let count = 0
let res = []
let before = `перед фильтрацией ${arrToString(_arr)}`
let after = null
_arr = _arr.filter(_k => {
count++
return true
});
res.push(`count: ${count}`)
after = `после ${arrToString(_arr)}`
res.push(before)
res.push(after)
return res.join(' ')
},
// 'for'(){},
}
let o2 = {
"test [,,,'k3']"() {
Object.values(o).forEach(_f => {
log(`${_f.name}() => ${_f(a)}`)
})
},
"test [undefined,undefined,undefined,'k3']"() {
Object.values(o).forEach(_f => {
log(`${_f.name}() => ${_f(b)}`)
})
},
}
Object.values(o2).forEach(group)
})
2.3 замеры времени
дано:
две матрицы (двумерный массив) matrixA и matrixB
ширина = 1366
высота 768
это характеристики моего монитора
каждая из них в самом конце (нижний правый угол монитора) содержит единичку
matrixA состоит из empty
matrixB состоит из undefined
задача:
запустить таймер, найти единичку (в самом конце), и остановить таймер.
ответ в миллисекундах:
() => test_for_in(matrixA) 2
() => test_for_in(matrixB) 206
() => test_for_of(matrixA) 122
() => test_for_of(matrixB) 130
() => test_for(matrixA) 122
() => test_for(matrixB) 115
решение:
group(function matrix() {
let width = 1366
let height = 768
let a = _n => Array(_n)
let matrixA = Array.from({ length: height },() => a(width))
setLast(matrixA)
let b = _n => Array.from({ length: _n },() => undefined)
let matrixB = Array.from({ length: height },() => b(width))
setLast(matrixB)
log(() => test_for_in(matrixA))
log(() => test_for_in(matrixB))
log(() => test_for_of(matrixA))
log(() => test_for_of(matrixB))
log(() => test_for(matrixA))
log(() => test_for(matrixB))
function setLast(matrix) {
let lastArr = matrix[matrix.length - 1]
lastArr[lastArr.length - 1] = 1
}
function test_for_in(matrix) {
let start = null
let end = null
start = Date.now()
for (var i in matrix) {
for (var j in matrix[i]) {
if (matrix[i][j] = 1) {
end = Date.now() - start
}
}
}
return end
}
function test_for_of(matrix) {
let start = null
let end = null
start = Date.now()
for (var arr of matrix) {
for (var val of arr) {
if (val = 1) {
end = Date.now() - start
}
}
}
return end
}
function test_for(matrix) {
let start = null
let end = null
start = Date.now()
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] = 1) {
end = Date.now() - start
}
}
}
return end
}
})
2.4 персонально для @yar85
объект и массив помещённые в массив являются ссылками, операция delete и присвоения
undefined удаляет только ссылку из массива. такие объекты удаляются из памяти в случае,
а об этом читаем тут learn.javascript.ru/memory-management
{
let o = {
'ссылка на объект'() {
let arr = []
let o = { name: 'O' }
arr.push(o)
// delete arr[0] // log(()=>f) // () => o {name: "O"}
// arr[0] = undefined // log(()=>f) // () => o {name: "O"}
log(() => o)
},
'ссылка на массив'() {
let arr = []
let arr2 = ['arr2']
arr.push(arr2)
// delete arr[0] // log(()=>f) // () => arr2 ["arr2"]
// arr[0] = undefined // log(()=>f) // () => arr2 ["arr2"]
log(() => arr2)
},
}
Object.values(o).forEach(group)
}
2.5 Оператор расширения (spread)
group(function spread() {
let arr = [,,,'C']
let arr2 = [...arr]
log(() => arr2) // (4) [undefined, undefined, undefined, "C"]
})