RUVDS.com corporate blog
Website development
JavaScript
Comments 76
+2
Пункт 2
const obj = {a:1};
const myObjArray = [obj, obj];
const deDupeObj = [...new Set(myObjArray)];
const myArray = [{a:1}, {a:1}];
const deDupe = [...new Set(myArray)];
// Как и ожидалось
console.log(deDupeObj); // [{a:1}]
// Упс...
console.log(deDupe); // [{a:1}, {a:1}]

Эту ситуацию надо объяснить, раз уж взялись давать советы.
+8
Поведение ожидаемое и очевидное, что здесь объяснять?
+2
Для кого оно ожидаемое и очевидное, тот не почерпнет в статье для себя ничего нового. Ну а впрочем это только моё мнение…
0
то, как передаются объекты (по ссылке) — это первые главы любого учебника по JS
+1

ссылки — это азы, их в учебниках объясняют, а не в статьях, которые рассчитаны на людей, уже во всю херачащих на JS.


Для кого оно ожидаемое и очевидное, тот не почерпнет в статье для себя ничего нового.

такие вещи, как обмен значений 2х переменных — вещь неочевидная даже для человека, который знает о destructuring assignment.

0

+1, не приходило в голову, что destructuring assignment может работать и без декларации (const, var, let). Плюс я был почти уверен, что { ...myProperty && { propName: myProperty } } просто упадёт с ошибкой если myProperty будет non-object (не падает).

0
Я это знал, но удивился, что деструктуризация работает и в следующем случае:
function swap(array, index1, index2){
  [array[index1], array[index2]] = [array[index2], array[index1]];
}
0
В JS как и в других языках обьекты или экзмемпляры класса сравниваются по ссылке, а не по значению.

const obj = {a:1}
здесь вы создали обьект.
const deDupeObj = [...new Set(myObjArray)] 
здесь вы сравниваете обьект с самим собой.

const myArray = [{a:1}, {a:1}]
здесь вы создаете два обьекта, и соотвественно с разными ссылками.
const deDupe = [...new Set(myArray)]
здесь вы сравниваете ссылки на два разных обьекта.
+2
Да мне то Вы зачем это объясняете? Я прекрасно это знаю. Коммент о том, что тот, кому будет полезна статья, может не знать этих нюансов.
Сужу по вопросам на Тостере и в чатиках Телеграма. Частенько проскакивают попытки сделать массив уникальных объектов с помощью Set.
+6

1–12: Мама, я прочитал документацию Javascript! Смотри, как я могу!


13: На самом деле такое копирование обладает рядом недостатков. JSON имеет в своём составе только примитивные структуры. В массивах undefined (и дырки) станут null. В объектах просто будут удалены. Типизированные массивы сконвертируются в обычные. Date станет строкой. Все функции, Map и Set и некоторые другие вообще потеряются, подобно undefined!


Разумеется, потеряются и symbol-ссылки, и любые другие неперечисляемые свойства (которых не будет в for-in/Object.keys()), геттеры, сеттеры. Даже обычные объекты могут выдать что угодно, если у них (или в прототипе) есть метод .toJSON(). На циклических ссылках код вообще упадёт.

+1
13: На самом деле такое копирование обладает рядом недостатков.

Эти вот «недостатки» — они не обязательно недостатки, иногда они наоборот крайне полезны. Единственное, что тут нужно иметь в виду, что прогон объектов через JSON — это всё-таки НЕ клонирование.

И, кстати, оно очень даже быстро работает; делая клонирование через обход дерева объектов руками, вполне можно сделать даже медленнее.
0
Если понадобилось копировать сложный объект, скорее всего где то выше в архитектуре проблемы.
0
Ну щас, ага.

У вас есть стейт приложения. Пользователь в этот стейт вносит изменения, которые должны будут примениться по нажатии кнопки «применить изменения». Как будете хранить «грязный» стейт? Как дельту к чистому? И если никакие undo-redo заведомо не нужны — всё равно будете дельты хранить? И будете писать код по созданию дельт и их накатыванию, вместо того, чтоб отклонировать чистый стейт, а потом залить грязный поверх чистого (в одну строчку)?
0
Угу. 64Кб жаваскрипта. Повторить двадцать раз на любой чих, а потом удивляться, чё это у нас ничего особенного не делающие страницы грузят мегабайты скриптов.

