Когда использовать var, let и const в Javascript [перевод статьи Tyler’а McGinnis]

Привет, Хабр! Представляю вашему вниманию перевод статьи «var vs let vs const in JavaScript» автора Tyler McGinnis.

image

В этой статье вы узнаете 2 новых способа для создания переменных в Javascript (ES6), let и const. На протяжении этой статьи мы рассмотрим разницу между var, let и const, а также смежные темы такие как: “область видимости функции против блочной области видимости“, “поднятие” переменных и иммутабельность.

Если вы предпочитаете видео, посмотрите это (оригинал на английском):


ES2015 (или ES6) представил нам 2 новых способа для создания переменных, let и const. Но прежде чем мы углубимся в различия между var, let и const, имеются некоторые темы, которые вам следует узнать в первую очередь. Это объявление переменных и их инициализация, область видимости (особая область видимости функции) и “поднятие”.

Объявление и инициализация переменных


Объявление переменной вводит новый идентификатор.

var declaration

Выше мы создаем новый идентификатор который мы назвали “declaration”. В Javascript, при создании, переменные инициализируются со значением undefined. Это означает, что если мы попробуем вывести нашу переменную declaration, мы получим undefined.

var declaration

console.log(declaration)

И так, мы вывели переменную declaration и мы получили undefined.

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

var declaration
 
console.log(declaration) // undefined

declaration = 'Это инициализация переменной'

И так, здесь мы инициализировали переменную declaration записывая в неё строку.

Это приводит нас к следующему понятию, область видимости.

Область видимости


Область видимости характеризует то, где переменный и функции могут быть доступны внутри нашей программы. В Javascript, имеются 2 типа областей видимости — глобальная область видимости, и область видимости функции. Согласно официальной спецификации,
“Если объявление переменной происходит внутри объявления функции, переменная определяется в локальной области видимости этой функции…”
Это означает, что если вы создадите переменную при помощи var, областью видимости этой переменной будет функция в которой она была создана и будет доступна только внутри этой функции или любой другой вложенной функции.

function getDate () {
  var date = new Date()

  return date
}

getDate()
console.log(date) // NOT OK: Reference Error

Выше мы попытались получить доступ к переменной снаружи функции, в которой она была объявлена. Так как областью видимости переменной date является функция getDate, она доступна только внутри этой функции или в любой другой функции вложенной в getDate(как показано ниже).

function getDate () {
  var date = new Date()

  function formatDate () {
    return date.toDateString().slice(4) // OK
  }

  return formatDate()
}

getDate()
console.log(date) // NOT OK: Reference Error

Теперь давайте взглянем на более продвинутый пример. Скажем, у нас есть массив prices и нам требуется функция, которая принимает этот массив, а так же переменную discount, и возвращает нам новый массив цен со скидками. Конечная цель может выглядеть примерно так:

discountPrices([100, 200, 300], .5)

И реализация может выглядеть примерно так:

function discountPrices (prices, discount) {
  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  return discounted
}

Выглядит достаточно просто, но какое это отношение имеет к области видимости блока? Взгляните на данный цикл for. Доступны ли переменные объявленные внутри него за его пределами? Оказывается доступны.

