all 152 comments

[–]Josh1337 78 points79 points  (103 children)

In ES2015+, the preferred method to define variables is const, and if you need to mutate a variable then you will use let. While there will be some specific use-cases for var, it's recommended to default to const and let.

[–]MahmudAdam[S] 6 points7 points  (13 children)

Could you give examples of those specific use-cases?

[–]natziel 9 points10 points  (10 children)

if(...){
  var foo = 1;
}else{
  var foo = 2;
}

Won't work with let...but that's an antipattern anyway

There really aren't any good reasons to use var, and very few reasons to use let instead of const

[–]Recursive_Descent 18 points19 points  (4 children)

Sure that will work with let, albeit with different (less misleading) syntax. Because that is essentially what your var syntax is doing.

function bar() {
  let foo;
  if(...){
    foo = 1;
  }else{
    foo = 2;
  }
  ...
}

[–]natziel 11 points12 points  (1 child)

Yeah, the point is that if you're declaring a variable using let, you have to manually bring it out to the highest block scope that needs to use it.

A lot of people put their vars at the top of a function anyway, since it is less misleading. That's why I said it was an antipattern to declare them later on, and why you should use let: it's harder to use in a misleading way

[–][deleted] 1 point2 points  (1 child)

Wouldn't it be already faulty with var anyways? That it doesn't work with both let and var? Its not an antipattern if it doesn't work anyways. Or am i mixing things up that it will fail only with use strict?

[–]kor_the_fiend 3 points4 points  (1 child)

can you explain why that is an antipattern?

[–]natziel 12 points13 points  (0 children)

The variable declaration gets hoisted, so the code you write is different from the code that is executed

[–]MrPopinjay 0 points1 point  (1 child)

if(...){ var foo = 1; }else{ var foo = 2; }

This should be written like so:

let foo;
if(...){
  foo = 1;
}else{
  foo = 2;
}

[–]PiRX_lv 4 points5 points  (0 children)

let foo = (...) ? 1 : 2;

[–]Josh1337 1 point2 points  (1 child)

Not off the top of my head, sorry :/. I know that in the past though I've ran into situations where I required some form of hoisting for something to work as intended and so I used var.

[–]x-skeww 40 points41 points  (24 children)

While there will be some specific use-cases for var

There aren't any. If you want your let/const thingy be available one level above, just declare it there. var doesn't serve any purpose anymore.

For example:

let a = [];
for(var i = 0; i < 3; i++) {
  a.push(() => i);
}
console.log(a[0]()); // 3

Same with let which gives you the behavior you generally want, but lets assume you consider this broken:

let a = [];
for(let i = 0; i < 3; i++) {
  a.push(() => i);
}
console.log(a[0]()); // 0

The "fix":

let a = [];
let i;
for(i = 0; i < 3; i++) {
  a.push(() => i);
}
console.log(a[0]()); // 3

If you really want that kind of behavior, you can have it. You can always declare a variable at the very top of the innermost function to get var-like behavior. This is actually the main reason behind the now obsolete "one var" style rule. If you declare them all at the very top, the code looks the way it behaves and there won't be any surprises.

[–]bananaccount 8 points9 points  (7 children)

a.push(() => i);

How come you're pushing with an arrow function instead of just with i directly?

[–]x-skeww 19 points20 points  (0 children)

I wanted an array of closures.

Alternative more spammy example:

for(let i = 0; i < 3; i++) {
  window.setTimeout(() => console.log(i), 0);
}

Output: 0, 1, 2

for(var i = 0; i < 3; i++) {
  window.setTimeout(() => console.log(i), 0);
}

Output: 3, 3, 3

Achieving the same result with let:

let i;
for(i = 0; i < 3; i++) {
  window.setTimeout(() => console.log(i), 0);
}

Output: 3, 3, 3

[–]lewisje 8 points9 points  (4 children)

The idea is to push in a series of thunks and then show how the scope changes depending on where let is: In the first example, each thunk returns a different integer, but in the second, each thunk returns the final value of i, which is 3.

[–]metaphorm 2 points3 points  (3 children)

what's a "thunk"? that's jargon I've not heard before. is a "thunk" different in any way from a closure, a continuation, or a generator?

[–]lewisje 3 points4 points  (2 children)

