В июне 2018 года стандарт ECMAScript 2015 (ES6) отметил свой трёхлетний юбилей. В ES6, во-первых, появилось множество новых возможностей JavaScript, во-вторых, с этого стандарта начинается новая эра развития языка. Кроме того, это был последний масштабный релиз JS, так как теперь TC39 применяет схему выпуска небольших ежегодных выпусков стандарта, а не выводит его новую редакцию раз в несколько лет.



Последние 4 года ES6, вполне оправданно, привлекает к себе всеобщее внимание. Автор материала, перевод которого мы сегодня публикуем, говорит, что он, всё это время, благодаря Babel, писал весь код с использованием современного варианта спецификаций JS. Он полагает, что прошло достаточно времени для того, чтобы критически проанализировать новые возможности ES6. В особенности его интересует то, чем он некоторое время пользовался, а потом пользоваться перестал из-за того, что это ухудшало его код.

О слабых сторонах JS


Дуглас Крокфорд, в своей книге «JavaScript: сильные стороны», писал и о том, что можно считать слабыми сторонами языка. Это — нечто такое, чем, по его мнению, пользоваться не стоит. К счастью, среди новшеств ES6 нет ничего столь же неприглядного, как некоторые старые проблемные возможности JS, такие, как оператор нестрогого равенства, выполняющий неявное приведение типов, функция eval() и инструкция with. Новые возможности ES6 спроектированы куда лучше. Однако и в нём есть некоторые вещи, которых я избегаю. Те возможности, которые входят в мой список «слабых сторон» JS, попали в этот список по следующим причинам:

  • Они, по сути, являются «ловушками». То есть, кажется, что предназначены они для выполнения неких действий, и в большинстве случаев, работают так, как ожидается. Однако иногда они ведут себя неожиданно, что легко может привести к появлению ошибок.
  • Они увеличивают объём языка в обмен на небольшую выгоду. Такие возможности дают разработчику какие-то небольшие преимущества, но требуют от того, кто пытается разобраться с его кодом, знания о неких механизмах, обычно где-то скрытых. Это вдвойне справедливо для возможностей API, когда использование подобной возможности означает, что другой код, взаимодействующий с кодом, написанным неким разработчиком, обязан знать о применении этой возможности API.

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

Ключевое слово const


До выхода ES6 переменные в JavaScript можно было объявлять с использованием ключевого слова var. Кроме того, переменные можно было и вовсе не объявлять, тогда они, даже если используются в функциях, попадают в глобальную область видимости. Роль переменных могут играть свойства объектов, а функции объявляют с использованием ключевого слова function. У ключевого слова var есть определённые особенности.

Так, оно позволяет создавать переменные, добавляемые к глобальному объекту, или такие, область видимости которых ограничена функциями. Однако ключевое слово var не обращает внимания на блоки кода. Кроме того, обратиться к переменной, объявленной с помощью ключевого слова var можно и в коде, расположенном до команды её объявления. Это явление известно как поднятие переменных. Эти особенности, если их не учитывать, способны приводить к возникновению ошибок. Для того чтобы исправить ситуацию, в ES6 появились два новых ключевых слова для объявления переменных: let и const. Они решали основные проблемы var. А именно, речь идёт о том, что переменные, объявленные с использованием этих ключевых слов, имеют блочную область видимости, как результат, например, переменная, объявленная в цикле, не видна за его пределами. Кроме того, использование let и const не допускает обращения к переменным до их объявления. Подобное приведёт к ошибке ReferenceError. Это было большим шагом вперёд. Однако, появление двух новых ключевых слов, а также их особенности, привели к дополнительной путанице.

Значение переменной (константы), объявленной с помощью ключевого слова const, нельзя перезаписать после объявления. Это — единственное различие между const и let. Выглядит эта новая возможность полезной, и она, действительно, может принести определённую пользу. Проблема заключается в самом ключевом слове const. То, как ведут себя константы, объявленные с его помощью, не соответствует тому, что большинство разработчиков ассоциируют с понятием «константа».

