20

If the let keyword introduces a proper implementation of block scope, does var any longer have a use case? I am looking at this from a software design standpoint rather than a syntactical, "well you could" standpoint.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Kamuela Franco
  • 686
  • 1
  • 5
  • 16
  • Related: http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword – Marvin Aug 05 '15 at 15:35
  • 2
    I understand the difference, I am just trying to pick programming brains right now for why `var` will not just leave common usage forever. – Kamuela Franco Aug 05 '15 at 15:41
  • Possible duplicate of [What's the difference between using "let" and "var" to declare a variable?](https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable) – James Donnelly Feb 27 '18 at 08:23

5 Answers5

18

If the let keyword introduces a proper implementation of block scope, does var any longer have a use case?

There could be one use case: let declarations in global scope don't create a property on the global object. Example:

"use strict"; // for chrome
var foo = 42;
let bar = 21;
console.log('window.foo (var)', window.foo); // 42
console.log('window.bar (let)', window.bar); // undefined

From 8.1.1.4 Global Environment Records

The object Environment Record component of a global Environment Record contains the bindings for all built-in globals (clause 18) and all bindings introduced by a FunctionDeclaration, GeneratorDeclaration, or VariableStatement contained in global code. The bindings for all other ECMAScript declarations in global code are contained in the declarative Environment Record component of the global Environment Record.

However, this can also easily be solved by creating an explicit global variable using by assigning to the global object directly:

window.foo = 42;

This would also be the only way to create global classes btw, because the class declaration has the same behavior.

(Note: I'm not advocating the use of global variables)


There are syntax constructs where you can only use var, but that's more a consequence of the how the spec evolved and doesn't really serve any practical purpose. For example:

if (true)
  var foo = 42; // valid but kind of useless or bad design

// vs

if (true)
  let foo = 42; // invalid

Block scope is not the only useful feature though. The temporal dead zone is another handy feature to find bugs more easily. Compare:

var foo = 42;
function bar() {
  console.log(foo); // undefined
  var foo = 21;
}
bar();

// vs

var foo = 42; // or `let`, doesn't matter
function bar() {
  console.log(foo); // ReferenceError, temporal dead zone
  let foo = 21;
}
bar();

You get a reference error when trying to access a let variable that wasn't initialized yet.

Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 2
    From what you're saying, it seems like `let` introduces a stricter environment for business logic. It's my opinion that it has to phase the use of `var` out over time in enlightened circles. – Kamuela Franco Aug 05 '15 at 18:09
  • why does the let declaration after the if statement fail without curly braces? , ie `if (true) let foo = 42; // invalid`, whereas `if (true){ let foo = 42; // valid }` – user1063287 Nov 30 '21 at 16:05
  • 1
    @user1063287: Just from a practical perspective there is no point in declaring a variable that cannot be accessed anywhere else (the `let` variable wouldn't be visible outside the `if` statement but there is no other statement in the `if` statement). From a grammar perspective, an `if` statement's body has to be a *Statement*. A `let` "statement" is a *LexicalDeclaration*, which is a *Declaration* but **not** a *Statement*. But a *BlockStatement* (`{...}`) can contain a list of *Statement*s and *Declaration*s. – Felix Kling Nov 30 '21 at 17:42
6

let can't be used in global scope yet. var can.

This is what you get from Chrome when you try a global let outside of strict mode:

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

Amit
  • 45,440
  • 9
  • 78
  • 110
5

Practically there may be some use-cases.

1. Declare variable in try-catch like so:
try {
    //inits/checks code etc
    let id = getId(obj);

    var result = getResult(id);
} catch (e) {
    handleException(e);
}

//use `result`

With let the result declaration would be before try, - a bit early and out of context.

2. Same for conditional declarations:
if (opts.re) {
    var re = new RegExp(opts.re);
    var result = match(re);
    if (!result) return false;
}

// result is available here

With let this code would be forced to complain "good style", though that might be impractical.

3. Loop block:
for (var x = 0; x < data.width; x++) {
    if (data[x] == null) break;
    //some drawing/other code
}

//`x` pointing to the end of data

Some may consider that untidy, I myself prefer lets, but if code I already has vars - that's natural to keep using them.

dy_
  • 6,462
  • 5
  • 24
  • 30
  • Then why not declare a variable with `let` before the `try` or `if` and assign a value afterwards? In my opinion, it would make it more readable, being able to follow more easily in which block what variables are available and where they are declared within that same block. – undefined Oct 25 '21 at 15:17
  • @undefined it would require an additional line of code that doesn't do much – alexpirine Jul 04 '22 at 00:26
  • @alexpirine it defines a variable in the needed scope, which is how all (to my knowledge) other languages work and it makes little sense to be able to use a variable outside of the scope it was defined. This effectively removes the implicit functionality of `var` and replaces it with explicit functionality of `let` – undefined Jul 12 '22 at 12:37
  • 1
    @undefined yes, it sure helps with the scope, but again, sometimes fine-tuning the scope doesn't matter much. I mainly develop in Python, and we only have global and local scopes. In practice, 99.99% of the time we use the local scope which is more or less equivalent of using `var` in JavaScript. So we have less control over it and linters have to work harder to spot issues, but it's not preventing us from writing software :) However, I decided to use your approach by defining the correct scope with `let` when writing in JS. It seems to better fit “the JavaScript way of doing things”. – alexpirine Jul 14 '22 at 13:39
1

You can use var if you want to deconstruct something into the function scope, for example a conditional:

if (Math.random() > 0.5)
  var {a,b} = {a: 1, b: 2}
else 
  var {a,b} = {a: 10, b: 20}

// Some common logic on a and b
console.log(a, b)

With let you would have to write something like

let result;

if (Math.random() > 0.5)
  result = {a: 'foo', b: 'bar'}
else 
  result = {a: 'baz', b: 'qux'}

// Using const might make more sense here
let {a, b} = result; 
// Some common logic on a and b
console.log(a,b)
Uri
  • 2,306
  • 2
  • 24
  • 25
  • with `let`, one can change the property directly without causing error like `result = {a: 'foo', b: 'bar'}; result.a= poof` – Eldwin Mar 23 '21 at 21:28
  • Why not `let a, b;` then `a = "foo"`, `b = "bar"`? Seems simpler to me and makes it obvious in which block these variables can be used. – undefined Oct 25 '21 at 15:20
1

Don't Throw Out var

Stylistically, var has always, from the earliest days of JS, signaled "variable that belongs to a whole function." var attaches to the nearest enclosing function scope, no matter where it appears. That's true even if var appears inside a block:

function diff(x,y) {
    if (x > y) {
        var tmp = x;    // `tmp` is function-scoped
        x = y;
        y = tmp;
    }

    return y - x;
}

Even though var is inside a block, its declaration is function-scoped (to diff(..)), not block-scoped.

While you can declare var inside a block (and still have it be function-scoped), I would recommend against this approach except in a few specific cases. Otherwise, var should be reserved for use in the top-level scope of a function.

Why not just use let in that same location? Because var is visually distinct from let and therefore signals clearly, "this variable is function-scoped." Using let in the top-level scope, especially if not in the first few lines of a function, and when all the other declarations in blocks use let, does not visually draw attention to the difference with the function-scoped declaration.

In other words, I feel var better communicates function-scoped than let does, and let both communicates (and achieves!) block-scoping where var is insufficient. As long as your programs are going to need both function-scoped and block-scoped variables, the most sensible and readable approach is to use both var and let together, each for their own best purpose.

There are also other semantic and operational reasons to choose var or let in different scenarios.

WARNING:
My recommendation to use both var and let is clearly controversial and contradicts the majority. It's far more common to hear assertions like, "var is broken, let fixes it" and, "never use var, let is the replacement." Those opinions are valid, but they're merely opinions, just like mine. var is not factually broken or deprecated; it has worked since early JS and it will continue to work as long as JS is around.

Where To let?

My advice to reserve var for (mostly) only a top-level function scope means that most other declarations should use let. But you may still be wondering how to decide where each declaration in your program belongs?

POLE already guides you on those decisions, but let's make sure we explicitly state it. The way to decide is not based on which keyword you want to use. The way to decide is to ask, "What is the most minimal scope exposure that's sufficient for this variable?"

Once that is answered, you'll know if a variable belongs in a block scope or the function scope. If you decide initially that a variable should be block-scoped, and later realize it needs to be elevated to be function-scoped, then that dictates a change not only in the location of that variable's declaration, but also the declarator keyword used. The decision-making process really should proceed like that.

If a declaration belongs in a block scope, use let. If it belongs in the function scope, use var (again, just my opinion).

Why use var for function scoping? Because that's exactly what var does. There literally is no better tool for the job of function scoping a declaration than a declarator that has, for 25 years, done exactly that.

You could use let in this top-level scope, but it's not the best tool for that job. I also find that if you use let everywhere, then it's less obvious which declarations are designed to be localized and which ones are intended to be used throughout the function.

By contrast, I rarely use a var inside a block. That's what let is for. Use the best tool for the job. If you see a let, it tells you that you're dealing with a localized declaration. If you see var, it tells you that you're dealing with a function-wide declaration. Simple as that.

function getStudents(data) {
    var studentRecords = [];

    for (let record of data.records) {
        let id = `student-${ record.id }`;
        studentRecords.push({
            id,
            record.name
        });
    }

    return studentRecords;
}

The studentRecords variable is intended for use across the whole function. var is the best declarator to tell the reader that. By contrast, record and id are intended for use only in the narrower scope of the loop iteration, so let is the best tool for that job.

In addition to this best tool semantic argument, var has a few other characteristics that, in certain limited circumstances, make it more powerful.

One example is when a loop is exclusively using a variable, but its conditional clause cannot see block-scoped declarations inside the iteration:

function commitAction() {
    do {
        let result = commit();
        var done = result && result.code == 1;
    } while (!done);
}

Here, result is clearly only used inside the block, so we use let. But done is a bit different. It's only useful for the loop, but the while clause cannot see let declarations that appear inside the loop. So we compromise and use var, so that done is hoisted to the outer scope where it can be seen.

The alternative—declaring done outside the loop—separates it from where it's first used, and either necessitates picking a default value to assign, or worse, leaving it unassigned and thus looking ambiguous to the reader. I think var inside the loop is preferable here.

There are other nuances and scenarios when var turns out to offer some assistance, but I'm not going to belabor the point any further. The takeaway is that var can be useful in our programs alongside let (and the occasional const). Are you willing to creatively use the tools the JS language provides to tell a richer story to your readers?

Don't just throw away a useful tool like var because someone shamed you into thinking it wasn't cool anymore. Don't avoid var because you got confused once years ago. Learn these tools and use them each for what they're best at.

Source: YDKJS by Kyle Simpson

norbekoff
  • 1,719
  • 1
  • 10
  • 21