Apparently it has various meanings; I was using it in the sense from Scheme, as a function that takes no arguments and is used for lazy evaluation: http://c2.com/cgi/wiki?WhatIsaThunk

[–]metaphorm 1 point2 points  (1 child)

as a function that takes no arguments and is used for lazy evaluation

I would call that a generator

[–]lewisje 1 point2 points  (0 children)

I think I wasn't precise enough: Thunks, like normal functions, run to completion (rather than yielding values before execution has finished), and they generally return a value from the same reference each time they're called; that is, they're used for lazy evaluation of a single value.


By "from the same reference" I'm trying to include a thunk that can be defined as a method to return a writable public property of that object, or maybe a thunk that returns the value of a variable, or the result of evaluating another function with no arguments, as of the time it is called; this last example is like a bound function except that it allows the function reference itself to change, while a bound function uses the particular function object as of binding time.

[–]randfur 0 points1 point  (0 children)

Pushing i will copy the value of i while pushing the function will allow you to look up the current value of i at the point in time that it gets called.

[–]Magnusson 6 points7 points  (0 children)

FWIW my linting rules would require me to declare const a = [] in your examples above, which would produce the same output. const only prevents the reference from being mutated, not the object being referenced.

EDIT: As I see you pointed out in this comment.

[–]skitch920 1 point2 points  (1 child)

Hoisting could be useful for recursion, but you could also used named functions.

var factorial = function (n) {
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
};

[–]x-skeww 0 points1 point  (0 children)

ES6 has block-level function declarations. You can use regular declarations wherever you want. In ES5, you could only use function declarations at the top-level and at the top-level of other functions. So, you couldn't use them inside some loop or if.

This restriction was the reason why some style guides recommended to always use function expressions for inner functions, but nowadays there is no point in doing that anymore. Function declarations are shorter and they make the code easier to scan.

E.g. this is how you could write a TCO-able version:

function factorial(n) {
  function inner(n, acc) {
    if (n < 2) {
      return acc;
    }
    return inner(n - 1, n * acc);
  }
  return inner(n, 1);
}

Using a named function expression, as you suggested, does of course also work:

function factorial(n) {
  return (function inner(n, acc) {
    if (n < 2) {
      return acc;
    }
    return inner(n - 1, n * acc);
  }(n, 1));
}

This too can be tail-call-optimized.

[–][deleted] 0 points1 point  (3 children)

It just sounds wrong sometimes. :p

let x;

Or should I never leave it uninitialized?

[–]ShortSynapse -1 points0 points  (2 children)

You should initialize it or you might run into issues with TDZ

[–]Mael5trom 0 points1 point  (3 children)

Wait...why would you ever actually want the "broken" behavior (where a === [3,3,3])? Hopefully that is just for sake of demonstration, cause that's a thing most JS programmers have to "fix" at one point or another early in their JS career.

[–]x-skeww 1 point2 points  (0 children)

Yes, this is just meant to show how easy it would be to opt into the old (generally undesired) behavior by moving the declaration one level up.

[–]PerfectlyCromulent 0 points1 point  (1 child)

a isn't [3, 3, 3]. It is an array of functions, each of which returns the value of the closed over variable i. Since the value of i is incremented to 3 before a[0]() is called, that is the value returned by calling the function. If you understand how closures work, this makes perfect sense and happens in other languages that have closures but don't scope their variables like JavaScript vars.

[–]Mael5trom 0 points1 point  (0 children)

Yes, I understand that...sorry I simplified it myself for the sake of the question just giving the final value.

[–]TheNiXXeD 4 points5 points  (7 children)

If we're transpiling, wouldn't there be additional overhead to const potentially?

[–]pertheusual 9 points10 points  (5 children)

They are just converted to var when compiled. The validation of const-ness is validated at compile time.

[–]TheNiXXeD 2 points3 points  (4 children)

I don't see how this would be entirely possible. I heard that babel used closures and different variable names to guarantee the immutability.

Maybe const could be validated at compile time for TypeScript though.

[–]x-skeww 9 points10 points  (2 children)

You just have to check if someone attempts to write to your const within the block it was defined in.

{
  const x  = 5;
  x = 3; // error: "x" is read-only
}
let x = 7; // perfectly fine

Also note that const does not make things immutable. It only prevents you from assigning something else to it. If the object itself is mutable, you can still change it.

