Image


Начитывая очередную статью про малоизвестные фичи языка JavaScript и втихую пописывая какие-то невменяемые решения в консоли браузера, я часто проговариваю в голове мол ну на проде то конечно все не так!? Ведь язык давно обзавелся огромнейшим комьюнити и имеет удивительно широкий охват промышленной разработки. Раз так, то почему же мы часто забываем про его возможность быть понятным для каждого и буквально пропагандируем все эти специфичные и "запоминаемые" конструкции? Just make it Obvious!


Рассуждения на тему


Эту графоманию можно пропустить.


Если говорить о промышленной разработке, то в подавляющем большинстве случаев требование к коду быть поддерживаемым даже важнее, чем решать поставленную бизнесом задачу. Для многих это очевидно, для некоторых — отчасти (встречаются конечно и редкие Д'Артаньяны). Чем понятнее наш код, тем меньше рисков для него — попасть на пыльную полку, а для нас и наших преемников — заработать проблем с нервной системой.


Не секрет, что JavaScript удивителен своей гибкостью, что является как его величайшим достоинством, так и досадным проклятием. Путь JavaScript-разработчика долог и крайне интересен: мы поглощаем книжку за книжкой, статью за статьей и набираемся уникального опыта, но местами — действительно language-специфичного. Широчайшее распространение языка и в то же время богатое число накопившихся и подкармливаемых неочевидностей способствуют образованию двух фронтов: тех, кто едва ли не боготворит этот язык, и тех, кто смотрит на него как на неуклюжую и качающую права утку.


И все бы ничего, но часто представители обоих фронтов работают на одном проекте. И обычной, всеми принятой практикой является непонимание (нежелание понимать и даже игнорирование) кода друг друга. И в самим деле, "я же на Java-разработчика устраивался, а не это ваше!". Масла в огонь подливают и сами JavaScript-последователи мол "никто на самом деле не знает JavaScript!" да "я могу это в одну строчку написать на js!". Каюсь, что и сам злоупотребляю на досуге ненормальным программированием...


Эту проблему начинаешь чувствовать, когда занимаешь место маргинала и приобретаешь некоторый опыт работы с людьми и их кодом по обе стороны от баррикад. Планинг и прочие митинги проходят продуктивнее, когда все разработчики понимают друг друга не только на уровне бизнес-сторей, но и хоть немного на уровне их имплементации. Пресловутый бас-фактор меньше сказывается на проекте, когда в случае болезни единственного фронт-ендера остальные члены команды не брезгуют поправить какую-то строчку .js файла. Процесс обмена знаниями в команде и за ее пределы становится прозрачнее для всех, когда каждый имеет более детальную картину. Ну и все в том же духе.


Я никого не призываю "фулстекнуться" или "T-шейпнуться" (как сейчас правильно говорить?), но почему бы нам немного не приподнять этот занавес хотя бы со стороны JavaScript-сообщества? Для этого достаточно лишь привнести немного явности в наш код, используя гибкость языка не чтобы выпендриться, а чтобы нас понимали.


Взросление и принятие ответственности


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


Изначально разработанный для веб-дизайнеров этот "самый неправильно понятый язык программирования" долгое время топтался на месте, несмотря на стремительно растущую популярность и значимость. За 13-14 лет, предшествующие редакции ECMAScript 5.1, трудно вспомнить какие-то важные изменения в стандарте или понять вектор его развития. В то время огромный вклад в формирование экосистемы языка вносило его комьюнити: Prototype, jQuery, MooTools и проч. Получив эту обратную связь от разработчиков, JavaScript проделал значительную работу над ошибками: громкий 6-летний релиз ES6 в 2015 году и теперь уже ежегодные релизы ECMAScript, благодаря переработанному комитетом TC39 процессу внесения новых возможностей в спецификацию.


Что ж, когда наши приложения стали достаточно большими, прототипная модель ООП для описания пользовательских типов перестала себя оправдывать из-за непривычного подхода. Ну серьезно, что это?


function Animal() {
/* Call me via new and I will be the constructor ;) */
}
function Rabbit() {}

Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;

В языке не появились классы, но появился их синтаксис. И код стал доступен для приверженцев традиционной класс-ориентированной парадигмы:


class Animal {
    constructor() {
    /* Obviously, the constructor is here! */
    }
}
class Rabbit extends Animal {}

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


В то же время, в языке, где функция является объектом первого порядка и имеет место постоянная событийность, совершенно обычное дело:


let that = this;
setTimeout(function() {
    that.n += 1;
}, 1000);

И тут начинаются объяснения о контекстах this и замыкании в JavaScript, что отпугивает каждого второго внешнего разработчика. Но во многих случаях, язык позволяет избежать лишних удивлений, явно используя Function.prototype.bind или вовсе так:


setTimeout(() => this.n += 1, 1000);

У нас тоже появились стрелочные функции, и это действительно — функции, а не функциональные интерфейсы (да, Java?). Вместе с расширенным набором методов работы с массивом они также помогают писать привычный декларативный пайплайн вычислений:


[-1, 2, -3, 4]
  .filter(x => x > 0)
  .map(x => Math.pow(2, x))
  .reduce((s, x) => s + x, 0);

Язык по праву считает себя мультипарадигменным. Но вот простой пример про сигнатуру некоторой функции:


function ping(host, count) {
    count = count || 5;
    /* send ping to host count times */
}

Сначала проходящий мимо задастся вопросом мол вероятно функция может принимать только первый аргумент, а потом мол какого черта в этом случае count становится булевом!? И действительно, функция имеет два варианта использования: с указанием count и без. Но это совершенно неочевидно: приходится смотреть в реализацию и понимать. Разобраться может помочь использование JSDoc, но это не общепринятая практика. И здесь JavaScript пошел навстречу, добавив поддержку не перегрузки, но хотя бы дефолтных параметров:


function ping(host, count = 5) { /* ... */ }

Резюмируя, JavaScript обзавелся огромным числом привычных вещей: генераторы, итераторы, коллекции Set и словари Map, типизированные массивы, да даже регулярные выражения начали радовать поддержкой lookbehind! Язык делает все, чтобы быть пригодным для многих вещей и стать дружелюбным для всех.


Благоприятный путь к очевидному


Сам язык — безусловно молодец, и с этим трудно спорить! Но что не так с нами? Почему мы постоянно напоминаем всему миру, что JavaScript все таки какой-то не такой? Давайте посмотрим на примеры некоторых широко используемых приемов и зададимся вопросом их целесообразности.


Приведение типов


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


let bool = !!(expr);
let numb = +(expr);
let str = ''+(expr);

Эти трюки известны каждому JavaScript-разработчику и мотивируются они тем, что мол так можно "быстро" превратить что-то во что-то: под быстротой здесь понимается короткая запись. Может еще и false записывать сразу как !1? Если разработчик так сильно переживает за печатаемые символы, то в его любимой IDE можно без труда настроить необходимый live template или автокомплит. А если — за размер публикуемого кода, то мы всегда прогоняем его через обфускатор, который знает получше нашего как все это обесчеловечить. Почему не так:


let bool = Boolean(expr);
let numb = Number(expr);
let str = String(expr);

Результат — такой же, только понятен всем.


Для строковых преобразований у нас есть toString, но для численных есть интересный valueOf, который может быть тоже переопределен. Классический пример, который вводит в ступор "непосвященных":


let timestamp = +new Date;

Но ведь есть у Date известный метод getTime, давайте использовать его:


let timestamp = (new Date()).getTime();

или готовую функцию:


let timestamp = Date.now();

Абсолютно незачем эксплуатировать неявное приведение типов.


Логические операторы


Отдельного внимания достойны логические операторы И (&&) и ИЛИ (||), которые в JavaScript не совсем логические: принимают и возвращают значения любого типа. Вдаваться в детали работы вычислителя логического выражения не будем, рассмотрим примеры. Ранее представленный вариант с функцией:


function ping(host, count) {
    count = count || 5;
    /* ... */
}

Вполне может выглядеть следующим образом:


function ping(host, count) {
    // OR arguments.length?
    if (typeof count == 'undefined') {
        count = 5;
    }
    /* ... */
}

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


Это скорее кажется дикостью для разработчика, который изначально выбрал путь JavaScript. Но для большинства других — вот этот код действительно дикий:


var root = (typeof self == 'object' && self.self === self && self) ||
    (typeof global == 'object' && global.global === global && global);

Да, это компактно, и да, это могут позволить себе популярные библиотеки. Но, пожалуйста, давайте не злоупотреблять этим, так как наш код будут читать не контрибьюторы в JavaScript, а разработчики, решающие бизнес-задачи за выделенные сроки.


Может встретиться и вовсе такой паттерн:


let count = typeof opts == 'object' && opts.count || 5;

Это определенно короче обычного тернарного оператора, но при чтении такого кода первым делом вспоминаешь приоритеты используемых операций.


Если же мы пишем функцию-предикат, которую передаем в тот же Array.prototype.filter, то обернуть возвращаемое значение в Boolean — это хороший тон. Сразу становится очевидно назначение этой функции и не возникает диссонанса у разработчиков, языки которых имеют "правильные" логические операторы.


Побитовые операции


Распространенный пример проверки наличия элемента в массиве или подстроки в строке с помощью побитового НЕ (NOT), который предлагается даже некоторыми учебниками:


if (~[1, 2, 3].indexOf(1)) {
    console.log('yes');
}

Какую проблему это решает? нам не приходится осуществлять проверку !== -1, так как indexOf получит индекс элемента или -1, а тильда прибавит 1 и поменяет знак. Тем самым выражение будет оборачиваться "ложью" в случае индекса -1.


Но избежать дублирования кода можно и по-другому: вынести проверку в отдельную функцию какого-нибудь utils-объекта, как это делают все, чем использовать побитовые операции не по назначению. В lodash для этого есть функция includes, и работает она не через жопу тильду. Можно возрадоваться, так как в ECMAScript 2016 закрепился метод Array.prototype.includes (у строк тоже есть).


Но не тут-то было! Еще тильду (наравне с XOR) используют для округления числа, отбрасывая десятичную часть:


console.log(~~3.14); // 3
console.log(2.72^0); // 2

Но ведь есть parseInt или Math.floor для этих целей. Побитовые операции здесь удобны для быстрого набора кода в консоли, так как они к тому же имеют низкий приоритет перед остальной арифметикой. Но на код-ревью такое лучше не пропускать.


Синтаксис и конструкции языка


Некоторые странные практики трудно отнести к какому-то конкретному разделу. Например, говорят, что скобки при вызове конструктора необязательны и следующие два выражения идентичны:


let rabbit = new Rabbit();

let rabbit = new Rabbit;

И это действительно так! но зачем создавать вопрос на пустом месте? Не каждый язык может похвастать такой "особенностью". А если все таки хочется, то пусть это будет соглашением по всему проекту. Иначе возникает ложное чувство, что есть какая-то разница.


Похожая ситуация с объявлением набора переменных. Синтаксис директив var и let позволяет объявить (и определить) сразу несколько переменных, перечисленных через запятую:


let count = 5, host, retry = true;

Кто-то использует переводы строк для читаемости, но в любом случае такой синтаксис — не частое явление в популярных языках. Никто не даст по рукам и не спросит, если написать так:


let count = 5;
let retry = true;
let host;

Опять таки, если есть соглашение о хорошем стиле на уровне проекта/компании, то вопросов нет. Просто не надо чересчур комбинировать варианты синтаксиса по настроению.


Есть в языке и вовсе специфичные конструкции, как например IIFE — позволяет вызвать функцию сразу по месту ее определения. Весь трюк в том, чтобы парсер распознал функциональное выражение, а не деклорацию функции. И это можно сделать уймой разных способов: классически обернув скобками, через void или любой другой унарный оператор. И в этом нет ничего замечательного! Необходимо выбрать единственный вариант и не отходить от него без необходимости:


(function() {
/* ... */
}());

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


Появление синтаксиса классов в ES6 не было сопровождено привычными модификаторами доступа. А иногда разработчику хочется и на классах пописать, и приватность соблюсти. Что приводит к такому коду Франкенштейна:


class Person {
    constructor(name) {
        let _name = name;
        this.getName = function() { return _name; }
    }
    toString() {
        return `Hello, ${this.getName()}`;
    }
}

То есть в конструкторе для экземпляра создаются аксессоры, а приватность достигается их доступом к локальным переменным-свойствам через замыкание. Этот пример выглядит вполне даже лакончино, но это совершенно немасштабируемый подход, если вокруг него не построить документированное решение-фреймворк. Господа, давайте использовать либо имеющиеся классы (и ждать стандартизации приватных полей), либо популярный паттерн-модуль. Создавать какое-то промежуточное микс-решение здесь — такое себе, так как классы перестают быть классами, а код — вразумительным.


Подытоживая, здравой мыслью будет поделиться принятым в проекте стайл-гайдом, конфигом для линтера или просто фрагментами кода с коллегами, которые вносят в проект его не-JavaScript составляющую. Язык предлагает несколько вариантов буквально для каждой типовой задачи, поэтому улучшить понимание друг друга и попасть под общий знаменатель не составит труда (ну или почти).


Злоключение


Тема эта конечно холиварная и примеров можно привести гораздо больше, но основной посыл статьи о том, что не следует злоупотреблять неочевидностями в JavaScript там, где этого можно избежать. Природа языка — уникальна: позволяет писать как элегантные и выразительные (в меру "упоротые") решения, так и понятные и доступные для всех. Я в корне не согласен с расхожим мнением, что JavaScript "сам себя наказал" или "похоронен под грудой добрых намерений и ошибок". Потому что сейчас большую часть странностей демонстрирует не язык, а образовавшаяся вокруг него культура разработчиков и (не)равнодушных.

Комментарии (46)


  1. b360124
    12.12.2018 11:22

    Ожидал очередную головоломку, а тут очередная статья, о том что js не идеальный. JSLint спасает от всех неоднозначных фич языка, а так вообще есть TypeScript, Flow где вообще все в рамках приличия, поэтому проблемы js — РЕШАЕМЫ и надуманы )))


    1. cerberus_ab Автор
      12.12.2018 11:31

      К сожалению линтером не обойтись… точки с запятой, скобки да strict equals. Многие вещи вполне допустимы для JavaScript-разработчика по самой природе языка, но со стороны очевидны не всем. И я специально не брал во внимание какое-то типизированное надмножество: у каждого свое ) но ведь можно в рамках приличия и без него.


      1. b360124
        12.12.2018 11:58

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


        1. cerberus_ab Автор
          12.12.2018 12:22

          Мое скромное мнение в том, что не надо постоянно ссылаться на «легаси неочевидностей», показывая кому-то свой код ) Если вся команда — это JavaScript-мастера, то вопросов нет. Иначе стоит задуматься о возможностях языка быть понятнее, которыми он в конце концов располагает и не нуждается в deprecated (почти негде).


    1. argonavtt
      12.12.2018 13:09
      +1

      Практика показывает, что каждый настраивает линтер под себя, в итоге он не панацея. Проблем бы может быть с TypeScript не было, если бы ему опять таки не пришлось контактировать с библиотеками написанными на чистом js, в итоге внедрять его очень не просто, приходиться писать много типов, ну или добавлять через дженерик (а тогда теряется весь смысл в этой строгой типизации) особенно если это уже рабочий проект.


  1. Tankerxyz
    12.12.2018 12:42
    +1

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


  1. NLO
    00.00.0000 00:00

    НЛО прилетело и опубликовало эту надпись здесь


    1. cerberus_ab Автор
      12.12.2018 12:53

      А в чем вопрос? это не идентичные функции, просто примеры. Конечно надо дополнить нужной проверкой на число, на ноль если нужно. Поинт в том, что явная проверка аргумента понятнее себя ведет нежели специфичный «логический» оператор.


    1. waltter
      12.12.2018 23:14

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


      1. cerberus_ab Автор
        12.12.2018 23:23

        Довольно старая статья об этом: When is it OK to use == in JavaScript?.


        По большому счету есть кейсы когда можно, но нет кейсов — когда нужно )
        В библиотеках часто используют "когда можно", чтобы код сократить (личное наблюдение).


  1. NLO
    00.00.0000 00:00

    НЛО прилетело и опубликовало эту надпись здесь


    1. cerberus_ab Автор
      12.12.2018 13:09

      Там пример про значение аргумента по умолчанию в случае его отсутствия, не про валидацию.


  1. paratagas
    12.12.2018 13:07
    +2

    Чтобы избегать такого:

    function ping(host, count) {
        count = count || 5;
        /* ... */
    }
    

    как раз и были созданы параметры по умолчанию, упоминаемые выше. Вот как бы оно выглядело после рефакторинга:
    function ping(host, count = 5) {
        // count = count || 5;
        /* ... */
    }

    Поэтому приведенный автором способ улучшения
    function ping(host, count) {
        // OR arguments.length?
        if (typeof count == 'undefined') {
            count = 5;
        }
        /* ... */
    }

    ничем не лучше антипаттерна
    count = count || 5;


    1. cerberus_ab Автор
      12.12.2018 13:11

      Согласен ) Я поленился придумывать более сложный пример инициализации без привязки к аргументам функции: да и посыл, казалось, понятен был и так.


      1. NLO
        00.00.0000 00:00

        НЛО прилетело и опубликовало эту надпись здесь


  1. NLO
    00.00.0000 00:00

    НЛО прилетело и опубликовало эту надпись здесь


    1. cerberus_ab Автор
      12.12.2018 13:49

      Ответ про разный результат был дан выше. Пример не об этом и мне стоило более четко выражать свою мысль и делать акцент. Постараюсь исправить ситуацию:

      1. Верный вариант задать аргумент по умолчанию — это default parameters.
      2. Если все таки нужно определить переменную по месту, то лучше не использовать «логический» оператор с проверкой наличия чего-либо.
      3. Если в проверку еще добавляется валидация в несколько условий, то нормальный if statement еще более необходим. Хотелось бы верить, что это понятно (как было прокомментировано выше), но код с инициализацией переменной через кучу И и ИЛИ с проверками встречается.


      1. NLO
        00.00.0000 00:00

        НЛО прилетело и опубликовало эту надпись здесь


        1. cerberus_ab Автор
          12.12.2018 14:08

          Я пару раз видел изумление, когда показывал код тому же Java-разработчику. И да, считаю это хорошей практикой читать и понимать код не только JavaScript как для себя, так и для коллег. Мне эти операторы угодили, но в js они только называются «логическими» и часто понятны только js-разработчикам.


  1. ua9msn
    12.12.2018 13:56
    +3

    let timestamp = +new Date;
    Но ведь есть у Date известный метод getTime, давайте использовать его:
    let timestamp = (new Date()).getTime();

    Вообще то, правильно будет
    let timestamp = Date.now();


    1. cerberus_ab Автор
      12.12.2018 14:01

      Уух… мне стыдно, но я об этой функции позабыл. Спасибо большое! добавил )


  1. CoolCmd
    12.12.2018 14:45

    let str = ''+(expr);
    let str = String(expr);
    Результат — такой же, только понятен всем.

    строго говоря, не такой же. Если expr — это объект, у которого есть метод valueOf, то первый способ переведет в строку то, что вернул метод valueOf, а второй — то, что вернул метод toString.


    1. cerberus_ab Автор
      12.12.2018 14:51
      -1

      let d = new Date;
      ''+d; // Wed Dec 12 2018 14:47:27 GMT+0300
      +d; // 1544615247741

      Здесь будет строковое преобразование, так как один из операндов — явно строка. Но замечание очень дельное! с этим надо быть осторожным )


      1. CoolCmd
        12.12.2018 15:14

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


        1. cerberus_ab Автор
          12.12.2018 15:48

          Да, вы правы. Спасибо, что поставили на место.


  1. JTG
    12.12.2018 14:50
    +1

    Но во многих случаях, язык позволяет избежать лишних удивлений, явно используя Function.prototype.bind или вовсе так:
    setTimeout(() => this.n += 1, 1000);
    Оставляя в стороне «явные» отличия в поведении обычных и стрелочных функций (от которых у Гвидо волосы на спине бы встали дыбом), this настолько не стыкуется с синтаксисом классов, что надо или выпилить bind (=сделать код обратно несовместимым), или добавить self.

    Утрированный пример: допустим, есть графики D3 или highcharts, которые используют контекст в своих колбеках на всю катушку, и «старый» класс с классическим that = this, который строит конфигурацию для графика:

    function Config () {
      let self = this;
      self.field = 'foo';
      
      this.getConfig = function() {
        return {
          tooltip: {
            formatter: function () {
              return 'Value of ' + self.field + ' for X=' + this.x + ' is ' + this.y;
            }
          }
        }
      }
    }

    Если всё это дело попытаться переписать с использованием «новых» классов, сразу возникают нестыковки:

    class Config {
      constructor() {
        this.field = 'foo';
      }
    
      getConfig() {
        return {
          tooltip: {
            // Так потеряется значение this.field
            formatter: function () {
              return 'Value of ' + this.field + ' for X=' + this.x + ' is ' + this.y;
            },
            // Так станут недоступными this.x и this.y
            formatter: () => {return 'Value of ' + this.field + ' for X=' + this.x + ' is ' + this.y},
          }
        }
      }
    }

    Но как же хорошо, что в JS существует столько явных возможностей, среди которых есть даже IIFE. Так язык позволяет избежать лишних удивлений:

    formatter: (self => function() {return 'Value of ' + self.field + ' for X=' + this.x + ' is ' + this.y})(this)

    (Да, это потому, что либы писались ещё за царя Гороха. Но самое страшное, что так продолжают писать! Потому что пока в языке есть этот чёртов this, каждый будет вертеть им как хочет)


    1. cerberus_ab Автор
      12.12.2018 15:13

      Оох… этот контекст в highcharts! Библиотеки — это действительно отдельная тема )


      Я разделяю недовольство по поводу нечистой стрелочной функции с this в примере, но готов иногда пойти на это ради читаемости.


      Призыва переписывать все на классы не было. Но если бизнес-логика это позволяет, то пусть это будут классы… а не жгучая смесь подходов и танцы вокруг контекста.


      Я бы не относил IIFE к явным возможностям ) но это повсеместная штука, ее трудно игнорировать. Мысль была хотя бы не спорить о синтаксисе.


    1. mayorovp
      12.12.2018 19:13

      Так надо же добавить тот самый self = this первой строчкой в getConfig…


  1. JustDont
    12.12.2018 16:27

    Это как раз та область, где явно видно силу Typescript. У TS хватает своих проблем (основная из которых — в том, что окружающий мир в основном не TS), но вот в области превращения неочевидных конструкций JS в очевидные — он прекрасен.

    Хотя автор конечно безусловно прав в утверждении, что и на JS можно понятно писать.


  1. ilyapirogov
    12.12.2018 17:03

    Сделать поля приватными на уровне модуля на данный момент можно так:


    const nameField = Symbol('name');
    
    class Foo {
        constructor(name) {
            this[nameField] = name;
        }
    
        toString() {
            return `Hello, ${this[nameField]}`;
        }
    }


    1. cerberus_ab Автор
      12.12.2018 17:05

      Да, вариантов несколько. Попробуй только потом в этом разобраться…


    1. surefire
      13.12.2018 10:36
      -1

      const foo = new Foo('world');
      Object.getOwnPropertySymbols(foo).map(sym => foo[sym]); //  [ 'world' ]


      1. ilyapirogov
        13.12.2018 15:56
        -1

        В Java тоже можно доступ к приватным полям через рефлексию получить, но это же не делает их публичными :)


  1. inew-iborn
    12.12.2018 21:48

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


    1. megahertz
      13.12.2018 07:28

      Есть Airbnb JavaScript Style Guide, не совсем best practices, но частично и их захватывает. Пожалуй, наиболее популярное соглашение в мире JS.


  1. megahertz
    13.12.2018 07:45

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

    Можно добавить в список как сделать не очевидно — повсеместную замену обычных функций на стрелочные.


  1. epishman
    13.12.2018 12:09

    Со всем согласен кроме наезда на замыкания — по моему очень крутая штука.


    1. cerberus_ab Автор
      13.12.2018 12:12

      Это очень полезная штука ) на него не было наезда: там про сохранение контекста this или про расширение свойств экземпляра класса в конструкторе.


  1. rboots
    14.12.2018 15:24

    Идея статьи в том, что JavaScript читают и пишут, в том числе, разработчики из других языков и не нужно использовать фичи, не популярные в других языках. Но у меня зеркальный вопрос, а соблюдают ли такую практику разрабочтики других языков, чтобы их было удобно читать тем, кто привык разрабатывать на JavaScript? Почему мы должны перестраиваться? Только потому, что язык стал популярным и на нём стало писать много людей, которым лень нормально освоить синтаксис JavaScript?


    1. Free_ze
      14.12.2018 15:31

      Там проблемы с грамматикой, а синтаксис вполне себе си-подобный.

      соблюдают ли такую практику разрабочтики других языков

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


    1. cerberus_ab Автор
      14.12.2018 15:35

      Идея в том, что JavaScript позвляет это сделать без усилий в большинстве случаев. А на той, другой стороне как минимум две проблемы: 1) часто это строгие и не динамические языки и 2) на серверной стороне слишком много языков.


    1. inew-iborn
      14.12.2018 17:26

      как правило всякие неявные фишки языка используют в исключительных ситуациях.


      в javascript я часто встречаю


      if (foo === void 0)

      неужели это всегда необходимо? все же


      if (typeof(foo) === 'undefined')

      намного читабельнее и понятнее


      1. JustDont
        14.12.2018 17:41

        Это тот прекрасный код, где объективно нужно по-разному обрабатывать undefined и null, или же просто обычный выпендрёж?
        Потому что когда не надо — обычное "== null" таки куда понятнее.


        1. inew-iborn
          14.12.2018 18:06

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


        1. inew-iborn
          14.12.2018 20:30

          Постараюсь ответить более развернуто.


          Одно из правил в программировании гласит что нужно уменьшать сложность. Код должен читаться как книга, конечно есть вещи которые нужно изучить для понимания ЯП, с этим я не спорю.


          как одни из примеров:


          ()();

          да я понимаю зачем это нужно, но жестить всегда, потому как "так позволяет язык, так пишут во фреймворках...", без каких либо оснований нет(я про void 0 если что… и подобное...).


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


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


          как-то так...


      1. TheShock
        14.12.2018 17:46

        Самый читаемый и правильный вариант, который явно прописан в спецификации вот:

        if (foo == null)