И к слову о этой ветке комментариев, Immutable.js — это как раз таки копирование объектов во все поля. Только зашитое в либу.
0
А вот и нет. Там хитрые внутренние механизмы, избегающие дублирования данных и лишней работы.
0
Там хитрые внутренние механизмы, избегающие дублирования данных и лишней работы.

Никакая внутренняя хитрость не поможет вам сохранить два указателя на данные по той же цене, что и один указатель. Я в курсе, что immutable.js очень неплохо оптимизирован (и вообще либа концептуально хорошая), но то, что он делает — это всё равно копирование структур данных. Даже если копируется только левая сторона (указатели).
0
Immutable.js
Этот тот, в котором поля указываются строками и IDE никак не может помочь с написанием кода? «Гениальное» решение.

Люди уже давно на TS переходят, а некоторые — до сих пор в динамику бросаются
0
Ну да, проблема абсолютно всех IDE, которые не поддерживают одну очевидно динамическую библиотеку, а не какой-то одной кривой динамической библиотеки.
0
Да уж, я вчера был очень пьяный и написал очень глупую вещь. Я пытаюсь реконструировать, как пришёл к этой мысли, но не получается.
0

Решал подобную задачу. Активно использовал иммутабельность (без immutable.js). В итоге для redo/undo не хранил никаких дельт, а хранил ссылку на предыдущий стейт целиком. Сами понимаете, вес одного undo-среза был крошечный.


Вопрос с сериализацией и десериализацией решил просто: кастомный cloneObject метод, который используя Map упаковывал state в json, где все circular references были строкой вида $circular$firstPath. И был такой же метод десериализации который строил такой же объект восстанавливая все ссылки. На всё про всё строк 30 кода.

+2

Хорошо вам там. В плюсах аналогичный подход почти наверняка уронит sort и прочие алгоритмы, ожидающие корректный порядок.

+1
Вот прям уронит? Мне кажется, все быстрые алгоритмы сортировки должны такой компаратор нормально переварить, потому что у них тупо нет времени, чтобы наткнуться на неконсистентность. Потому что неконсистентность — это когда мы получили результат сравнения, противоречащий предыдущим результатам. Но если он противоречит им — значит, мы могли его знать ещё до того, как провели сравнение (предполагая, что компаратор «нормальный»).
+2

Можно вылезти за границы массива, например, или уйти в бесконечную рекурсию/цикл, или ещё чего натворить. Вот я сконструировал пример, который валит libstdc++, примерно с третьей попытки.


Собственно, эта страница приоткрывает завесу: требуется, чтобы предикат описывал некоторый strict weak order со всей его аксиоматикой.


На CppNow 2019 был доклад Гашпера про порядки и как они относятся к C++20'овому operator spaceship, и в лицах ряда людей читалось непонимание математической части (хотя там ничего принципиально сложного не было, хоть я и дико не согласен с философией его подхода). Но это так, навеянные мысли вслух к вопросу о том, зачем программистам математика.

+1
Или на Питоне. Мой любимый, квиксорт в одну строку:
qs = lambda L: [] if L==[] else qs([x for x in L[1:] if x<L[0]]) + L[0:1] + qs([x for x in L[1:] if x>=L[0]])
0
Не очень хороший выбор pivot. Должен плохо работать для почти отсортированных массивов.
Зато нагляднее некуда.
0
Да, тут скорее для наглядности или искусства ради. У него есть проблема есть и посерьёзнее, будучи рекурсивным не будет работать для списков больше определённого размера. Причём не очень большого, порядка тысячи.
+1
Аж даже любопытно стало вспомнить перл и написать то же самое на нём =)
$qs = sub { my ($car, @cdr) = @_; @_ == () ? () : ($qs->(grep { $_ < $car} @cdr), $_[0], $qs->(grep { $_ >= $car} @cdr)) };
+1
Ну и грех было бы не перевести на javascript, раз уж это тема статьи:

const qs = a=>a.length?[...qs(a.slice(1).filter(e=>e<a[0])),a[0],...qs(a.slice(1).filter(e=>e>=a[0]))]:[];
+2
JSON.parse(JSON.stringify(originalObject));
Удачи в копировании объектов с properties NaN, Infinity, Function, и многими другими.
+1
Если честно, не люблю эти однострочники, некоторые из них просто щекочат эго джунов, не принося никакой пользы. Да, их надо знать, чтобы распознавать в чужом коде, но свой код должен быть кристально ясен.
Если вы удаляете дубликаты из массива, как насчет функции `removeDuplicates(arr, predicate)`? Вы можете реализовать ее оптимальным способом, она будет фурычить быстрее, а постороннему программисту будет сразу ясно — вот тут удаляют дубликаты. Ну или в каком-нибудь lodash такая функция наверняка есть. Вы можете гарантировать, что ваш код всегда будут читать любители и знатоки однострочников? Или может быть, в вашем проекте появляются иногда джуны, или переученные за две недели с джавы «фуллстэк-девелоперы»? Сколько времени это будет стоить вашей компании на промежутке в хотя бы год? Если ваша зарплата включает в себя процент от прибылей, такие вопросы — не праздные.

