Обновить
16
0
Андрей Грабов-Сметанкин@ukbpyh

Ведущий разработчик C++

Отправить сообщение
Только сейчас обратил внимание, что несмотря на то, что указано, что метод

const matrix = new Array(5).fill(
  new Array(5).fill()
)
или в такой форме
[...Array(5)].fill([...Array(5)])

приведенный napa3um'ом(тут ) и qbz'ом (тут) соответственно, не рабочий (а подметил это TheShockздесь и здесь)), по причине:

const matrix = [...Array(5)].fill([...Array(5)]);
matrix[1][2] = 42;

console.log(matrix);
/*
(5) […]
​0: Array(5) [ undefined, undefined, 42, … ]
​1: Array(5) [ undefined, undefined, 42, … ]
​2: Array(5) [ undefined, undefined, 42, … ]
​3: Array(5) [ undefined, undefined, 42, … ]
​4: Array(5) [ undefined, undefined, 42, … ]
​length: 5
*/

ответа на то, почему именно так происходит, тогда не прозвучало.

Дело в том, что используя функцию Array.prototype.fill() в качестве заполнителя, мы должны учитывать один немаловажный нюанс: если мы передаём ей объект, коим безусловно является и массив, то передаётся не n отдельных экземпляров, а 5 ссылок на один объект, и, когда мы выполняем операцию

arr[1][2] = 42;

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

Для примера и наглядности:

const matrix = [...Array(5)].fill({test:""});
console.log(matrix);
/*
(5) […]
​0: Object { test: "" }
​1: Object { test: "" }
​2: Object { test: "" }
​3: Object { test: "" }
​4: Object { test: "" }
​length: 5
*/

matrix[0].test = 42;
console.log(matrix);
/*
(5) […]
​0: Object { test: 42 }
​1: Object { test: 42 }
​2: Object { test: 42 }
​3: Object { test: 42 }
​4: Object { test: 42 }
​length: 5
​*/

Ну, а когда мы используем Array.prototype.fill() в функции вроде

const matrix = Array.from(Array(5), () => Array(5).fill())

мы имеем пять различных объектов и получаем на выходе корректную матрицу, так как Array.prototype.fill() вызывается 5 раз (по разу на каждый элемент) находясь в map функции метода Array.from().

Конечно, для кого то все эти вещи могут показаться очевидными, но, цитируя одного из упомянутых выше комментаторов: «просто для полноты картины», решил закрыть этот подвисший вопрос, и, возможно, кому-то это по итогу может оказаться полезным.
Присваивание d = xxx не самое верное решение, лучше сделать выборку. Так как, при взгляде не код не понятно, что из себя представляет эта самая переменная xxx (что создаёт проблемы для сопровождения), кроме того, опять же проблемы с безопасностью.
На счёт скорости: самое быстрое, как вышло из статьи — использовать локальные переменные, не важно let или var. А созданная ссылка xxx глобальная, то есть, если вопрос только в быстродействии — надо опять же использовать

var/let  d;
d = document.querySelector('#xxx');
d.innerHTML='bla-bla';

только в локальном контексте.

Но если вопрос не в скорости и, если отвлечься от безопасности, как вы предлагаете, и есть большое желание/необходимость пользоваться именно глобальной переменной, то тогда да, смысла делать отдельную выборку и заводить новую переменную нет.
Похоже вы правы. Я когда смотрел ориентировался на соседнюю инструкцию — обратил внимание на for с var

for(varVariableDeclarationList;Expression;Expression)Statement

— здесь есть точка с запятой, при этом если посмотреть на VariableDeclaration, там тоже есть точка с запятой, но я похоже ошибся, так как если взглянуть именно на VariableDeclarationList её там уже нет и значит всё верно. В общем, отталкиваясь от синтаксиса for при var я неверно сориентировался в пунктуации, если коротко.

Спасибо за ценное замечание, статью поправил.