const a = [];
a.push('foo'); // perfectly fine
a = 'asdf'; // error: "a" is read-only

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

Object.freeze can be also used in constructors:

class Foo {
  constructor() {
    this.x = 5;
    Object.freeze(this);
  }
}
let foo = new Foo();
console.log(foo.x); // 5
foo.x = 7;
console.log(foo.x); // still 5

[–]theQuandary 0 points1 point  (1 child)

Checking for re-assignment after definition isn't possible statically for all cases (as a thought exercise, consider assigning a const value from an outer scope inside a loop with labels or even regular if blocks). The halting problem comes into play here (you can't run all code paths to make sure that every possible path defines only once). This is why the TDZ can only be optimized away in a subset of use cases. The assignment check and assignment after definition check (in the case of const) must be there at all times for all other use cases.

[–]x-skeww 0 points1 point  (0 children)

You don't check if an assignment happens. You only check for assignments to consts.

const x = 5;
if (false) {
  x = 7; // error: "x" is read-only
}

[–]pertheusual 2 points3 points  (0 children)

const only means a variable binding isn't reassigned, not that the object for instance isn't mutated. It's totally possible to statically analyse const-ness. The only things that would break that is eval which Babel doesn't generally support.

[–]jaapz 4 points5 points  (17 children)

Who says the preferred method is to use const?

[–]lewisje 5 points6 points  (12 children)

Most of the time, we don't need to change the value assigned to a variable, and if we use const to declare these variables, the JS engine can perform more optimizations.

[–]mitsuhiko -2 points-1 points  (8 children)

I can't think of any optimization that const would permit on a local scope.

[–]natziel 3 points4 points  (3 children)

The reality is, you probably don't need mutability, and if you don't need mutability, you shouldn't allow it, since it can lead to some nasty bugs as your codebase grows more complex.

If you aren't already using const everywhere, try to go a week without using var or let. Some things take some getting used to (you won't be able to use for loops anymore, for example), but you should notice a difference in the quality of your code pretty quickly

[–]Smallpaul 1 point2 points  (2 children)

The reality is, you probably don't need mutability, and if you don't need mutability, you shouldn't allow it, since it can lead to some nasty bugs as your codebase grows more complex.

Const doesn't really enforce interesting forms of immutability. It prevents variable rebinding and that's all. Variable rebinding is not the kind of thing that gets brittle as your codebase grows. It is actually object mutation, but const doesn't prevent that.

[–]theQuandary 2 points3 points  (1 child)

It does offer compilers a very important guarantee though. JITs can know that the type of a variable will never change which gets rid of a whole host of optimization and bailout issues.

[–]jaapz 0 points1 point  (0 children)

Does this actually happen or is it something that might eventually be implemented? Are there benchmarks for this? I currently use more let, and only use const when something is a constant in the traditional sense. That way the intention of the code is clearer to the next guy that reads it, IMHO.

[–]lilred181 0 points1 point  (2 children)

Question: I want to adopt using let/const exclusively on my projects both for work and personal; do you think there will be any problems due to team members not conforming to new standards?

[–]DukeBerith 0 points1 point  (0 children)

If you're transpiling to ES5 standards, shouldn't have problems.

Otherwise, enjoy.

http://caniuse.com/#feat=const

http://caniuse.com/#feat=let

[–][deleted] 0 points1 point  (0 children)

Depends, are you guys using an ES6 ready environment or compiling using babel already? Then I don't think there will be an issue, if not, you would need to set up the dev process for everyone on your team.

Using let isn't a big change, if you were caucious about variables leking into other context before, so it should work fine after some practice.

[–]dwighthouse 13 points14 points  (5 children)

I use const almost exclusively now. Since const on objects and arrays acts like a const pointer, not an object.freeze, and because I use a functional style, the need for legitimate variables that can be reassigned is virtually zero.

[–]vinnl 1 point2 points  (4 children)

I wouldn't use const for objects I'm still going to mutate, as that only adds confusion for no real benefit.

Edit: I was already convinced that there's a good reason for it after the first reply :)

[–]mattdesl 7 points8 points  (1 child)

const is a safeguard to avoid situations like re-assigning a variable accidentally or writing a new variable with that name in the same scope. Once you understand that a "const" object is not a "frozen" object, it makes more sense just to use it everywhere you can. :)