Мой вывод: такие вещи могут быть полезны только в случае если вы пишете код на выброс, для себя на один раз. Как только речь о серьезных продуктах и мало-мальских командах, такое ковбойство надо вымарывать.
0
Или вот пункт 8 — ну спотыкается взгляд о такие конструкции. А что если в объекте справа переименовалось свойство, а у вас нет тайпскрипта и приличной IDE, которая бы все подчеркнуло?
0
Пункт 13 — очень плохо давать такие советы. Во-первых, автор просто так ляпнул, что этот способ медленный. Он не медленный, он самый быстрый.
Во-вторых, применять этот способ нужно не когда у вас мало времени, а когда вы точно уверены, что объекты будут простенькие, без циклических ссылок или ссылок на какие-нибудь гигантские другие объекты и нужно чтобы это было супер-быстро, потому что делается миллиарды раз. Да я могу маленький движок графовой БД таким, что попытка `stringify` одного из узлов выведет полностью всю эту базу. Первый вопрос, который надо задавать человеку, делающем deepCopy — «а без deepCopy точно ничего не получится»?

0
Он не медленный, он самый быстрый.

В каком месте он самый быстрый? Я прошелся по тесту в Вашей ссылке, и мне показоло что он самый медленный, при этом в два раза медленее того же 'Object.assign({}, obj);'
0
Согласен, но собственно способ 'JSON.parse(JSON.stringify(obj))' копирует очень малую часть от самого объекта, так что я бы не стал их сравнивать, так как одно и другое далеко от полного копирования объекта.
0
Хахаха, а вот тут уже я, не подумав, ляпнул. И впрямь, самый медленный, на jsbench 100% — это максимум, чем меньше процентов тем лучше. Как удалять комменты на хабре
0
Пункт 12 — так выглядит код говнокодера и script-kiddy, не понимающего ни что такое инкапсуляция, ни что такое предусловия. Ты взял неизвестный тебе алгоритм сортировки, который ожидает консистентную функцию сравнения (если A>B, то B<A, а если A>B и B>C, то A>C) и передал туда фигню вместо консистентной функции сравнения. Как будет работать неизвестный тебе алгоритм, если передать в него не то, что он ожидает? Завершится ли он когда-нибудь? Если завершится, то за какое время? Ах, «но ведь оно работает, я пробовал во всех браузерах?»

P.S. это не обращение к авторам статьи, это просто моя клокочущая ненависть к таким идеям.
0
> Приведение значений к логическому типу
> myBoolean = !!myVariable;

А зачем?
Ведь, если myVariable = 1, то
if ( myVariable ) {
    // я и без boolean, а на честном <s>слове</s> integer попаду в эту ветвь оператора if
}
+1
Вот тоже не знаю зачем это делать, разве что при проверке ===, хотя тоже не понятно зачем это делать, помню даже когда то спорил с одним 'специалистом', который с пеной у рта доказывал что нужно писать именно 'if (!!myVariable)', хотя так и не смог привести доказательства, когда данный пример будет отличаться от 'if (myVariable)'
0
Так делать нужно, очевидно, когда вам нужен именно boolean (для дальнейших вычислений). Использовать это прямо в ифах — довольно бессмысленная затея, а вот «утечь» строку в код, который ожидает именно boolean (скажем, в сериализацию) — опасно.
0
Полностью с Вами согласен, я лишь приводил пример только про ифы.
+1
Чаще всего подобное приведение к boolean встречается в конструкциях типа switch(true), т.к. switch как раз сравнивает строго:
const myVariable = 1;
switch (true) {
    case myVariable:
       console.log('Эта ветка не отработает');
       break;
    
    case !!myVariable:
        console.log('А эта отрабтает');
        break;
}
+2
Как я уже писал выше, это было сказано лишь для ифа, а для строгого сравнения — да, можно такое пременить.

Но чисто для интереса хотело бы спросить, где вообще такой свитч можно применить? Не лучше для данного примера использовать обычный if/else?
+1