const CONSTANT = 123;
// Эта команда приведёт к ошибке "TypeError: invalid assignment to const `CONSTANT`"
CONSTANT = 345;
const CONSTANT_ARR = []
CONSTANT_ARR.push(1)
// А эта команда выведет [1] без каких-либо сообщений об ошибках
console.log(CONSTANT_ARR)

Использование ключевого слова const предотвращает запись в константу нового значения, но не делает объекты, на которые ссылаются подобные константы, иммутабельными. Эта особенность даёт слабую защиту от изменения значений при работе с большинством типов данных. В результате, из-за того, что использование const может привести к путанице, и из-за того, что при наличии ключевого слова let наличие const выглядит избыточным, я решил всегда использовать let.

Тегированные шаблонные строки


Ключевое слово const — это пример того, как спецификация создаёт слишком много способов решения слишком малого количества задач. В случае с тегированными шаблонными строками перед нами обратная ситуация. Синтаксис таких строк рассматривался комитетом TC39 как способ решения задач интерполяции строк и работы с многострочными строками. Затем эту возможность решили расширить за счёт использования макросов.

Если вы раньше не встречались с тегированными шаблонными строками, учтите, что они немного напоминают декораторы для строк. Вот пример работы с ними с MDN:

var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) {

  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "

  // Технически (в нашем примере) 
  // после последнего выражения имеется строка,
  //но она пуста, поэтому не обращайте на неё внимания.
  // var str2 = strings[2];

  var ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  return str0 + personExp + str1 + ageStr;

}

var output = myTag`that ${ person } is a ${ age }`;

console.log(output);
// that Mike is a youngster

Тегированные шаблонные строки нельзя назвать совершенно бесполезными. Вот обзор некоторых вариантов их использования. Например, они полезны при очистке HTML-кода. И, в настоящий момент, их применение демонстрирует самый аккуратный подход в ситуациях, когда нужно выполнять одну и ту же операцию над всеми входными данными произвольного строкового шаблона. Однако, нужно такое сравнительно редко, сделать то же самое можно с помощью соответствующего API (хотя такое решение получается длиннее). И, для решения большинства задач, использование API будет не хуже применения тегированных шаблонных строк. Эта функция не добавляет в язык новых возможностей. Она добавляет в него новые подходы к работе с данными, с которыми должны быть знакомы те, кому придётся читать код, написанный с использованием тегированных шаблонных строк. А я стремлюсь к тому, чтобы мой код оставался как можно более чистым и понятным.

Переусложнённые выражения деструктурирующего присваивания


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

let conferenceCost = isStudent ? 50 : 200

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

let conferenceCost = isStudent ? hasDiscountCode ? 25 : 50 : hasDiscountCode ? 100 : 200;

То же самое можно сказать и о деструктурирующем присваивании. Этот механизм позволяет вытаскивать значения переменных из объектов или массивов:

let {a} = {a: 2, b: 3};
let [b] = [4, 5];
console.log(a, b) // 2, 4

Кроме того, при его использовании можно переименовывать переменные, получать вложенные значения, задавать значения по умолчанию:

let {a: val1} = {a: 2, b: 3};
let [{b}] = [{a:3, b:4} , {c: 5, d: 6}];
let {c=6} = {a: 2, c: 5};
let {d=6} = {a: 2, c: 5};
console.log(val1, b, c, d) // 2, 4, 5, 6

Всё это замечательно — до тех пор, пока дело не дойдёт до построения сложных выражений с использованием всех этих возможностей. Например, в нижеприведённом выражении объявляется 4 переменные: userName, eventType, eventDate, и eventId. Их значения берутся из разных мест структуры объекта eventRecord.

let eventRecord = {
  user: { name: "Ben M", email: "ben@m.com" },
  event: "logged in",
  metadata: { date: "10-10-2017" },
  id: "123"
};
let {
  user: { name: userName = "Unknown" },
  event: eventType = "Unknown Event",
  metadata: [date: eventDate],
  id: eventId
} = obj;

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

