Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Есть ли бóльшая пустота чем undefined?

#javascript #массивы


#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"] })

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

Отправить комментарий