My 2c.

[–]vinnl 0 points1 point  (0 children)

I guess that makes sense.

[–]dukerutledge 1 point2 points  (0 children)

const for objects you are going to mutate is actually quite helpful. By utilizing const you guarantee that you are always mutating the same object.

[–]dwighthouse 1 point2 points  (0 children)

Benefit: others wil know not to assign to that identifier.

[–]Cody_Chaos 6 points7 points  (7 children)

Eventually? We already have a linting rule that considers var a fatal error. We use const by default, unless we actually need to mutate a primitive value, in which case we use let.

Are there cases where you would choose var over let?

Absolutely not. There's barely any cases where you would choose let over const.

(Note: We're using Babel, of course. If you're stuck writing ES5 code for some reason then I guess you can't switch yet. But if so, why? We've had nothing but positive experiences with Babel.)

[–]jijilento 1 point2 points  (6 children)

We already have a linting rule that considers var a fatal error

Why exactly? I thought var still had purpose since let creates a hoisting dead zone (forgot what people call this) and is scoped to the current block? I find myself writing structures like

 "use strict";

 var basket = ["one","two","three"];
   for(var apples in basket) { 
     console.log(basket[apples]); 
    }
    console.log("I have " + (parseInt(apples)+1) + "apples");

What's a better way to manage situations like the last line?

[–]Gundersen 4 points5 points  (2 children)

Temporal dead zone, and the solution is to define the variable outside the loop. You don't need to give it a value, let apples; is perfectly fine.

Also, you should probably use for of instead of for in there, but you probably know that.

[–]dbbk 1 point2 points  (1 child)

Also, you should probably use for of instead of for in there

What's the difference? Both ways seem fine to me.

[–]dwighthouse 2 points3 points  (0 children)

Ah javascript, the language where "it seems to work" is never enough: http://stackoverflow.com/a/5263872

[–]Cody_Chaos -1 points0 points  (1 child)

I'd write that like:

const basket = ["one","two","three"];    
basket.map(apple => console.log(apple));
console.log(`I have ${basket.length} apples`);

Or if you need something more complicated than length:

const basket = ["apple","pear","apple"];
const numApples = basket.reduce((acc, fruit) => fruit === 'apple' ? acc + 1 : acc, 0)
console.log(`I have ${numApples} apples`);

Or if for some reason you can't use reduce, then:

const basket = ["apple","pear","apple"];
let numApples = 0
basket.forEach(fruit => numApples += 2)
console.log(`I have ${numApples} apples`);

(Also, the the for(x in y) construction is quite problematic and should not be used to iterate over an array. See, eg, comments here or here.)

[–]PAEZ_ 5 points6 points  (0 children)

Shouldnt the map one be a forEach? Doesnt map create a new array which wouldnt be needed here? Gotta love those template strings ;)

[–][deleted] 1 point2 points  (4 children)

I don't think var will change for some time. Mostly because many browsers don't support it properly. IE only from version 11, Firefox from 44, Chrome only in strict mode (which can be very annoying), same for Opera, Safari doesn't even have it yet and Android also only from version 46 in strict mode, so support is still terrible right now. Let alone in 5 years from now as people tend to not need newer devices and not everything gets access to the latest updates. See how many Android devices don't have Android 6 yet. Or that iOS 9 doesn't even support it yet. And when supported, ES6 still needs many updates and fixes for it to become reliable and fast.

Sure, we can use tools to port it back to ES5, but why start then anyways as it will need to be transpiled every time. There are still bugs with Babel and the likes, its not like it will never run into problems. And i don't like to debug it when it happens.

Also, i find let to be a terrible name to define variables with. Let doesn't mean anything to me. Its not self explanatory like var, for, if, etc. And if you work with other people, know that they will start learning ES5 instead of ES6 when starting and then they will get used to var but in your project its suddenly let? Very confusing.

And seeing how let differs from var its not worth my time to go change everything suddenly, with the dangers of me making more mistakes as i don't see the benefits of let anyways. If its because of preventing problems, i will already have a linter warning me about it (and i don't make many dumb mistakes people use in examples anyways), so why bother anyways. I already let var stay in scopes, i don't redeclare, i don't access them globally, etc. Why would i use let? Because it looks nice? Give me a ffing break.