let eventRecord = {
  user: { name: "Ben M", email: "ben@m.com" },
  event: "logged in",
  metadata: { date: "10-10-2017" },
  id: "123"
};
let userName = eventRecord.user.userName || 'Unknown';
let eventDate = eventRecord.metadata.date;
let {event:eventType='UnknownEvent', id:eventId} = eventRecord;

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

Дефолтный экспорт


У ES6 есть одна приятная особенность. Заключается она в том, как его разработчики подошли к стандартизации того, что раньше делалось с помощью различных библиотек, нередко конкурирующих друг с другом. Так в спецификации появились классы, промисы, модули. Это — всё то, чем сообщество JS-разработчиков пользовалось до ES6, находя это в сторонних библиотеках. Например, модули ES6 представляют собой отличную замену того, что вылилось в войну форматов AMD/CommonJS, и дают удобный синтаксис для организации импорта.

Модули ES6 поддерживают два основных способа экспорта значений: именованный экспорт (named export) и дефолтный экспорт, или экспорт по умолчанию (default export):

const mainValue = 'This is the default export
export default mainValue

export const secondaryValue = 'This is a secondary value;
export const secondaryValue2 = 'This is another secondary value;

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

// дефолтный импорт
import renamedMainValue from './the-above-example';
// именованный импорт
import {secondaryValue} from './the-above-example';
// именованный импорт с переименованием
import {secondaryValue as otherValue} from './the-above-example';

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

  1. При использовании именованного экспорта имена экспортируемых переменных, по умолчанию, соответствуют именам импортируемых переменных, что упрощает их поиск для тех, кто не пользуется интеллектуальными инструментами разработки.
  2. При использовании именованного экспорта программисты, применяющие интеллектуальные инструменты разработки, получают такие удобные возможности, как автоматический импорт.
  3. Именованный экспорт даёт возможность единообразно экспортировать из модулей всё, что угодно, в нужных количествах. Дефолтный экспорт ограничивает разработчика лишь экспортом одного значения. В качестве обходного пути тут можно применить экспорт объекта с несколькими свойствами. Однако при таком подходе теряется ценность алгоритма tree-shaking, применяемого для уменьшения размеров JS-приложений, собираемых чем-то вроде webpack. Использование исключительно модулей с именованным экспортом упрощает работу.

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

Итоги


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

