Var For Life - Why Let And Const Don't Interest Me In JavaScript
WARNING: This is just an opinion piece from one person.
For as long as I've been creating variables in JavaScript, I've been using the Var keyword. And I like to think that I've been doing alright with it. Now, with ES6, there are a lot of really smart people talking about how many of our variable declarations should be using Const and Let. But, I just don't connect with the concept. While the explanations that I read are logical, they don't strike a chord in me - they don't seem to be solving a problem that I have.
There's no doubt that Const and Let are more specific. By definition, they are. And that would be great if that were the way that I thought about the code. But, it's not. And, if the code doesn't reflect the way that I think, how can I justify my choices? And, if I can't justify my choices, how can I be confident that I actually know (or at least think that I know) what I'm doing?
When I write a variable declaration like this:
var sanitizedValue = value.replace( /^\s+|\s+$/g, "" );
... I'm not thinking about whether or not that variable will be changed later on in the function. Maybe it will, maybe it won't. And, maybe that truth of that reality will change over time. And, that's where I really start to lose interest in Const and Let.
Maybe the above statement is how I initially write the code. But, then I realize, "Hey, Regular Expressions are hard - maybe I should break this up a bit." So, I change the above single line of code into several lines of code with more explanation:
// When sanitizing the input, let's start out with the core value.
var sanitizedValue = value;
// Then, strip off all leading spaces.
sanitizedValue = sanitizedValue.replace( /^\s+/, "" );
// Also, strip off all trailing spaces.
sanitizedValue = sanitizedValue.replace( /\s+$/, "" );
Since I'm using Var, reassigning the sanitizedValue works well. But, if my original declaration were a Const, reassigning the sanitizedValue variable would throw the following error:
Uncaught TypeError: Assignment to constant variable.
And, to me, this feels like a code smell. Now, I'm sure many of you are thinking that the "real code smell" is actually the inline sanitization and you probably think that the act of sanitization should be moved out into its own function. And, in many cases, I would absolutely agree with you. But, not always; maybe this is the only use of it and seeing the implementation would be a value-add for the developer. Or, maybe this is the sanitization function and there's no one else call.
The point is, while the intent of the code did not change, the possible variable declaration approaches did. And, that is the biggest disconnect for me. When the variable declaration approach changes despite the fact that the intent of the code remains the same, I feel like I can never truly justify my decision. It becomes one of mere coincidence and not one of intent.
Block scoping (using Const or Let) suffers from the same problem. Imagine that I start out with code that looks like this:
var message = "Good morning";
Then, later on, I want the message to change based on the time of day. So, I alter it to be something like:
if ( isMorning() ) {
var message = "Good morning";
} else {
var message = "Good afternoon";
}
If those declarations used Const or Let, instead of Var, the refactoring to an If-Else statement would probably break the code with the following JavaScript error:
Uncaught ReferenceError: message is not defined
This is because the block-scope of the If-statement wouldn't allow the Let-based or Const-based value be available to the rest of the function.
Now, again, you're probably thinking that the selection of the message should be factored out into another function. And, again, you might be right. But, the problem is, we keep talking about fixing the architecture of the code in order to justify the use of new variable declarations. And that, to me, is the root of the disconnect. Instead of trying to use an unnecessarily "meaningful" variable declaration, I would first try to refactor the code such that the resultant clarity makes the type of variable declaration an insignificant choice. And, once it becomes insignificant choice, we can reduce it down to a single choice - Var.
At the end of the day, I guess my core emotion here is that if the type of variable declaration is actually adding meaningful clarity, if feels like its a symptom of something else that's wrong.
Now, if you tell me that using Const will result in a 50% performance boost because of some magical compiler optimization, then that becomes a different type of conversation. Not one of clarity but, rather, one of performance. Of course, I have no idea how Const and Let affect performance, if at all. But, if they did, we could have a conversation based on different values.
In the end, however, this is just the way that I think about the code that I write. I have never felt that the use of Var lead to unclear variable usage. However, I can see places in which I would have to use Var in order to get my code to work. So, rather than ever caring about how my variables will get used, I'm just going to continue to use Var and move on to bigger problems (like architecture). Of course, your feelings about your own code may be totally different and your mileage may vary.
Want to use code from this post? Check out the license.
Reader Comments
I'll be honest with you... I'm not sure how much I like the const/let over var situation either. For certain things that we know are constants, like environment variables or something, I like the idea of being able to prefix them with a `const` to show beyond a shadow of a doubt that they won't be changed. Then again, CAPITALIZED_VARIABLES do a decent job of signifying that with var.
Maybe it's that I'm so used to javascript function scoping that when I see the var keyword used inside an if/else I automatically pull it out above the if/else so the variable declaration is separate like it'll be eventually parsed, and in that case it should work with let too. Then again, what are you really gaining with let other than adding another feature that placates those familiar with block scoping? The more I think about it the more I'm with you on this one. Maybe it's just something we'll have to get used to.
I think the introduction of let clears up the scoping issues inherent with bar. Anyone that has programmed in JavaScript for any length of time is used to the non-block scoped nature of var and will often structure code in a manner to make the variable scope clearer (see Douglas Crockford's jslint rules). That said, it goes against how most people think of variable scope, especially when compared to othe C-style languages.
I think let is often more of how developers think about scope and in a lot of cases bar can be easily replaced with it.
I can't think of a similar argument for const, but I would expect a marginal perfomance gain. It also adds enforcement of constant rules where understood conventions (all caps) cannot.
@James,
I've only programmed in a few languages, and I don't think any of them had block-level scoping, only function and component, so I don't think I have any bias towards thinking about scoping other than at the function level. I guess it's all what you know - I would never have expected a variable to be limited to a block, seems odd to me.
Of course, when you go to a new language, you have to learn it, right? I mean, no one is saying C-style languages should change to match JavaScript? Every language is different.
Even in a loop, though, I don't see the point of the block scoping. If you are in trouble of overwriting other variables of the same name (such as having multiple values called "X" in the same lexical geography), then that's a horrible idea, and you shouldn't do that even with block scoping.
And, what's wrong with a variable going outside the block? I can't think of a good reason to prevent this; but, I can think of a reason to allow it. For example, knowing where in a loop I broke out:
for ( var i = 0 ; i < 100 ; i++ ) {
. . . . if ( someRandomTruthy() ) { break; }
}
console.log( "Loop broke at", i );
If I were to `let` the iterator, instead of `var` it, I couldn't do this. Why the unnecessary restriction. Just seems odd to me.
@Rob,
Yeah, I keep seeing people explain "this is what these do"... but, I don't think I ever see a solid of explanation of , "AND, this is the PROBLEM that solves."
The main reason why let and const are useful is to use up less memory. If the JS engine knows something will not change it can allocate the least amount of memory and use cached values. With a var things can change all the time. I do agree with you though that for a lot of our code this can feel like overkill.
There is a great detailed explanation of the implications here: http://raganwald.com/2015/05/30/de-stijl.html
I like this post as it shows me a thing I keep telling people: there is no _one_ way of doing things. You don't _have_ to use feature $x because it is new. And we need to explain the _why_ much more than the _what_.
let, const and block scoped bindings come into their own when we talk about large applications that need to scale, or libraries that need to have a very low memory footprint. It can be useful for smaller scripts, too, but you shouldn't feel that you need to use it just because it is the new thing to do. The overhead of having to transpile for non-ES6 environments might not be worth it in your case.
I was sceptical for a time as well. Some of it depends on your style of coding. I tend to write in a functional style. As a result, the majority of my "var" declarations are "const". I have grown to like this. If I'm writing a function that has more than one or two "let or var" declarations, I consider refactoring.
"let" seems less useful. Block scope is a "nice to have", but I tend to write with "const", so block scope is not something I worry about much.
@Christian,
I agree that if the conversation is about performance, memory, and scaling, then that can be a very interesting conversation. Especially with a very large application where memory consumption is a _real issue_. Of course, it also seems like one of those things people should worry about when they have to worry about it (not necessarily before).
That said, if we are going to live in the age of distributed systems and scaling horizontally instead of vertically, it seems that we don't want to have to rely on Var vs. Const to "make the difference." But, I will concede that if you know something is good for performance, it's not wrong to use it (unless it hurts readability ... like a reverse while-loop instead of a standard for-loop).
There's a lot about ES6 that I am looking forward to. This is just one of the features that seems disproportionately praised for what it is actually doing.
Will definitely check out that link, though, thanks!
@Mike,
Can you expand on this:
> I tend to write in a functional style. As a result, the majority of
> my "var" declarations are "const".
What is the problem that const is solving in functional programming?
@Christian,
It just occurred to me that you might not be talking about server-side JavaScript. I was just having a Node.js conversation with someone, so was thinking about Node, where you can scale horizontally. But, with client-side JavaScript, one user only ever has one browser :D As such, memory use does become much more of an issue, especially with large thick-client apps.
I know that at InVision, I'm in the Chrome timeline / profiler all the time looking at memory leaks and the massive amount of garbage collection that needs to be done.
So, makes me wonder if there is also a different use-case / mindset for server-side vs. client-side JavaScript.
Sure. I use map, filter and reduce extensively when working with data.
var x = data.map(d => d * 10);
vs.
const x = data.map(d => d * 10);
I don't expect "x" to change. "const" helps the compiler enforce this but more importantly, I as a reader of the code know it is not suppose to change. It's a trivial example I know, but in a larger function, it's helpful to know what mutates and what remains unchanged.
@Mike,
I think my capacity to think about the code is just not as robust as other people. If I'm looking at line 5 of a function, I honestly don't remember how a variable was declared on line 1. Maybe that's why I just don't connect with how this improves readability or the ability to reason.
But, if it helps the compiler, no arguing with that :)
@Ben,
there very much is. There is too much "this is the better way to write JS!" without considering just how much difference there is between server and client side.
@Christian,
I was just replying to John Papa's poll about Node, said:
> If all you've done is client-side JavaScript, I think you know
> enough to be very dangerous in Node.js :D
And, to be honest, my experience is almost entirely with client-side code. Am looking to break into the Node.js world a bit more. Seems really exciting.
I like 'em.
Regarding your first sample, with the sanitize code, it should definitely be moved into a function, even if it's local to the script that uses it, just so you can give it a meaningful name like `function trimWhitespace(value)`.
`const` makes your intent explicit, and stops you accidentally assigning to the wrong variable, for example.
`let`, as I understand it, is very good for the GC as well as not leaking to shadowed variables and such. I particularly like it in for loops, as in `for (let item of list)`, where it stops the `item` variable from being used outside the loop (and if you do want to do that, then use `var`).
The point of the `trimWhitespace` function name being that it removes the need for those two comment lines ;)
@Mark,
But, as I mentioned in the blog post, what if that IS the trimWhitespace() function? I might still want to have two lines that breaks up the implementation of the trimming operation. And then, you still have the same issue, the intent of the code remains the same but the variable declaration has to change. As such, I don't see how it make the code more meaningful.
Personally, I think compiler-based optimizations are the only selling point that I can see at this time.
@Ben,
I guess, as a C# developer of 15 years, I'm used to having `readonly` variables and writing functions when there's a multi-step initialization. But I still maintain that having a two-line function called `trimWhitespace` is better than having a couple of regular expressions with comments explaining that they're trimming whitespace, regardless of const/let/var.
@Mark,
Ha ha, sorry, I didn't mean to say I was fighting against the trim function. 100%, that's how I would write it. I only meant to try to illustrate that you could start with this:
function trimValue( value ) {
. . . . CONST trimmedValue = value.replace( /^\s+|\s+$/g, "" );
. . . . return( trimmedValue );
}
... but, if you wanted to break that up, you would have to change the variable declaration from Const to Var:
function trimValue( value ) {
. . . . VAR trimmedValue = value;
. . . . trimmedValue = trimmedValue.replace( /^\s+/, "" );
. . . . trimmedValue = trimmedValue.replace( /\s+$/, "" );
. . . . return( trimmedValue );
}
... otherwise you get an error. And all I'm saying is that if the variable declaration has to change while the intent of the code remains the same, something feels "off".
The problem with const is it limited use case. Most of the time ii do not need it and when i do i think specifying you intensions with a comment or casing is good enough.
But another problem that i am afraid of is the c++ syndrome where new keywords appear after every revision. I just want a simple language where i do not need to remember 100 keywords and what they do in different occasions. On top of that when the start reusing keywords to not introduce new ones. Then i will look for some other language.
@Emir
I agree that the concept of const can be handled with a comment or casing convention, but that still doesn't make it a constant value that the compiler could be optimized for or that prevents the user for changing it after initialization. Using the const keyword for true constants in a program both conveys the rule without needing a comment and enforces it to actually be constant. In terms of changing variable intent, in most cases you would need to change the casing of the variable name anyway to go from using it as a const or not just to indicate the intent.
As for new keywords const and let, both are currently reserved words in ECMA 5 (all the way back to 1 for const), so they have been bouncing around the spec for awhile.
Most of this comes down to personal preference in the end because even without the new keywords the JavaScript community has developed patterns and conventions to simulate the concepts. The new keywords are simply tools that you can use in place of or to supplement some of those techniques and give you the added benefit of better GC and memory optimization.
@Ben,
Gotcha.
The CONST and LET keywords is not only about performance and memory, it's also about trust! It's about that I can trust that noone else screwed up these variables!
When I did a ColdFusion gig last year I really did miss my constants and block scoped variables. The reson to this was that I couldn't trust that other code didn't change the variables for me. Because it did happen quite a few times!
In the previous comments where you have tried to use CONST. Well, you used it wrong!
Constants is for variables that you want to trust that they never change!
For example:
const FRUIT_APPLE = 1;
const FRUIT_BANANA = 2;
const FRUIT_LEMON = 3;
$('#mySelect').append($("<option/>", { value: FRUIT_APPLE, text: "Apple" }));
$('#mySelect').append($("<option/>", { value: FRUIT_BANANA, text: "Banana" }));
$('#mySelect').append($("<option/>", { value: FRUIT_LEMON, text: "Lemon" }));
$("#mySelect").change(function() {
switch(this.value) {
case FRUIT_APPLE:
document.write("Apple");
break;
case FRUIT_BANANA:
document.write("Banana");
break;
case FRUIT_LEMON:
document.write("Lemon");
break;
}
});
This way I can trust that I will compare against the correct values as I have declared them with CONST and noone can change them.
The same with LET when I use it within loops and if statement blocks is that I can use the variable here, but I do not change the meaning of the variable for anyone else.
I really like the idea of using const to enforce immutability.
I also wonder if the inclusion of let and const is a more performant way to enable some consistent behavior at the JS level for some languages which transpile to JavaScript.
I could see functional languages like elm making good use of it, simplifying the under the covers contract to ensure immutability.
@Ben,
Would totally agree that the code example smells.
The second example introduces mutation into your algorithm unnecessarily in an effort to improve readability. A better approach would be to use CONST to clarify the intent of the RegEx's
const leadingSpaces = /^\s+/;
const trailingSpace = /\s+$/;
var sanitizedValue = value.replace( leadingSpaces, "" ).replace( trailingSpaces, "" );
@Kjell-Åke,
I think this is the most compelling point. CONST and LET demonstrate intent, that is enforced by the compiler. On a large distributed project maintaining intent is very important. They also have the added value of small performance improvements. So the question I would ask is if you see no reason to use them is that because you're not applying intent to the life cycle of your variables? Or because you don't want the additional performance?
@Kjell-Åke, @Janga,
I think intent and trust make sense, if that is *actually* what the developer is thinking about at the time. And, I would hazard a guess that when people write things in all upper-case, such as:
const FRUIT_APPLE = 1;
... they *really do intend* for that variable to never change.
However, I would also guess that when a developer creates a variable like:
var x = Math.max( 0, input );
... they do not *actually* think about whether or not that variable will be changed.
Now, it sounds like people coming from other languages with a stronger sense of type may be trained to think that way. But, I think the vast vast majority of JavaScript developers do not. They create a variable, they use. Maybe they overwrite the reference, maybe they don't. But, I doubt that most of them think about that at the time they declare the variable.
And, by adding constructs like Const and Let, you're now getting the developer to solve a problem that they didn't have before.
But, of course, that's just my guess.
But, think about it this way, if we have developers who maybe already don't understand scoping and hoisting and how JavaScript works, are they really doing to know how and when to use Const and Let effectively?
It seems like the people who truly understand it probably won't need it. And the people who may need it probably won't know to use it :D
But again, I'm just guessing at that point. I can really only talk about how *I* think about code.
@Chris,
That's definitely a clean approach, labeling the RegEx patterns with something that makes sense. I would definitely like to see code that way. And, the way that you wrote it makes me want to think about const values more like "static class values." Which makes me somewhat more comfortable with const being in, say, the top of a node.js module, but never inside of a function.
@Daryl,
Just to be careful const doesn't make an object immutable, it just locks the reference. So, if you have an object whose reference is const:
const person = { name: "Daryl" };
... you can still mutate the person properties. You just cannot overwrite the person reference. This was something that confused me the first time I saw const so I just wanted to draw attention to it.
For me it's const4lyfe. I use let sparingly, and avoid var if at all possible.
The simple fact is that variable mutation makes your code harder to read and reason about. Const gives you assurances that things won't change. Let allows you to re-use variable names without worrying that they're going to collide with other block-scoped variable names in the same function (seriously, JavaScript variable hoisting is awful behavior; especially when it comes to closures).
General good programming practice dictates that local variables refer to a single thing, and a single thing only (extension of SRP to variables). In most cases, they should not be mutated to refer to anything else in the scope of a function. If you find yourself "assembling" a variable, such as your example with strings, that should be (as you said) moved into a different function. That is the "correct" way to do it, and a language feature demanding that you do things correctly is a good one, in my opinion.
Before I get to reasons, one alternative way to write your code could have been:
const message = isMorning() ? "Good Morning" : "Good Afternoon";
If you had multiple variables to set, you could simply have done:
const {message, otherThing} = isMorning()
? {message: "Good Morning", otherThing: 3}
: {message: "Good Afternoon", otherThing: 5}
Anyhow, reassigning variables is a pretty large code smell in general. Even though virtually all functions should always fit on screen without scrolling for readability and sanity's sake (if you don't do this already, I strongly encourage you to try: it'll change your life), reading a function is faster if you're certain that in no branches a variables definition changes.
If you're going with the assumption that it's OK to reassign variables, then you're adding the mental weight of scanning every branch of your function to see exactly what the variable is being used for at any given moment in the function (including closures which may execute far off into the future). For complex or long functions where you don't bother to do this, there is a potential that you've forgotten a variable re-assignment - which may lead you to writing buggy code.
It's all about assurances. For every "const" in a function you have one less thing to worry about: reducing the mental weight of understanding the code. THAT is a tangible benefit beyond performance and memory use.
Everything that can vary (counters, etc), should be defined with let. The reason for this is that block level scope is a fundamental tool in code readability. An inner block (such as a loop or conditional) should only mutate variables defined in itself, or its outer block. This is a natural way that most programmers understand code, as it creates a hierarchy of functionality. If two sibling blocks need to share a variable, that variable needs to be moved up into the parent block - making the relationship CLEAR to the reader and future maintainers.
The reason for this is pretty much exactly the reason for const: it reduces mental weight; giving you assurances that you didn't re-use a variable in a sibling block. Why should understanding the logic of block B require you to also have the logic of block A in your brain's L1 cache? Treating blocks like a contract is a good idea, and its an extension of well understood OO best practices to the function level.
Frankly, letting a block mutate their sibling's variables is like letting two functions mutate each other's locals, or two classes mutate each others private fields; which are both clearly unacceptable. Bock level scoping is the exact same concept.
It's not just a philosophy: it's all about readability, assurances, and encouraging you (and the maintainers) to do the right thing. If you try to do something like mutate a const or refer to a let from a sibling scope, that SHOULD make you wonder "hm, is this the best way I could write this code? Maybe I should promote this let binding up, or maybe I should refactor this calculation out into a function" or something similar.
Is it more work? Sure. Is the work worth it in the long run? Absolutely.
@Ben,
You said "Of course, it also seems like one of those things people should worry about when they have to worry about it (not necessarily before)."
The fact that these things have been introduced into the spec should tell you that people *have* had to worry about it in the past. Whether these new tools solve any of your existing problems is a personal matter, but the mere presence of tools designed to solve real problems that others have encountered shouldn't force you to reevaluate your own coding style if it works.
@Chris,
I will definitely agree that this stuff is all personal preference. I'm not saying that anyone should change the way they code or that I will change my approach because of the things other people like (heck, just look at my white-space usage :P ). If anything, I would encourage people *not* to change their existing preference until they have truly *thought* about and wrapped their head around the problems that new constructs actually solve. I guess my big fear is that things like Const and Let will simply be *cargo culted* into the JavaScript zeitgeist.
@Nelson,
I don't disagree with anything you are saying about writing clean code. I honestly really try to follow many if not most of all the principles that you describe. Another one that I think is great is the, "Principle of Least Surprise." Which, I think dove-tails with everything you are saying. If I have a variable and then it gets totally re-used for an unrelated purpose, that's very *surprising* and you probably shouldn't do it.
The real issue that I have here is that if you apply all of these good practices, then it seems the value-add of Const and Let are gone. Meaning, you probably write code in which Const, Let, and Var can all be interchanged (with some exception) and your code will just work... because you've already solved all of those problems by writing clean code in the first place.
And, if that's the case, I guess I just wonder why bother having three different types of variable assignment when you can have just one.
I personally am very excited about the arrival of let and const, although I feel that they've been named poorly (at least 'let' has). I'll run it down:
* let: This is valuable to me (and I'd assume anyone else) for whom Javascript is not their primary language because it makes variable scoping work the same way as in other languages. E.g.
var i = 5;
for (var i = 0; i < 3; i++) { console.log(i); }
console.log("Out of loop:", i);
Ask most programmers what the above code would output and I they're likely to reply "0, 1, 2, Out of loop: 5", when in fact it produces "0, 1, 2, Out of loop: 3'. This is obvious to people well-versed in JS but at least 'let' allows programmers to define function scope as they understand it.
* const: This one is more about the trend for programming to move towards a more functional & immutable style. When reading other people's code it becomes much easier to reason about the contents of a function when you know which variables will change and which will not. This is something which I have picked up from several years of programming in F# in which variables are immutable unless they are declared with the 'mutable' keyword modifier.
Nelson is spot-on here. Ben, you mentioned in an earlier comment:
> I think my capacity to think about the code is just not as robust as other people. If I'm looking at line 5 of a function, I honestly don't remember how a variable was declared on line 1. Maybe that's why I just don't connect with how this improves readability or the ability to reason.
The use of immutable variable references is entirely about easing the mental load of tracking what a function is doing. If a variable is used on line 5, but is mutated in 3 separate conditional branches* in lines 2, 3 and 4, then you have to mentally execute the function for every input to find out what the variable actually represents. However, if it's declared as a const, you just look at the declaration. Immutability means you don't NEED to keep variables on your "mental stack".
If you're not using conditional branches but are still mutating a variable, I'd question why. The whitespace example above was addressed by Chris Jones, but if your concern is readability then I'd still find this easier to follow and reason about:
const sanitizedValue = value
.replace( /^\s+/, "" ) // strip leading spaces
.replace( /\s+$/, "" ); // strip trailing spaces.
Even if you personally don't see the benefit, I'd consider it a courtesy to anyone reading your code to try and use immutability wherever possible. Think of it in the same way as the 'readonly' keyword in C# - it's not strictly ever necessary, but it does enhance the readability of code to know that "this value never changes once set".
(*) conditional branches in a function are hurt readability, and generally go hand-in-hand with mutability. Immutable variables tend to force you to break conditionals down into more functional constructs - see a for-loop vs a fold or map
Love Ya Brother, but I had to ;)
http://www.cutterscrossing.com/index.cfm/2015/11/18/Death-to-Var--Why-Let-and-Const-Really-Interest-Me-In-JavaScript
@Cutter,
I left a comment, but wanted to reiterate here that:
>> When you set a variable with const, you are assigning a variable to a specific location in memory.
... is probably the best explanation of "const" that I have read so far.
@Alex,
The reason why I think this is such a great example the problem:
const sanitizedValue = value
.replace( /^\s+/, "" ) // strip leading spaces
.replace( /\s+$/, "" ); // strip trailing spaces.
... is that, while this is readable, it still doesn't mean that you won't have to mess with it.
Imagine that the above code has a type, where you included a "space" before the "\s". And, you just don't see it the code. So, what might you do? Add a "debugger" or a "console.log()" statement between the two .replace() calls:
console.log( value );
const sanitizedValue = value
.replace( /^\s+/, "" ); // strip leading spaces
console.log( sanitizedValue );
sanitizedValue = sanitizedValue.replace( /\s+$/, "" );
console.log( sanitizedValue );
... of course, this breaks because you can't reassign the value. Now, you could simply change the variable declaration while debugging. Or, you could create and log an intermediary variable. Or, I'm sure do a number of other things to get around the const limitation.
But, I guess my point is, why bother even having a different variable declaration type IF it's ever going to add friction? And, of course, I can *personally* take this point of view because the type of variable declaration is not where my bugs come from - generally bad architecture and poor choices is where my bugs come from :D
I think we will only ever be able to agree to disagree on this matter.
@Richiban,
The only push-back I have against this line of thinking:
>> for whom Javascript is not their primary language....
... is, why is JavaScript the one that has to change? I mean, why not have other languages remove block-level scoping so that everyone is on the same page? Somehow, that seems crazy right? Which is how it seems to me to change JavaScript to match other languages.
Now, I'm not saying JavaScript shouldn't borrow good ideas from other languages - that's always nice. I'm just saying that having a feature in other languages doesn't mean that it is a value-add ... simply that it exists.
I understand all this post as "I am so used to var, why should I use something else"?
About the scope thing, most C-like languages use block-scoping (if not all aside JavaScript). It feels more correct. I prefer to have local-scoping instead of "global" scoping. Refactoring even becomes easier when the variables are as local as possible (you simply move an entire block to another function and everything stays working).
Why not change other languages to be more like JavaScript? Two reasons. JavaScript came after C, C++ and others... and because that particular scoping is more a source of problems than a source of solutions. Optimization wise, it is easier to optimize when you know that all variables declared in a block are gone at the end of the block (as a reader, it is also easier).
About the const, I think this is to go in the direction of functional languages. Again, it is related to making it easier to reason about something.
You used the example of the sanitizedValue... but it could be done with many different consts, like:
const sanitizingQuotes = ...;
const sanitizingSomethingElse = ...;
const sanitizedValue = theFinalSanitization;
@Paulo,
Re this:
>>> I understand all this post as "I am so used to var, why should I use something else"?
.... think it's more like, "var is not a source of problems for me, so why would I change my approach?"
I honestly don't see this causing a problem for people. I think the bigger source of problems is *lexical binding* because people often don't understand why a value changes AFTER it has been passed into another context. I'm sure everyone in JavaScript has been bitten by something like:
for ( var i = 0 .... ) {
elements[ i ].onclick = function(){ alert( i ); };
}
... in which all the bindings end up alerting the last value of "i". Of course, this has nothing to do with block-scoping, but rather understanding lexical scoping.
My only point here is that, from what I usually see in my own bugs as well as many other bugs that I help people debug, the actual variable is rarely the cause of the problem but rather something more complex.
But, that's just my experience - your mileage may vary.
"var is not a source of problems for me, so why would I change my approach?"
If you are a solo developer, there's no compelling reason to do so - at least that comes up while you're around. If however you're working on a team, or on a codebase that won't always be yours, every use of const and let adds specificity and clarification of intent to the conversation you are having with those other (potentially unknown) developers with every line of code you write.
That extra detail is especially valuable when collaberating with less experienced (or even just differently experienced) developers. The "friction" they encounter when a const declaration interferes with what they want to do is their impetus to come and learn (probably from you, the original author) why they shouldn't be taking the information you established at the beginning of a function and mutating it half-way down that function. It is your opportunity to help them solve their problem through more appropriate refactoring, for example. And if you're working with developers who don't communicate well or enough, then you'll want even more friction. At least when code review comes around, there will be a variable changed from const to let or var, tripping your code smell sensor right from the start. The change may turn out to be appropriate, but reviewers will be on guard either way. Defensive driving keeps you safe on the road despite sharing it with drivers of highly varying caliber. Defensive programming works the same way.
It is my philosophy that programming languages should be tersely expressive and permissive rather than opinionated, to prioritize enabling good, elegant code over interfering with bad, sloppy code. That philosophy also carries into building libraries and frameworks. But in the granular implementation details of code for any kind of project it becomes the programmer's responsibility to present clear and focused intent - to control the scope of a problem space by laying down track that is intended to restrict.
All these scenarios (language design, libraries, APIs, protocols, frameworks, and end-use code) are just different scopes at which the Robustness Principle applies.
The code I write (regardless of scope) conforms to that principle in part by making the fewest assumptions about what other developers have done or might try doing, while minimizing the assumptions other developers might be able to make (or need to make) about what I have done or might yet do. In the end-use coding scenario especially, I even want to give as many hints as possible as to what I think they should do. To that end, const and let are handy tools of defensive programming.
Software development is a unique field where freedom is powered by restriction. I can do so much so easily in a relational database because of what referential integrity and column types don't let me do. If we only focus on the "permissive input" side of the robustness principle, we stray further and further from the structure and support that fuels high-velocity development. By the time problems start appearing in a project and slowing it down, they will already have taken deep roots in the codebase. If instead we keep a close eye on maintaining and even reinforcing the restrictive structures around us, we can achieve even greater velocities that only come from well-calibrated focus universally held throughout a larger team.
Simply put, the value of let and const are rooted in the inherent merit of increased specificity. That the specificity is then enforced by the language itself is the language obeying the robustness principle on both sides - being permissive by letting you optionally declare more in your code, and restrictive by enforcing what you declare if you do. Using let and const is a tool by which you can increase trust in your own code regardless of what other developers are working in the same codebase.
Skipping the costs of maintaining restrictive output can give short term gains, and if your team is really good, those gains will even hold up for a while. But any project which lives long enough will eventually pay the piper. Programming languages are among the most well-equipped tools in our stack to come up with new ways to defer that payment or even subsidize it. I personally cannot wait for ES7's async/await, not because it'll enable me to write code better (promises are a pretty solid solution, and generator-based coroutines do the trick in terms of controlling flow), but because it'll enable me to communicate better through code. It's not about what I can do, but how clearly I can explain what I'm doing.
@Ben,
How about this, for the first case:
const sanitizedValue = value => {
// Then, strip off all leading spaces.
value = value.replace( /^\s+/, "" );
// Also, strip off all trailing spaces.
value = value.replace( /\s+$/, "" );
};
As I think, it's still has detailed explanation and wouldn't throw any errors.
"Imagine that the above code has a type, where you included a "space" before the "\s". And, you just don't see it the code." -
I think that the semicolons is the best way to show where the line ends, and I can't understand why is every developer of some syntactic-sugar-things like CoffeScript, wants to get rid of it. :)
@Evan,
What a well thought out and written post. If you have a blog you should post a link so we can subscribe - and if you don't have a blog you should start one!
Personally, I disagree.
As a freelancer, I had to repair quite a bit of broken code from other programmers (mostly PHP, but also JS) during the last decade, and certain patterns always kept repeating themselves.
One pattern that I keep seeing (and that causes a lot of trouble), is one of your examples:
if ( isMorning() ) {
var message = "Good morning";
var somethingElse = 'a value';
} else {
var message = "Good afternoon";
}
// -additional code-
Seeing that block of code as example, made me cringe (and write this comment).
Why? Well, because (again, PHP mostly) it so happened that the previous developer sometimes started using 'somethingElse' further down the line. The problem? Well, sometimes, it wasn't defined, because the if/else block went a different path (usually, on -very- rare occasions!). I've seen published Magento or WordPress plugins with this, causing errors because that "rare" case caused some variables to not be defined.
Now, how does "let" play into this? Obviously, it forces you to define the variables outside of the if/else statement:
let somethingElse;
if( ... ) {
let message = 'Good Morning';
// ...
} else {
let message = 'Good afternoon';
// ...
}
Of course, var can do the same - but with var, you wouldn't get a "message is not defined" ReferenceError when suddenly using 'message' outside of it's block. Now, I'm really focusing on this choice of coding, since I'm sure most other aspects have been mentioned already.
I realize that this is very dependent on personal coding style - but by my experience, this kind of error/oversight isn't as rare as I'd like it to be. Also, when working in a team, it's just simply easier to see ahead of time what kind of variables are going to be involved, instead of going through two or more complicated blocks to check if something was defined or not. Another advantage of enforcing this style of coding is: Now, you can actually add comment what "message" is - once ;)
Yes, basically, the main feature I like in regards of let / const, is the enforcement of certain patterns and the additional, visual information. If I see a const, I -know- that the programmer who wrote it, did not intend for the variable to be changed. That's an information I wouldn't have with 'var'.
It's almost comparable to public/protected/private: They enforce certain behavior, mainly to coordinate with other developers. Otherwise, you could just use "_somePrivateMethod" all the time and just hope nobody later decides to violate that rule for whatever reason, instead of refactoring the necessary functionality.
In my one person opinion I disagree about var for life. Just after starting using const and let I started liking const especially. It makes code so much more easy to read and predictable. By default I use const everywhere I can but before using LET I think twice. I like the effort of using let as rarely as possible. I know that var has better performance so I'll compile to ES5 using closure compiler to get performance of VAR but predictability of CONST and necessary evil LET.
@Ben
In your previous comment you mention a common js gotcha:
for ( var i = 0 .... ) {
elements[ i ].onclick = function(){ alert( i ); };
}
This actually does change with the current standard for let. If var is replaced with let then i will refer to a different instance of i for each iteration of the loop, unlike with var, and the event will alert the intended number.