function discountPrices (prices, discount) {
  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

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

Оно не сломано, просто работает немного странно. На самом деле нет никаких причин иметь доступ к i, discountPrice и finalPrice за пределами цикла for. Это не приносит нам никакой пользы и может даже принести нам вред в некоторых ситуациях. Однако, так как переменные объявлены при помощи var, они входят в область видимости функции и вы можете получить к ним доступ.

Теперь мы обсудили объявление и инициализацию переменных, а так же области видимости, ещё одна вещь с которой нам нужно разобраться, прежде чем мы погрузимся в разбор различий между let и const, это “поднятие”.

“Поднятие”


Помните, ранее было сказано “В Javascript, при создании, переменные инициализируются со значением undefined “. Оказывается это и означает “поднятие”. Интерпретатор JavaScript назначает объявленным переменным значение undefined во время фазы называемой “Создание”.

Для более подробного изучения фазы Создания, “Поднятия” и областей видимости прочтите данную статью: “The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript”.

Давайте взглянем на предыдущий пример и увидим как “поднятие” влияет на него.

function discountPrices (prices, discount) {
  var discounted = undefined
  var i = undefined
  var discountedPrice = undefined
  var finalPrice = undefined

  discounted = []

  for (var i = 0; i < prices.length; i++) {
    discountedPrice = prices[i] * (1 - discount)
    finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

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

function discountPrices (prices, discount) {
  console.log(discounted) // undefined

  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

Теперь вы знаете все что нужно о var, теперь давайте наконец-то поговорим о главной цели, из-за которой мы здесь: в чем разница между var, let и const?

var, let или const


Для начала, давайте сравним var и let. Ключевое отличие между var и let это то, что let помимо глобальной области видимости и области видимости функции позволяет определять переменные в области видимости блока. Это означает, что переменная созданная при помощи ключевого слова let доступна внутри “блока”, где она была создана, также и внутри вложенных блоков. Когда я сказал “блок”, я имел в виду что-либо окруженное фигурными скобками {}, например цикл for или оператор if.

И так, давайте вернемся к нашей функции discountPrices в последний раз.

function discountPrices (prices, discount) {
  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

Вспомните, что мы вправе вывести i, discountPrice, и finalPrice за пределами цикла for, так как они были объявлены при помощи var, а переменные объявленные при помощи ключевого слова var ограничены областью видимости функции. Но что же произойдет теперь, если мы изменим var на let и попробуем запустить наш код?

function discountPrices (prices, discount) {
  let discounted = []

  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

discountPrices([100, 200, 300], .5) // NOT OK: ReferenceError: i is not defined

Мы получили ReferenceError: i is not defined. Что говорит нам о том, что переменная, объявленная при помощи let, ограничена областью видимости блока, а не функции. Попытайтесь обратиться к i (или discountedPrice или finalPrice) за пределами “блока”, где они были объявлены, и это выдаст нам ошибку обращения, как мы только что увидели.

var VS let

var: ограничена областью видимости функции

let: ограничена областью видимости блока

Следующие различие связано с “поднятием”. Ранее мы сказали, что определение “поднятия” это: “Интерпретатор JavaScript назначает объявленным переменным значение undefined во время фазы называемой “Создание”". Мы так же увидели это в действии при помощи вызова переменной до её объявления (вы получили undefined).

function discountPrices (prices, discount) {
  console.log(discounted) // undefined

  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

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

По факту, это и есть то что делает let. Если вы попытаетесь доступ к переменной до её объявление при помощи let, вместо получения undefined (как это было при объявлении при помощи var), вы получите ReferenceError.

function discountPrices (prices, discount) {
  console.log(discounted) // NOT OK: ReferenceError

  let discounted = []

  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

var VS let

var:
  ограничена областью видимости функции
  её значение будет undefined если вы попытаетесь обратиться к ней до её объявления.

let:
  ограничена областью видимости блока
  вы получите ReferenceError если попытаетесь обратиться к ней до её объявления.

let или const


Теперь вы понимаете разницу между var и let, что же о const? Оказывается, const почти такая же как и let. Однако есть одно отличие: если вы однажды присвоили значение используя const, вы не сможете его изменить на другое.

let name = 'Tyler'
const handle = 'tylermcginnis'

name = 'Tyler McGinnis' // OK
handle = '@tylermcginnis' // NOT OK: TypeError: Assignment to constant variable.

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

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

const person = {
  name: 'Kim Kardashian'
}

person.name = 'Kim Kardashian West' // OK

person = {} // NOT OK: Assignment to constant variable.

Заметьте, что изменения свойства объекта не является его перезаписью, так что даже если объект объявлен при помощи const, это не означает, что вы не можете изменить какие-либо из его свойств. Это только значит, что вы не можете перезаписать этот объект.

Теперь, наиболее важный вопрос, на который ещё не было ответа: что следует использовать var, let или const? Самое популярное мнение, и мнение которого придерживаюсь я, это использовать всегда const, пока вы не знаете будет ли переменная изменяться. Причина этого в том, что используя const вы даете понять себе и будущим разработчикам, которые должны прочитать ваш код, что эта переменная не должна изменяться. Если её потребуется изменить (например в цикле for), просто используйте let.

Между переменными, которые меняются и переменным которые не меняются, не так уж и много случаев осталось. Это значит, что вам больше никогда не придется использовать var.

Теперь непопулярное мнение, хотя оно все ещё имеет обоснование, это то что вы никогда не должны использовать const, несмотря на то что вы пытаетесь показать, что эта переменная неизменна, как мы видели выше, это не совсем так. Разработчики, которые придерживаются этого мнения всегда используют let пока нет переменных, которые на самом деле являются константами, такими как _LOCATION_ =….

Составим резюме выше сказанного, var ограничена областью видимости функции и если вы попытаетесь обратиться к такой переменной до её объявления вы получите undefined. const и let ограничены областью видимости блока и если вы попытаетесь обратиться к этим переменным до их объявления вы получите ReferenceError. И отличие между const и let это то, что значение, которое было присвоено const не может быть перезаписано, в отличие от let.

var VS let VS const

var:
  ограничена областью видимости функции
  её значение будет undefined если вы попытаетесь обратиться к ней до её объявления.

let:
  ограничена областью видимости блока
  вы получите ReferenceError если попытаетесь обратиться к ней до её объявления.

const:
  ограничена областью видимости блока
  вы получите ReferenceError если попытаетесь обратиться к ней до её объявления.
 не может быть перезаписана

Первоначально данная статья была опубликована на tylermcginnis.com как часть курса Modern JavaScript

Спасибо за прочтение данного перевода, надеюсь вы познакомились для себя с чем-то новым и полезным. Буду рад увидеть обратную связь!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 76

    –6
    Да ерунда же! Во всех зрелых и серьезных языках, которые лежат воснове остальных языков, с константами ассоциируются только те значения, которые реально являются константами, как например PI или строки
    представляющие название событий. Зачем придумывать все заного для js и ломать устоявшиеся каноны? Иммутабельность, это совсем не константы, это стиль\архитектура.
      +1

      Дело в том, что const — это не константа, а именно read-only переменная. Как правило, константам можно присваивать только скалярные значения, переменной же const в JS можно присвоить любой объект. Кроме того, const можно использовать и в циклах, если это перечисление. Например следующий код будет прекрасно работать:


      const fruits = ["apple", "banana", "orange"];
      
      for (const fruit of fruits) {
          console.log(fruit);
          // fruit = "dog" вызовет: TypeError: Assignment to constant variable.
      }

      Еще одна причина почему лучше везде где возможно использовать const, это интроспекция в IDE. Например, в TypeScript следующая проверка будет работать:


      const eventHandler: Function | null = getHandler();
      
      if (eventHandler === null) {
          return;
      }
      // с этого места eventHandler имеет тип Function
      
      // ... много кода
      
      // можно безопасно вызывать
      // eventHandler - точно не null
      eventHandler();

      А если же заменить const eventHandler на let eventHandler, то код не скомпилируется, поскольку между первичной проверкой на null и вызовом eventHandler() мог присвоиться null уже где-то в другом месте.


      Кстати, похожее декларирование переменных есть в Kotlin, только там используются ключевые слова var, val.
      https://kotlinlang.org/docs/reference/basic-syntax.html#defining-variables

        –2
        Дело не в том что можно присвоить, а что нет, дело в идеологии. Например, с точки зрения языка, имена для идентификаторов переменных можно задавать абсолютно любые, хоть в т_а_к_о_м кейсе. Но вы точно знаете, что наткнувшись на ПОДОБНЫЙ_КЕЙС имеете дело с константой. С той константой, что по логики приложения представляет неизменяемое значение. Замечу ещё раз что неизменяемая не по ходу выполнения программы, а по логике! Что значит по логике? Как уже сказал ранее, PI или значение типа для события, или же функция, возможно объект, который представляет из себя дефолтное значение какого-то конфига. Вот что такое константа с точки зрения программирования. Если помечать её все неизменяемые по ходу выполнения программы значения, то как тогда определить то, что явыше описал?

        И Ваш пример с функцией просто идеально иллюстрирует то о чем я говорю. В то время как первый пример иллюстрирует иммутабельность.
          0

          Если вы про то, что ключевое слово const для определения read-only переменных было выбрано неудачно и вносит путаницу, то тут я полностью согласен.

            +5
            А я — не согласен. Это просто ключевое слово, которое имеет свою семантику. Намного лучше val+var в некоторых языках, которые визуально очень похожи
        +2
        Иммутабельность
        Да, про иммутабельность там ни к селу, ни к городу. Видимо, автор хотел, чтобы из поисковика к нему приходили и на это слово.
          0
          А как ещё обозначить неизменяемость значения по ходу выполнения программы? Если readonly, то это непосредственно о конструкциях, а не о логике. К логике ближе понятие иммутабельности, хотя оно из области архитектуры стиля.
            0
            Во всей статье это слово встречается только раз, во вступлении и имеет мало отношения к тому, о чем речь пойдет дальше:
            На протяжении этой статьи мы рассмотрим разницу между var, let и const, а также смежные темы такие как: “область видимости функции против блочной области видимости“, “поднятие” переменных и иммутабельность.
          0
          Например, в C++?
          +6
          Абсолютно ненужная статья — материал по объявлению и инициализации переменных в современном JS за одну минуту гуглится любым новичком, который только начал изучать JS и сие гугление приносит over9000 ссылок и даже, прости Господи, видеоуроков по теме.
            +7
            В этой статье вы узнаете 2 новых способа для создания переменных в Javascript (ES6), let и const.

            Прям новых? Мы в 2016?

              +1
              Перевод какой-то деревянный… Про грамматику молчу тоже)

              И так и не распарсил это… Что имелось ввиду?
              > Следующие различие связано с “поднятием”. Ранее мы сказали, что определение “поднятия” это: “Интерпретатор JavaScript назначает объявленным переменным значение undefined во время фазы называемой “Создание”".
                +7

                Воды больше чем в моем дипломе.


                Предложу свой вариант: "всегда используйте const, кроме случаев когда не можете, тогда используйте let"

                  –3
                  всегда используйте const, кроме случаев когда не можете, тогда используйте let

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


                  Цитата из этой статьи:


                  Действительно ли вы хотели сообщить, что ответ никогда не должен меняться, или просто так совпало, что вы нигде его не меняли?

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

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

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


                  const a = 5; // точняк константа

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


                  Моё мнение — const по умолчанию хорош только при чисто функциональном стиле программирования. В остальных случаях лучше воспользоваться правилами из той же статьи:


                  1.Используйте const только в самой верхней области видимости
                  За всю жизнь не припомню случая, чтобы привязка, созданная внутри функции, расстроилась от своего изменения.

                  2.Старайтесь не использовать let в самой верхней области видимости
                  С другой стороны, если вам понадобился let в самой верхней области видимости, это знак, что у вас что-то типа глобального синглтон-состояния, от которого могут быть проблемы.
                    0

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


                    Кстати, вот вам привязка которая "расстраивается" от своего изменения:


                    const $element = $(...);
                    $element.on("foo", e => {
                        $element.bar();
                    });
                      0
                      Ага, если бездумно менять код, то он сломается, const от этого не спасёт, ведь при повсеместном его использовании вы как обычно просто замените const на let и он точно так же сломается. А было бы использование const осмысленным, может это и помогло бы.

                        +2
                        Если вы заменяете что-то в коде бездумно — не следует считать что все вокруг так же делают. const на let можно менять только после анализа всех мест использования.
                          –2
                          const на let можно менять только после анализа всех мест использования.

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


                          не следует считать что все вокруг так же делают.

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


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

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

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

                              Так там как раз пример правильного использования const. Там константа и кто-то правильно запретил её менять. Вы привели пример к этому тексту:


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

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

                              0
                              при обдуманном изменении let-переменной действительно ничего не сломается.
                              А типичная ошибка «пропустить один знак = в сравнении» — это обдуманное изменение? Мне кажется, использование const — лучше, чем постоянно писать Йода-сравнения.
                                0

                                У меня линтер заставляет обёртывать такое в дополнительные скобки:


                                if ((a = b)) {

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

                                  0
                                  А в тернарке?
                                  (a = b) ? 1 : 2;
                                  vs
                                  a == b ? 1 : 2;
                                    0

                                    Разве это не какое-то известное правило? Раньше, когда использовал javascript, у меня eslint подсвечивал такое заставляя добавлять скобки, причём я специально не настраивал это, то есть у многих должно работать как-то так же. Сейчас typescript как выяснилось не ругается, но при форматировании сам добавляет скобки. В данном случае добавил так:


                                    let a = 5;
                                    let b = 10;
                                    
                                    console.log((a = b ? 1 : 2));
                                      0
                                      Уберите консоль.лог и поймете, что это вообще не универсальное решение.

                                      let a = 5;
                                      let b = 10;
                                      
                                      var x = (a = b ? 1 : 2);


                                      Или всё-таки так?
                                      let a = 5;
                                      let b = 10;
                                      
                                      var x = (a == b ? 1 : 2);
                                        0
                                        Во втором случае typescript (или vscode) убрал лишние скобки.
                                          0
                                          А так?

                                          let a = 5;
                                          let b = 10;
                                          let c = 10;
                                          
                                          var x = a = b ? 1 : (a == c ? 2 : 3);
                                            0

                                            Сделал так:


                                            let a = 5;
                                            let b = 10;
                                            let c = 10;
                                            
                                            let x = (a = b ? 1 : a == c ? 2 : 3);
                        –3
                        Как мило, адепты повсеместного const минусят также бездумно как и ставят const)). Уверен, большинство даже не пыталось осмыслить написанное.
                          +1
                          Я не минусил, но прочитал и попытался осмыслить.

                          В целом вопрос написания const по дефолту мне напоминает холивар о написании классов final по дефолту. Суть идеи состоит в том, что класс для того чтобы его можно было эффективно наследовать должен быть заранее подготовлен к этому на этапе дизайна. Человек который пишет класс должен подумать о том как его будут наследовать и расширять. Если он об этом не думал (не было такой задачи) надо ставить final и такой класс не наследовать. Если понадобится то вернуться, передизайнить и убрать final.

                          Также и с const. Чтобы переменную можно было в любой момент брать и менять нужно заранее об этом подумать, чтобы нельзя было перевести приложение в некорректное состояние. Если программист об этом не подумал, следует объявить переменную const. Если кому-то понадобится её менять, пусть придет, подумает и поставит let.
                            –1

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

                              0
                              Я не понимаю о чем мы спорим. В вашем комментарии вы говорите что у вас замыкания чистые и не портят внешние переменные. Но ведь это отлично, я тут вас готов только поддержать. В такой ситуации нет необходимость объявлять переменные let если вы всеравно их не меняете, объявляйте const.
                                –2

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

                                  0
                                  Если вы не планируете её менять, зачем писать код с учетом этого? В 90% (оценочное суждение из головы) случаев никто ваш код трогать не будет никогда. В 9.5% случаев его удалят целиком не читая, и только в 0.5% случаев ваша работа проделанная для «подготовки переменной к последующему использованию» окажется полезна. А значит в 99.5% случаев вы делали лишнюю работу.

                                  Мой опыт говорит о том, что код должен хорошо делать то, что от него требуется и предусматривать расширение в тех направлениях которые уже запланированы или хотя бы точно известны. Проектировать в рассечете на то что может понадобиться тут или там как правила не требуется, т.к. результат оказывается переусложенным и избыточным, и время потрачено лишнее.
                                    –1
                                    Если вы не планируете её менять, зачем писать код с учетом этого?

                                    потому что я не планирую запрещать её дальнейшее использование, оно ничего не ломает. Мне кажется я повторил это уже раз 20. Вы точно попытались осмыслить?


                                    В 90% (оценочное суждение из головы) случаев никто ваш код трогать не будет никогда. В 9.5% случаев его удалят целиком не читая

                                    у меня другой опыт: пишется один раз, дорабатывается 10 раз, читается 100 раз. Вы реально постоянно всё с нуля переписываете?

                                      +3
                                      1. Кажется именно в этом месте происходит несовпадение взглядов. Мой поинт в том что если вы не предполагаете что вы или кто-то будет изменять эту переменную, то надо это запретить. Если вы разрешили то надо уделить этому внимание и подумать об этом. Но в большинстве случаев это не нужно и проще подумать об этом в тот момент когда необходимость возникнет. В этот момент у вас будет больше данных о том, что нужно сделать.

                                      2. Да, честно говоря достаточно редко приходится точечно исправлять код (если это не багофикс). Такое что приходится прийти в какой-то метод и дописать пару строк не часто бывает. Чаще всего заменять какими-то блоками\компонентами, ну или как минимум функциями. При этом зачастую приходится провести какой-то нанорефакторинг в рамках которого исправить let на const или наоборот не составляет труда.

                                      3. Чтение const упрощает значительно. Читая метод можно просто запомнить что в такой-то переменной лежит то-то и не думать об этом. Если переменная let то приходится держать в голове что там что-то может измениться.

                                      Я в целом прекрасно вас понимаю, и некоторое время назад считал также как вы, но попробовал и проникся. Дело в том что вы относитесь к const как к чему-то особенному. Т.е. «по умолчанию переменные надо создавать как let, а const надо помечать только константы которые точно не должны меняться, выделять их таким образом» или что-то вроде того. А вы попробуйте применить этот подход на практике и увидите что почти все переменные в вашей программе можно объявить как const. И в этот момент произойдет сдвиг в сознании и осознание того факта, что на самом деле const это модификатор по умолчанию, и только редкие, единичные переменные должны быть помечены как let. И именно они должны привлекать внимание и вызывать настороженность, а не наоборот.

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

                                        если что, то я уже больше 10 лет повседневно работаю с javascript/typescript и перепробовал очень многие вещи. Повсеместный const — одна из первых фич, которые я попробовал при появлении babel и навязал её использование не в одном проекте. Так что я то как раз полноценно с обоими вариантами поработал).
                                        Такие вещи действительно нужно именно пробовать и порой достаточно длительное время, приведу хороший пример: первый рабочий день в новой компании, открываю код и, о ужас:


                                        x == 1

                                        Какое равенство нужно использовать в сравнениях, двойное или тройное? Уверен 99% смело ответят, что тройное, а оставшийся процент — неопределившиеся новички. Я тоже был исключительно за тройное. Спрашиваю — "Ребят, что за г в этом файле?", — "А у нас везде двойное равенство", — "Вроде компания серьёзная, а вы тут дикие какие-то. Вам Интернет отключили? Весь мир исключительно тройное использует!". Мне объяснили как нужно использовать двойное равенство не получая с этого стандартных минусов и в чём с этого будет плюс. И вроде понятно всё объяснили, там и объяснять то нечего, но я подумал: "ересь какая-то", говорю: — "Ну пофиг, буду делать как у вас тут принято, мне же не жениться на местном коде, но я остался при своём мнении", — "Окей, попробуй, посмотрим, что ты скажешь через пол года". Больше это не обсуждалось, но менее чем через пол года при рефакторинге личного кода я добровольно заменял тройное равенство на двойное. Тоже дикий совсем стал). До сих пор с удовольствием пользуюсь этой фичей в личных проектах, но ни разу даже не пытался где либо её пропихнуть, просто знаю, за такие мысли сразу камнями закидают, никто даже на секунду не задумается про попробовать, просто категоричное нет от всей команды. А там как раз была целая команда с удовольствием использующая эту фичу и как мне кажется большинство приходило в неё с теми же мыслями, что и я.


                                        Та же ситуация с повсеместным const, все его используют, а тут какой-то вася какую-то ересь порет, и ладно бы объяснили и успокоился, но нет же, не унимается. Уже вон и в карму наплевали, действительно, да что вообще этот вася возомнил о себе!)). Мне вот как-то и в голову не приходило плевать в карму за отличающееся от моего мнение которое человек пытается спокойно обосновывать. Ну да пофиг, мне ж за карму на хабре не платят, хоть в минус загоняйте. Интересно другое: если завтра какой-нибудь фейсбук/гугл начнёт предлагать подобную ересь и использовать её у себя, то ситуация будет кардинально отличаться — нехотя, но начнут пробовать, а там глядишь и через пару лет станет общественным стандартом. Легчайше! Всего несколько лет назад кто бы мог подумать, что писать js, html и css в одном файле будет считаться нормой. Вот я бы такое предложил? Да сожгли бы!). Вот и получается победа общественного мнения и хайпа на собственным мозгом каждого.
                                        Меня же та ситуация с двойным равенством научила спокойней относится ко всяким странным идеям, в каждом новом проекте я стараюсь попробовать что-нибудь новое, пусть и странное, хотя чаще это относится к каким-то архитектурным решениям, а не к такой мелочи, что мы здесь обсуждаем. Просто хотел показать альтернативный вариант, но, к сожалению, у многих это вызывает лишь желание меня заткнуть.

                          +4
                          а потом сидите и гадайте

                          Не нужно конструкциям придумывать альтернативный смысл, тогда и гадать не придется.
                            –3

                            Есть там альтернативный смысл или нет, но кода может быть просто много:


                            const $element = $(...);
                            
                            // много кода
                            
                            $element.on("foo", e => {
                                $element.bar();
                            });
                            
                            // много кода
                            
                            // а здесь вам стала интересна константа $element

                            имя для новой переменной придумывать не хочется, может эта сгодится? Как вы это поймёте? Ведь const при его повсеместном использовании не значит ровно ничего.


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

                              +1
                              но кода может быть просто много

                              Если кода много, то нужен рефакторинг.

                              Кстати, насчет вашего примера: он кажется валидным даже для логической константности: внешний код подписывается на события, а сам элемент остается неизменным.

                              Ведь const при его повсеместном использовании не значит ровно ничего.

                              Он значит неизменность ссылки. Это может быть полезно и, как минимум, не хуже let.

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

                              Вам придется оценивать ее «обдуманность», что сводит на нет пользу этих предположений.
                                –1
                                Он значит неизменность ссылки. Это может быть полезно и, как минимум, не хуже let.

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


                                Вам придется оценивать ее «обдуманность», что сводит на нет пользу этих предположений.

                                помечено константой — не трогай, создай новую привязку, польза остаётся. Если же нужно как-то поработать именно с этой константой, то да, нужно будет оценить остался ли запрет на изменение.

                                  0
                                  Если ссылка никогда не меняется — её безопасно использовать в любых замыканиях.

                                  А замыкания в JS используются часто.
                                    0
                                    Верно, и всё же пользы от этого на мой взгляд явно меньше.
                                      0
                                      А замыкания в JS используются часто.

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


                                      class SomeView extends View {
                                        onBtnClick() {
                                          let btn = $('btn');
                                          btn.disable();
                                          request.get(...).then(res => {
                                            btn.enable();
                                            // ...
                                          });
                                        }
                                      }

                                      вроде замыкание, но такого в коде по-хорошему вообще не должно быть. Вот как мне теперь переопределить только обработку ответа при наследовании? Соответственно переписываем так:


                                      class SomeView extends View {
                                        initialize() {
                                          this.btn = $('btn');
                                        }
                                        onBtnClick() {
                                          this.btn.disable();
                                          request.get(...).then(this.onResponse);
                                        }
                                        @autobind
                                        onResponse(res) {
                                          this.btn.enable();
                                          // ...
                                        }
                                      }

                                      Опа-на и нет замыкания)). Причём избавлялись от него не ради избавления от замыкания, а по совершенно другой причине и такая причина в ООП всегда находится.
                                      Плюс есть async/await, то есть даже если точно не нужно выносить обработку в отдельный метод, то так:


                                      class SomeView extends View {
                                        async onBtnClick() {
                                          let btn = $('btn');
                                          btn.disable();
                                          let res = await request.get(...);
                                          btn.enable();
                                          // ...
                                        }
                                      }

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


                                      const gulp = require('gulp');
                                      
                                      gulp.task('some-task', () => {
                                        return gulp. // ...

                                      но это опять же к нашей ситуации не относится.
                                      В результате


                                      Если ссылка никогда не меняется — её безопасно использовать в любых замыканиях.

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


                                      Моё мнение — const по умолчанию хорош только при чисто функциональном стиле программирования. В остальных случаях лучше воспользоваться правилами из той же статьи:

                                      В любом случае это пока единственный хоть сколько-то весомый аргумент за повсеместный const из тех что пока прозвучали.

                                        0
                                        Моё мнение — const по умолчанию хорош только при чисто функциональном стиле программирования. В остальных случаях лучше воспользоваться правилами из той же статьи:

                                        В любом случае это пока единственный хоть сколько-то весомый аргумент за повсеместный const из тех что пока прозвучали.

                                        Сомнительно. Я вот больше в сторону ООП тяготею. Я там тоже редко нужно использовать не-конст. А уж анонимные функции — от и подавно. К примеру, на реакте я бы написал как-то так:

                                        class MyComponent extends React.Component {
                                          render () {
                                            return <button onClick={this.activate} />;
                                          }
                                          activate = (e) => {
                                            // do
                                          }
                                        }


                                        То есть опять же — не через замыкание.
                                          0
                                          Ну как бы и я про тоже, что замыканий с не константами в ООП получается совсем мало. Или я неправильно вас понял?
                                            0
                                            Замыканий вообще мало, методы очень маленькие и потому нету никакого смысла не объявлять переменные не через const.

                                            В тех редких случаях, когда в этом есть необходимость — лучше явно указать, что вот эта вот переменная — изменяется.
                                              0
                                              Вооот), замыканий исчезающе мало либо вообще нет, а значит что? Значит если взять любую переменную в любом методе, то перейдя в место после её последнего использования можно делать с ней всё что угодно, хоть слона туда засовывай, ничего гарантированно не сломается. Правильно? То есть на первый взгляд вообще пофигу let или const. Но не совсем, const при дальнейшей доработке кода будет постоянно заставлять заменять его на let бесполезно тратя наше время. Пользы же он будет давать ровно ноль. В результате приходим к варианту повсеместного использования только let, кроме самого верхнего уровня. То есть const где-то ещё совсем не должен встречаться, а если встречается, то скорей всего с кодом что-то не так.
                                                +2
                                                Господи, в каждом предложении вашего сообщения написана глупость!

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

                                                То есть на первый взгляд вообще пофигу let или const
                                                Нет, с точки зрения ясности кода — не пофиг. Понимание, что переменная один раз получает значение и больше не изменяется — очень поможет поддержке.

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

                                                Пользы же он будет давать ровно ноль
                                                Нет, не ноль.

                                                В результате приходим к варианту повсеместного использования только let
                                                Нет, не приходим.

                                                То есть const где-то ещё совсем не должен встречаться
                                                Нет, должен
                                                  –2
                                                  Зачем?

                                                  для дальнейшей доработки кода.


                                                  На дальнейшее я могу ответить только в том же духе, но не хочу.

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

                                        Ваш коллега устроился вчера, это компонент из легаси, вы со старыми привычками перешли работать в новую команду, а там такое мнение никто не разделяет или пишут, как попало…

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

                                        пример, в какой ситуации вам это что-то даёт

                                        Вы по неосмотрительности не затрёте ссылку, которую коллега решил сделать неизменяемой (для этого даже не нужно смотреть на объявление). Вы будете уверены в том, что присваивание выполняется лишь раз или же наоборот — let укажет на переиспользуемую природу ссылки. Может, как триггер: «А не фигню ли я делаю? Зачем здесь пересоздавать/перезапрашивать объект?»
                                          –1
                                          из легаси

                                          ну в легаси всё что угодно может твориться, здесь сложно что-то оценивать.


                                          не затрёте ссылку, которую коллега решил сделать неизменяемой

                                          как не затру, вот я нахожусь в каком-то месте кода, вижу $element, вижу, что это константа, если const расставлялся осмысленно, то да, не затру, ведь мне явно сказано не трогать, а так я лишь знаю, что пока ещё не менялась и всё, скорей всего можно менять просто заменив const на let. Ладно, я проверю точно ли можно так делать, но вот что там делает коллега с его хотфиксом я не знаю. А дальше получаем:


                                          из легаси
                                            0
                                            ну в легаси всё что угодно может твориться

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

                                            ведь мне явно сказано не трогать

                                            Вам это еще рантайм подскажет, выбросив ошибку.

                                            А дальше получаем: из легаси

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

                                              Ага:


                                              const PATH = require('path');
                                              const FS = require('fs');
                                              const GULP = require('gulp');
                                              const GULP_DATA = require('gulp-data');
                                              const NUNJUCKS_RENDER = require('gulp-nunjucks-render');
                                              const { t as T } = require('@riim/gettext');

                                              Как-то так себе получается.

                                                0
                                                Такие себе константы)
                                                  0
                                                  Если изменить любую в одном методе, то соседний может сломаться, с этой точки зрения вполне себе)
                                                    0
                                                    Это неймспейсы по сути, а не константы) А манкипатчинг — низя-низя.
                                      0
                                      В вашем примере (если заменить комментарии
                                      // много кода
                                      на хотябы одну строчку кода) уже 7 значимых строк кода (не считая пустые). Мне кажется не стоит писать методы в которых больше восьми, ну максимум десяти, значимых строк. Лучше выделять из них дочерние функции. А в небольшой функции и запутаться сложнее.
                                        0
                                        больше восьми, ну максимум десяти

                                        маленькое у вас ограничение. Я обычно ограничиваю себя высотой небольшого (macbook) экрана, в этом случае уже не важно значимые строки или нет.

                                          0
                                          Я посмотрел сейчас, на экран 13" макбука влезает около 40 строк кода. Это очень много для одного метода, на мой взгляд. Но в целом это конечно же дело вкуса и договоренности в рамках команды.
                                –1
                                А в тексте адекватный код?

                                function discountPrices (prices, discount) {
                                  let discounted = []
                                
                                  for (let i = 0; i < prices.length; i++) {
                                    let discountedPrice = prices[i] * (1 - discount)
                                    let finalPrice = Math.round(discountedPrice * 100) / 100
                                    discounted.push(finalPrice)
                                  }
                                
                                  console.log(i) // 3
                                  console.log(discountedPrice) // 150
                                  console.log(finalPrice) // 150
                                
                                  return discounted
                                }
                                
                                discountPrices([100, 200, 300], .5) // NOT OK: ReferenceError: i is not defined

                                Мы получили ReferenceError: i is not defined. Что говорит нам о том, что переменная, объявленная при помощи let, ограничена областью видимости блока, а не функции.

                                тут discountPrices — за пределами функции.
                                а i и finalPrice за пределами блока и комментом обозначены как выдающие значение, но объявлены через let в другом блоке
                                  0

                                  Разумеется, нет. Об этом написано сразу под ним в тексте.

                                    +1
                                    Комментарии не нужно было копировать просто.
                                      0

                                      Я так понимаю, что автор просто в комментариях к коду показывал, что у этих переменных такое значение было бы, если бы не let

                                      +1
                                      Интересно, что практически все статьи про let приводят в пример цикл for(;;), и никто никогда не описывает, какая чертовщина там творится, стоит тронуть переменные цикла в теле цикла.
                                        –1
                                        Замыкания в цикле — это анти-паттерн, который нужно избегать всеми силами. В остальном, в данном примере все корректно отработано.
                                          –1

                                          Как бы let и const ради замыканий в цикле и делались.

                                            +1
                                            В принципе да, вы правы, это считалось дурным кодом только с `var`. В таком случае код по ссылке выше полностью корректен.
                                          0
                                          В смысле «чертовщина»? `let` для того и задуман в том числе.
                                            0
                                            Так понятно, для чего, но совершенно неочевидно — как. Вот вы бы, например, угадали результат, который там получается?
                                            Скопирую сюда:
                                            let a = [];
                                            for(let i = 0;i<4;i++){
                                              a.push(function(){
                                                return(i);
                                              });
                                              i++;
                                            }
                                            console.log(a.length);
                                            console.log(a[0]());
                                            console.log(a[1]());
                                            Спойлер
                                            2, 1, 3
                                            Очень неочевидно, что i в «шапке» цикла и i в теле — это разные переменные с двумя копированиями значений на каждую итерацию.
                                              +1
                                              Вас путает двойка в начале и слишком мало данных. Если посмотрите так, то понять значительно легче:

                                              let a = [];
                                              for(let i = 0;i<14; i++){
                                                a.push(function(){
                                                  return(i);
                                                });
                                                i++;
                                              }
                                              console.log(a.map(x => x())); // [1, 3, 5, 7, 9, 11, 13]


                                              Первая итерация — создался i, он равен нулю, добавили функцию с замыканием на i, увеличили i, теперь он равен 1

                                              Вторая итерация, создан новый i, который равен старому, равен единице, увеличили на 1, теперь он равен 2, добавили функцию с замыканием на i, увеличили i, теперь он равен 3

                                              Третья итерация ...
                                                0
                                                С чего вы взяли, что двойка меня путает? Я просто говорю, что это неочевидно, если не копать специально — в какой момент что создаётся, и что что-то вообще откуда-то куда-то каким-то именно образом копируется в какой-то момент каждой итерации.
                                                P.S.
                                                В качестве разминки
                                                let arr = [];
                                                for(let i = 0, i1 = 0, b = { a: 0}; i < 4; i++) {
                                                  let c = {...b};
                                                  arr.push(()=>[i++,b.a++,c.a++, i1++]);
                                                  i1++;
                                                }
                                                console.log(arr.map(x => x().join(', ')));
                                                console.log(arr.map(x => x().join(', ')));
                                                +2
                                                Да нет, там всё-таки одна переменная на каждую итерацию, с одним копированием значения. Вот тут «просто» и «понятно» написано как оно работает: tc39.github.io/ecma262/#sec-forbodyevaluation
                                            0
                                            .

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

                                            Самое читаемое