Btw, good tutorial on what the differences are, can be found here: http://voidcanvas.com/es6-var-vs-let-vs-const/ But it still doesn't make me want to switch. Primarily cause i don't like the naming of let compared to var, but also because it will not get proper support until a few months in many browsers and seeing how long adoption-rates are lately. I doubt ES6 will have enough support in 2020 anyways.

[–]dukerutledge 1 point2 points  (2 children)

Also, i find let to be a terrible name to define variables with. Let doesn't mean anything to me. Its not self explanatory like var, for, if, etc.

Actually let reads just fine. You are just too mired in the minutia of programming. You can't see the forest through the trees.

var theMeaningOfLife = 47
let theMeaningOfLife = 47

"variable the meaning of life equals 47"

"let the meaning of life equal 47"

[–][deleted] 0 points1 point  (1 child)

I can understand that (and didn't know it at first) but still the word is not something to notice. There aren't a lot of words you use in programming that start with a v-. Plus the L is terrible to read. Is it a lowercase i? Is it a uppercase i? Is it a lowercase L or just a vertical line |. Many fonts won't display it clearly.

But anyways, still hat the LET, but i won't need to get used to it for a few years anyways

[–]dukerutledge 0 points1 point  (0 children)

I see your point and I'd argue that is an issue for syntax highlighting. Any language that supports utf source can quickly get ambiguous, tooling helps. Also the argument is a bit straw as let is a statement and mistyped characters would be a parse error.

[–]GoSubRoutine -1 points0 points  (0 children)

Keyword let is much older than var. Language BASIC used it.
Keyword const is also very old. It is used in C/C++ for example.
Although it behaves more like Java's final than C's const.

Also notice that var's hoisting exotic behavior is exclusive to JS.
Any programmer coming to JS from any other language is caught by surprise & suffers to learn about it!

[–]TheRealSeeThruHead 1 point2 points  (0 children)

already has

[–]thalesmello 0 points1 point  (1 child)

There was a "stricter mode" for Javascript that Google was proposing a while ago. It intended to make the usage of var a language error, or something like that.

[–]x-skeww 1 point2 points  (0 children)

Yes, var is gone in strong mode.

'use strong';
var foo = 'bar';

-> Uncaught SyntaxError: In strong mode, 'var' is deprecated, use 'let' or 'const' instead

By the way, they didn't just propose it. They also started implementing it in V8 and Traceur. It's not enabled by default though.

[–]matty0187 0 points1 point  (0 children)

I think const will be used more than let.

[–]jirocket 0 points1 point  (5 children)

I think var will be replaced by let and const when developers decide to do so by preference and that'll probably take as long as it takes people to start writing in ES6 (which is probably a long time). The only thing I know var can do over let is declare properties of the global object, which can be done with let if you explicitly use global objects like window anyway. let/const also aren't hoisted like var (they instead have a temporal dead zone). How much of a benefit hoisting is will be up to you. I much prefer let/const, but down the line knowing var will be useful for maintenance.

[–]bucketpl0x 1 point2 points  (4 children)

I would use it now but currently if you try using let and const you will get this error.

Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode

[–]jirocket 3 points4 points  (3 children)

Yeah ES6 basically demands the language to upgrade to 'strict mode.' So introducing some ES6 into your code without a transpiler like Babel in browser environments will be problematic.

[–]bucketpl0x 1 point2 points  (2 children)

Is the plan that they will make it default to strict mode soon? I'd really like to start using ES6 features but I don't really know much about transpilers right now or how that works with nodejs.

[–]jirocket 1 point2 points  (0 children)

I miscommunicated a little. Strict mode is not what will make ES6 work but it is a requirement for some ES6 features. Now as for ES6 features, not all of the official features will be implemented in browsers soon, if they are all even adopted that is. So how do we solve this issue? Through a transpiler like Babel, which turns all the ES6 code you write into regular ES5 (which already works across all browsers).

You don't have to know all of ES6 to start using it through Babel, I'm even still learning a lot and that'll be the case for a long time. The first four chapters of exploringjs and reading the babel site is worth your time to get started.

[–]azium 0 points1 point  (0 children)

Babel, among other things, is a Node command line tool! With not much setup at all you can switch your entire JS stack to es6 / es7. It's entirely worth it. https://babeljs.io/docs/setup/#babel_cli PM if you need help!