Уважаемые читатели! Есть ли в современном JavaScript что-то такое, чего вы стараетесь избегать?

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


  1. Ivanq
    17.07.2018 13:46
    +4

    Как всегда, притянули за уши.


    Проблема заключается в самом ключевом слове const. То, как ведут себя константы, объявленные с его помощью, не соответствует тому, что большинство разработчиков ассоциируют с понятием «константа».

    "Большинство разработчиков". Покажите мне этих разработчиков. Если человек использует const и до сих пор не понял, что это значит — либо разработчик никакой (передача по ссылке? это что такое?), либо никогда и не пробовал понять. Это проблема "большинства разработчиков", но никак не языка.


    Однако код, написанный с его помощью, становится сложно понять, если, применяя такой оператор, начать использовать вложенные конструкции:
    let conferenceCost = isStudent ? hasDiscountCode ? 25 : 50 : hasDiscountCode ? 100 : 200;


    let conferenceCost = (
        isStudent ? (
            hasDiscountCode ? 25 : 50
        ) : (
            hasDiscountCode ? 100 : 200
        )
    );

    Так понятнее? Спагетти-код можно написать на любом языке.


    let eventRecord = {
      user: { name: "Ben M", email: "ben@m.com" },
      event: "logged in",
      metadata: { date: "10-10-2017" },
      id: "123"
    };
    let {
      user: { name: userName = "Unknown" },
      event: eventType = "Unknown Event",
      metadata: {date: eventDate}, // кто-то опечатался
      id: eventId
    } = obj;


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

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


    function handlePost(post) {
        const [
            {name: authorName, email: authorEmail},
            ...moreAuthors
        ] = post.authors;
        // ...
    }

    Этот код понятен. Вот у нас есть пост, у него есть авторы, первого мы вынесем в переменные authorName и authorEmail, остальных запихнем в moreAuthors. А теперь тот-же код без rest/spread:


    function handlePost(post) {
        const authorName = post.authors[0].name;
        const authorEmail = post.authors[0].email;
        const moreAuthors = post.authors.slice(1);
        // ...
    }

    Сильно понятнее? Сразу поймете, почему slice(1)? Ну… нет.


    Однако на практике мне удалось выяснить, что пользоваться технологией именованного экспорта предпочтительнее по следующим причинам:

    Не зря сделали одновременно и default-экспорт, и именованный. default на то и default, что содержит основной экспорт. Например, если у Вас в папке хранятся плагины, экспортирующие функцию install:


    import {install as installPlugin1} from "./plugin1";
    import {install as installPlugin2} from "./plugin2";
    import {install as installPlugin3} from "./plugin3";

    Или:


    import installPlugin1 from "./plugin1";
    import installPlugin2 from "./plugin2";
    import installPlugin3 from "./plugin3";

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


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


    Вариант с export function install:


    const context = require.context("./plugins");
    for(const name of context.keys()) {
        context(name).install();
    }
    
    ...
    
    import {install as installPlugin1} from "./plugin1";
    import {install as installPlugin2} from "./plugin2";
    import {install as installPlugin3} from "./plugin3";

    Вариант с export function installPluginN:


    const context = require.context("./plugins");
    for(const name of context.keys()) {
        Object.values(context(name))[0]();
    }
    
    ...
    
    import {installPlugin1} from "./plugin1";
    import {installPlugin2} from "./plugin2";
    import {installPlugin3} from "./plugin3";

    Вариант с export default function install:


    const context = require.context("./plugins");
    for(const name of context.keys()) {
        context(name).default(); // Ну или как-то иначе, если будет "import ... from '*.js'"
    }
    
    ...
    
    import installPlugin1 from "./plugin1";
    import installPlugin2 from "./plugin2";
    import installPlugin3 from "./plugin3";



    Хватит топить JavaScript. У Вас половина статей про то, какой JS плохой и половина про том, какой JS хороший. Найдите другую тему (CSS Paint — хороший пример).


    1. Ivanq
      17.07.2018 13:49

      Кстати, у хабра глюк. Когда пишешь сообщение в markdown, а затем меняешь, чекбокс markdown сбрасывается, и сообщение сохраняется, как будто написано на HTML.


    1. faiwer
      17.07.2018 15:49

      Так понятнее? Спагетти-код можно написать на любом языке

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


      Сильно понятнее? Сразу поймете, почему slice(1)? Ну… нет.

      Честно? Да, раз в 5 понятнее. Просто и по делу, без ребусов. К тому же:


      function handlePost(post) {
          const [firstAuthor, ...moreAuthors] = post.authors;
          const { name, email } = firstAuthor;
      }

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


      А целом согласен с вами по некоторым пунктам (вроде const), да и автор несколько перегнул палку.


      1. dagen
        18.07.2018 11:58

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


        Просто такие вопросы обычно решают заранее и выносят в код стайл, командный или корпоративный.


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


        1. Ivanq
          18.07.2018 12:11

          На самом деле, все проблемы из-за default — когда используешь require(), нужно постоянно добавлять .default. Случаев, когда нужно использовать одновременно и export default, и named export — ужасно мало. Проще было бы сделать export default = module.exports и import = require.


          1. VolCh
            18.07.2018 20:20

            Ну частый случай, когда нужен и дефолт и именнованный экспорт — React+Redux приложение. Дефолтно экспортируем компонент, обёрнутый в connect(), а именнованно голым, для тестов чаще всего, но бывают и другие варианты.


        1. faiwer
          18.07.2018 12:14

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


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


    1. Bhudh
      18.07.2018 00:20
      +2

      Лично мне в деструктуризации глубоко непонятен один момент: зачем вынесли имя переменной направо?
      Вот в Вашем коде:

      const [
              {name: authorName, email: authorEmail},
              ...moreAuthors
          ] = post.authors;
      

      есть строчка
              {name: authorName, email: authorEmail}
      Присваиваемое: слева, имя переменной справа.

      Это идёт вразрез с привычным синтаксисом объявления объектного литерала:
              {name: authorName, email: authorEmail}
         где присваиваемое: справа (очевидно, это имя переменной, чьё значение присваивается), а идентификатор, по которому впоследствии будут обращаться к этому значению: слева.

      Расскажите, к чему было так корёжить привычный синтаксис и сразу ли Вы привыкли, что в одном случае код надо парсить слева направо, а в другом справа налево?


      1. mayorovp
        18.07.2018 06:32
        -2

        Вот затем и вынесли. Для симметричности. Смотрите:


        const {name: authorName, email: authorEmail} = {name: authorName2, email: authorEmail2};


        1. Bhudh
          18.07.2018 19:04

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


          1. mayorovp
            18.07.2018 19:36

            Да, приводит. Но любой другой вариант будет не менее странным.

            Выход я вижу только один — не использовать такие сложные конструкции.


      1. Ivanq
        18.07.2018 08:48
        +1

        Ох, это, пожалуй, мне и не нравится в деструктизации. Постоянно забываю, в каком порядке идут переменные: src: dst или dst: src. Странно сделали — но, видимо, чтобы легче было парсить. Сравните:


        const {
            authors: [
                {
                    name: {
                        full: fullName
                    }
                }
            ]
        } = post;

        (да, это скорее пример непонятного кода, но все же)


        И:


        const {
            [
                {
                    {
                        fullName: full
                    }: name
                }
            ]: authors
        } = post;

        Чтобы понять, что речь идет об авторах, нужно дочитать до конца, или читать снизу вверх, что еще хуже.


        Сразу ли привык? Нет, до сих пор не привык полностью. Мне деструктизация больше нравится, когда имя переменной и свойства совпадают, например:


        const {
            authors: [
                {fullName},
                ...moreAuthors
            ]
        } = post;

        Такой код смотрится элегантно.


  1. Fragster
    17.07.2018 15:30
    -5

    Тернарный оператор прекрасен, если его оформлять нормально:

    let i = 2;
    let val = 
         i === 1 ? 'i is 1' : 
         i === 2 ? 'i is 2' : 
         i === 3 ? 'i is 3' : 
         i === 4 ? 'i is 4' : 
         '???';


    1. faiwer
      17.07.2018 15:53
      +2

      В последнее время народ очень активно топит за prettier. Я не разделяю этих взглядов, но не удержался от того, чтобы посмотреть во что он превратит ваш code-style :)


      let i = 2;
      let val =
        i === 1
          ? "i is 1"
          : i === 2
            ? "i is 2"
            : i === 3
              ? "i is 3"
              : i === 4
                ? "i is 4"
                : "???";


      1. Fragster
        17.07.2018 16:49

        Если прям очень хочется пользоваться автоформаттерами, которые портят такие выражения, то даже в таких случаях есть решение: prettier.io/docs/en/ignore.html. Ну, или более правильное — написать свой плугин для подобного форматирования тернарного оператора (как самому нравится/принято в команде).


    1. pantus
      17.07.2018 17:46
      +2

      Само наличие тернарного оператора делает скрипт более приближенным к стандартам высокоуровневых языков. Лично мне он нравится, но злоупотребоять им не советую особенно в случае большой вложенности, как в Вашем примере. Думаю всегда можно избавиться от многоэтажных условий используя другое построение алгоритма или же самих данных в сочетании с оператором switch() { case()}.


  1. pantus
    17.07.2018 16:25
    -2

    Именованный экспорт даёт возможность единообразно экспортировать из модулей всё, что угодно, в нужных количествах. Дефолтный экспорт ограничивает разработчика лишь экспортом одного значения. В качестве обходного пути тут можно применить экспорт объекта с несколькими свойствами. Однако при таком подходе теряется ценность алгоритма tree-shaking, применяемого для уменьшения размеров JS-приложений, собираемых чем-то вроде webpack. Использование исключительно модулей с именованным экспортом упрощает работу.

    Субъективное мнение. Не согласен. Дефолтный экспорт дает разработчику дополнительную гибкость, например:
    import {
      Config900Api,
      Other900Api,
      System900Api,
    } from 'server-api';
    
    const MyServices = {
      ConfigApi: Config900Api,
      OtherApi: Other900Api,
      SystemApi: System900Api,
    };
    export { MyServices, MagicIds };
    

    Использование:
    import { MyServices } from 'Api/Services';
    
    function getAccessKeys(userId: number, taskUri?: string): Promise<SomeResponse> {
    const params: SomeParams = { ... };
      return MyServices.ConfigApi.getUser(params, new Configuration(), { credentials:  { .... } })();
    }
    

    В таком случае потдержка версионности внешнего Api сводиться к изменениям в одном файле. А теперь представьте объем рефакторинга при изменении версии Api если бы вы использовали
    Именованный экспорт


  1. molnij
    17.07.2018 20:24

    Извините, но статья скорее должна была называться «JavaScript ES6: слабые стороны [автора]»
    У ES6 безусловно хватает слабых сторон (начать с того, что до сих пор приходится пользоваться костылями типа babel чтобы на нём писать), и ни одной из них в статье не приведено. А «проблема» const — это вообще песня…


  1. taujavarob
    17.07.2018 21:25

    Автор:

    В результате, из-за того, что использование const может привести к путанице, и из-за того, что при наличии ключевого слова let наличие const выглядит избыточным, я решил всегда использовать let.


    Так оно и будет.

    Вангую:
    Если верен принцип:
    Всё что можно не писать в коде (но код при этом будет работать) — будет опущено при написании кода. ©
    — пример, указание типов в JavaScript — то, если можно в функциях не думать о том что использовать? (conts или let) и код будет работать(!) — то большинство будет использовать только let.


  1. pmcode
    18.07.2018 06:58
    +2

    Даже когда в языке еще не было const, а мне нужна была именно константа, я всегда вводил ее в верхнем регистре, именно для того, чтобы при попытке сделать так MY_CONSTANT.property = 123, или переписать ссылку, срабатывал внутренний стопор. С введением const код просто стал немного логичнее и безопаснее. Непонятно почему вообще кто-то считает, что const делает объекты иимутабельными.


    Что мне действительно не нравится в ES6, так это то, что я не могу просто преобразовать полученный от сервера объект в инстанс класса. Вот чтобы прямо со всей вложенностью. Спрашивается, зачем мне классы тогда вообще нужны, по крайней мере на клиенте?


    1. VolCh
      18.07.2018 07:28

      Как минимум потому что json (вы же про него?) не содержит имён классов. А даже если бы содержал, то как быть с идентичностью объектов? Если второй раз встречается объект с тем же набором полей, то новый надо создавать или только ссылку на существующий добавлять? А если в json встречается один раз, но был создан ручками до того? А если был создан до того ручками с тем же ид, но другим значением, как конфликты разруливать?


      1. pmcode
        18.07.2018 08:12

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


        1. gnaeus
          18.07.2018 09:40
          +1

          Причем, в простых случаях она и вовсе не нужна.

          Наверное, это потому что Java — язык со статической типизацией. И сериализатор заранее знает типы всех полей класса. А в JS для этого не обойтись без внешних аннотаций или Typescrpit + Reflect Metadata.


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


        1. VolCh
          18.07.2018 11:09

          Сторонние библиотеки есть и были задолго до es5 даже.


  1. AxisPod
    18.07.2018 11:26
    +1

    Опять «проблемы» очередного не очень умного индуса? Или вы с ним согласны, что переводите и публикуете подобные статьи?

    А потом на собеседованиях куча «гениальных» разрабочиков несёт ахинею.


    1. alex6636
      19.07.2018 18:25

      Вы сильно расстроились тли обиделись? У этого недоязыка и сильных то нет