Дэвид Флэнаган — JavaScript. Подробное руководство (6-е издание) — 2012
14.7. Элементы документа как свойства окна

Если для именования элемента в HTML-документе используется атрибут id и
если объект Window еще не имеет свойства, имя которого совпадает со значением
этого атрибута, объект Window получает неперечислимое свойство с именем,
соответствующим значению атрибута id, значением которого становится объект HTMLEle-
ment, представляющий этот элемент документа.

Как вы уже знаете, объект Window играет роль глобального объекта, находящегося
на вершине цепочки областей видимости в клиентском JavaScript. Таким
образом, вышесказанное означает, что атрибуты id в HTML-документах становятся
глобальными переменными, доступными сценариям. Если, например, документ
включает элемент />, на него можно сослаться с помощью
глобальной переменной okay.

Однако важно отметить, что этого не происходит, если объект Window уже имеет
свойство с таким именем. Элементы с атрибутами id, имеющими значение
«history», «location» или «navigator», например, не будут доступны через глобальные
переменные, потому что эти имена уже используются. Аналогично, если HTML-
документ включает элемент с атрибутом id, имеющим значение «х» и в сценарии
объявляется и используется глобальная переменная х, явно объявленная переменная скроет неявную переменную, ссылающуюся на элемент. Если
переменная объявляется в сценарии, который в документе находится выше
именованного элемента, наличие переменной будет препятствовать появлению нового
свойства окна. А если переменная объявляется в сценарии, который находится ниже
именованного элемента, первая же инструкция присваивания значения этой
переменной затрет значение неявно созданного свойства.


var/let d;
d = document.querySelector('#xxx');
d.innerHTML='bla-bla';

Этот вариант предпочтительней (безопасней), поскольку, если где-то в вашем коде будет объявление var переменной с таким же именем как значение id глобальная переменная xxx уже не будет ссылаться на html элемент.
Спасибо за комментарий.

Возник один вопрос: что вы имеете в виду, под особенностями eval в devtools? Разве при открытии консоли браузер начинает как-то по другому обрабатывать скрипт?
Статья была ещё раз обновлена.

Добавлена проверка на то, влияет ли только чтение (без записи) глобальных переменных на производительность. Оказалось, что нет. Выводы также соответствующим образом скорректированы.

Ещё раз извиняюсь, что добавляю информацию уже после публикации, но раз пришла мысль, думаю было бы неправильно её не добавить, тем более возникший нюанс довольно существенный.

Спасибо!
Спасибо, название браузера подправил.

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

По поводу выводов относительно простенького примера. Исходя из нескольких прогонов в разных конфигурациях, которые приведены в статье, очевидна корреляция между количеством обращений к переменным в глобальной области видимости и соответствующим снижением производительности. Я проверял помимо этого еще на Watefox'е, и, во всех трёх браузерах была результаты согласовывались. И, да в Watefox'е тоже было больше 30 секунд, и, я понимаю, что он является форком Firefox.