Такой switch можно использовать если очень хочется не пройти code-review :)

0
Для данного конкретного, естественно, if'а достаточно. Даже просто && обойтись можно. Это была просто демонстрация того, что switch требует строгого равенства, и если об этом забыть, можно получить неожиданный результат.

А сам switch(true) именно с необходимостью приводить к boolean я нередко наблюдал в ситуациях, подобных вот этой:
getSomeMeasurementAsync((err, data) => {
    switch (true) {
        case !!err:
            handleError(`Error getting measurement`, err);
            break;

        case !!data.error:
            handleError(`Measurement error`, data.error);
            break;

        default:
            handleMeasurement(data.measurement);
    }
});

+1
Задумка конечно интересная, запомню, может когда нибудь пригодиться, но даже в данном случае я бы не рекомендовал использовать данную структуру.
+1
Чем тут обосновано использование switch'a и '!!'? Чисто «выпендриться»?
getSomeMeasurementAsync((err, data) => {
  if (err) {
    return handleError(`Error getting measurement`, err)
  } else if (data.error) {
    return handleError(`Measurement error`, data.error)
  }
  return handleMeasurement(data.measurement)
})
0

!!variable очень распространённый "хак". Используется когда вам нужно гарантировано привести к boolean-у и не хочется писать variable ? true : false. Т.е. не в if(), until(), repeat() и пр., а скорее при формировании нового объекта где по контракту должен быть boolean.

0
Кажется я в третий раз буду писать про это :)
Я знаю про этот «хак», и тоже использую его временами, и как написали Вы, и написали выше, он очень хорошо подходит для строгого сравнения, либо же при сериализации, но я лишь писал про ифы, не более того :)
0
Вообще я не понимаю от куда пошла мода писать if(!!...), так как где то два года назад, много кто мне об этом начал говорить, хотя выводов так и не предоставляли, лишь в стиле «так надо», как будто кто то, что то ляпнул, и все как стадо начали повторять…
0

Ну а если у вас какой-нить filter, и вы не знаете, что у него внутри — строгое или нестрогое сравнение?

0
У filter внутри сравнение такое, которое вы ему туда положите. RTFM -> «Falsy/truthy values»
+2
Ну boolean часто нужен всё-таки. Выше про сериализацию писали. Иногда надо сохранить логическое значение надолго, а прилетает всё что угодно. Приведение к boolean перед сохранением позволяет отпустить значение для GC.

Но я сам очень не люблю
!!value
и тупо пишу
Boolean(value)
+3
const myObject = { ...myProperty && { propName: myProperty } }

Да ну, такое надо на ревью обратно отправлять.
0

А почему бы не просто "myProperty ? { propName: myProperty } : {}"?


Вроде и понятнее, и символов меньше...

+1
Ну у меня обычно это объект с несколькими полями, вроде settings. Как-то так:
{
  value1,
  value2,
  ...condition1 && {
    value3,
    value4,
  },
  ...condition2 && {
    value5,
  },
}

Можно конечно писать
{
  value1,
  value2,
  ...condition1 ? {
    value3,
    value4,
  } : {},
  ...condition2 ? {
    value5,
  } : {},
}
но эти пустые объекты мои глаза немного ддосят.
+1
А как было бы лучше? if'ами?

Да, лучше if'ами
let config = {
  value1,
  value2,
}
if (condition1) {
  config = { ...config, value3, value4 }
}
...
+1
А как было бы лучше? if'ами?
А ещё лучше, чтобы у вас был строго-типизированный код и наполнение объектов полями не зависело бы от if'ов
+1
Пункт 10. Быстрое создание числовых массивов
На MDN есть еще вот такой пример:
// Генерирования последовательности чисел
Array.from({ length: 5 }, (v, k) => k); 
// [0, 1, 2, 3, 4]
-1
А зачем тут вообще from?
Чем плох классический spread-вариант?
[...Array(5).keys()]
0
А разве однострочные конструкции не затрудняют чтение кода в больших объёмах?

Всегда казалось, что например написать `const myBoolean = Boolean(myVariable);` более очевидно для понимания, нежели играться с восклицательными знаками. Впрочем это актуально для любого приведения типа, будь то строка или число.
0
По-моему вот так
const code = Math.floor(Math.random() * Math.pow(10,6)).toString().padStart(6, "0");

Будет чище. Не придется считать нули :)
Only those users with full accounts are able to leave comments. , please.