Кроме того, именно простота этих примеров позволяет увидеть «чистую» производительность при проведении самых простых операций, которых подавляющее большинство. Однако, не спорю, в каких-то более экзотических ситуациях возможно результаты будут иные, но я надеюсь, что выводы полученные из данной статьи, помогут лучше сориентироваться в них.
Я сильно извиняюсь перед теми, кто уже успел прочитать статью. Дело в том, что уже опубликовав я обратил внимание, на то, что при использовании let объявлений, просадка в скорости оказалась сильнее чем в случае с var, особенно в FireFox. Соответственно я внёс в статью соответствующую отсылку на данный факт и изменил 3-ий пункт выводов, так как, с учетом этого обнаруженного нюанса в случаях необходимости использования переменных с глобальным контекстом вариант с var видится предпочтительным. Спасибо!
В продолжение темы производительности let и var переменных, затронутой здесь в самом конце, предлагаю ознакомиться с моей новой статьёй Зависимость производительности кода от контекста объявления переменных в JavaScript. Спасибо!
Статья обновлена: помимо несущественных правок и изменения оформления, добавлено немного информации про работу метода Array.prototype.map(). Если кому интересно, можете ознакомиться.
Спасибо.
Про музыку: есть такое явление, как возврат моды — тогда вспоминаются 60-е, 70-е, 80-е и иногда частично переносится звучание от туда, но с новым оттенком. Как самый простой пример ремейки старых композиций.
А касательно плохоспроектированного — почему речь должна идти именно о плохоспроектированном. Можно допустим построить концепт самолёта, такой, для прочности которого нужны другие материалы, недоступные сейчас. Он не сможет летать сейчас так как хотелось бы, но сможет потом.
Если мы говорим о фундаментальных законах физики, то да, они зачастую неизменны, но и в них иногда вносятся поправки временем, как в закон всемирного тяготения Ньютона например. А если говорить о среде, тут дело другое — сейчас мы летаем на самолётах, но кто сказал, что потом не будем летать на условных летающих тарелках с антигравитационной тягой, а не турбореактивной и какие-то инженерные идеи пригодятся для них, а какие-то канут в лету, а может так станется, что вспомнится какая-то ранее забракованная идея, которая вполне будет уживаться в рамках полётов на летающих тарелках. Вопрос во взятом масштабе.
В музыке есть такое понятие как мейнстрим. В отличие от других направлений, мейнстрим, в среднем, не противоречит радикально вкусам большинства, подходит под большинство ситуаций и при этом не вызывает ощущения сильного диссонанса, тогда как андеграунд сцена, классическая или этническая музыка, например, подходят чаще только под конкретные ситуации, и, к тому же, имеют как своих отдельных ценителей, так и ярых противников, для которых большая часть того или иного жанра является явно неприемлимой для восприятия. Всё дело в природе мейнстрима, который постоянно вовлекает в своё звучание и мисседж отдельные элементы, наиболее удачные, с точки зрения восприятия окружающими, из других стилей и жанров. Эти элементы соответствуют конкретному культурному «состоянию», в котором они звучат уместно (современно, модно). Также из мейнстрима удаляются «устаревшие» элементы, по тем же критериям, соответственно. Что характерно, другие стили также не стоят на месте и, вместе со своим развитием, часто плодят новые жанры. И, не стоит забывать, что отброшенные прежде музыкальные элементы часто находят себе вторую жизнь как в новых для них стилях, так и возвращаясь в старые, уже после, и в других сочетаниях, порождая новое общее звучание и мисседж. То есть, получается одна стабильная линия, которая хоть и устойчивая, но достигающая эту самую устойчивость за счет своей непрерывной изменчивости, и, множество менее стабильных, часто сильно флуктирующих, но вместе с тем порождающих нужные мутации, в том числе и для «стабильной».
Это я к чему — а к тому, что Принцип Анны Карениной, безусловно есть и в IT, и в других сферах, но нужно понимать, что делая что-то похожим на удачную систему, мы получаем тот же мейнстрим, который также будет хорошо сконфигурирован и использующим best practicies, актуальными прежде всего на текущий момент времени в текущей среде. Используя же экзотику, шанс нарваться на неработающий где-то приём возрастает пропорционально её экзотичности. Однако, именно экзотичность привносит что-то новое в среду, меняя её и открывая дорогу как новым приёмам, так и старым, незаслуженно забытым ранее (или точнее заслуженно, но на тот момент времени). Часто экзотика просто опережает своё время, находя применение только в, уже, другой среде. Поэтому, естественно, всегда, самым надежным и гарантированно работающим будет способ максимально близкий к успешному в данный момент времени. Но время течет, меняется среда — меняется способ, и, через n-ое количество лет, возможно никто и не подумает решать ту или иную задачу так, как сейчас может казаться безальтернативным.
Поэтому, понимание текущей среды и динамики её развития позволит как внедрять в свои системы «правильную» экзотику, так и задавать, тем самым, «моду» на мейнстрим.
Выше, уже ответили почему подобный код не корректен.
А в целом, рекомендую внимательней ознакомится как со статьёй, так и с комментариями к ней, в частности, в которых уже подробно разобрали случаи с Array.prototype.fill().
Получится
/*
[…]
0: Array(5) [ <5 empty slots> ]
1: Array(5) [ <5 empty slots> ]
2: Array(5) [ <5 empty slots> ]
3: Array(5) [ <5 empty slots> ]
4: Array(5) [ <5 empty slots> ]
*/

а надо
/*
[…]
0: Array(5) [ undefined, undefined, undefined, … ]
1: Array(5) [ undefined, undefined, undefined, … ]
2: Array(5) [ undefined, undefined, undefined, … ]
3: Array(5) [ undefined, undefined, undefined, … ]
4: Array(5) [ undefined, undefined, undefined, … ]
*/

дополнив то, что вы написали, этого можно достичь так:
new Array(5).fill(void 0).map(() => new Array(5).fill(void 0));
или, еще проще:
new Array(5).fill().map(() => new Array(5).fill());

впрочем, чуть выше napa3um уже привел подобный пример.
Спасибо, здесь вы правы, учту.
Array.from()
Настольные
Возможность Chrome Firefox (Gecko) Internet Explorer Opera Safari
Базовая поддержка 45 32 (32) Нет Нет 9.0

Мобильные
Возможность Android Chrome для Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Базовая поддержка Нет Нет 32.0 (32) Нет Нет Нет

И, я не призываю писать всё время на ES5, повторюсь: цель статьи была не в том, чтобы обязательно вывести в ней какой ультимативный способ для создания массивов/матриц, а для разъяснения принципов работы некоторых внутренних инструментов JavaScript. И ничего против использования Array.from(), Array.prototype.fill(), оператора spread и прочего, я не имею.

diomas выше писал:
так тоже будет ожидаемый результат:

const matrix = Array.from(Array(5), () => Array(5).fill())

Ваш код аналогичен.

Кроме того, касательно Array.from() — я уже писал в комментарии выше — этот метод работает только в ECMAScript 6 и до сих пор не имеет поддержки в некоторых браузерах (Array.from()), метод же Function.prototype.apply() единственный, который может использоваться везде или практически везде, но прежде всего целью статьи было, показать через внутренний принцип работы некоторых функций то как JavaScript работает с массивами и подобными им объектами.
Оператор spread, в частности, был, приведен в статье в качестве примера, в том числе, для того, чтобы показать, что не только Function.prototype.apply(), может проводить подобные фокусы. Использование других методов вроде from() и fill() конечно вполне себе законно и расширяет возможности, и где-то красоту, поэтому я, естественно согласен, что их можно и нужно использовать в соответствующих ситуациях.

На счет производительности думаю вы правы, но мне кажется этот вопрос выходит за рамки данной статьи. Но в качестве «справочно» сойдёт :)
Прост для полноты положу тут, как красиво создавать заполненные (неразрежённые) массивы без грязных хаков с апплаями:

const matrix = new Array(5).fill(
  new Array(5).fill()
)

Про то, что этот способ не вполне рабочий уже было сказано выше и вы в последующем комментарии уже сами свой вариант обновили, поэтому, хотелось бы остановиться на «хаках с апплаями».

На мой взгляд приведенный в посте способ с apply() самый простой для применения в средах исполнения до ECMAScript 6. Кроме того, описание работы конструктора Array и метода Function.prototype.apply() (касательно разбивки объекта подобного массиву), даёт лучшее понимание, того как работают остальные, в том числе, новые, методы со схожим функционалом в части работы с подобными объектами.
Спасибо всем, кто дополнительно привел свои варианты и тем кто нашел в части из них ошибки — ваши комментарии прекрасно дополняют материал.
1

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Зарегистрирован
Активность

Специализация

Десктоп разработчик, Системный инженер
Ведущий