I am interested if there are any limits to what types of values can be set using const
in JavaScript—in particular functions. Is this valid? Granted it does work, but is it considered bad practice for any reason?
const doSomething = () => {
...
}
Should all functions be defined this way in ES6? It does not seem like this has caught on, if so.
Thanks for any comments!
There's no problem with what you've done, but you must remember the difference between function declarations and function expressions.
A function declaration, that is:
function doSomething () {}
Is hoisted entirely to the top of the scope (and like let
and const
they are block scoped as well).
This means that the following will work:
doSomething() // works!
function doSomething() {}
A function expression, that is:
[const | let | var] = function () {} (or () =>
Is the creation of an anonymous function (function () {}
) and the creation of a variable, and then the assignment of that anonymous function to that variable.
So the usual rules around variable hoisting within a scope -- block-scoped variables (let
and const
) do not hoist as undefined
to the top of their block scope.
This means:
if (true) {
doSomething() // will fail
const doSomething = function () {}
}
Will fail since doSomething
is not defined. (It will throw a ReferenceError
)
If you switch to using var
you get your hoisting of the variable, but it will be initialized to undefined
so that block of code above will still not work. (This will throw a TypeError
since doSomething
is not a function at the time you call it)
As far as standard practices go, you should always use the proper tool for the job.
Axel Rauschmayer has a great post on scope and hoisting including es6 semantics: Variables and Scoping in ES6
Although using const
to define functions seems like a hack, but it comes with some great advantages that make it superior (in my opinion)
It makes the function immutable, so you don't have to worry about that function being changed by some other piece of code.
You can use fat arrow syntax, which is shorter & cleaner.
Using arrow functions takes care of this
binding for you.
example with function
// define a function
function add(x, y) { return x + y; }
// use it
console.log(add(1, 2)); // 3
// oops, someone mutated your function
add = function (x, y) { return x - y; };
// now this is not what you expected
console.log(add(1, 2)); // -1
same example with const
// define a function (wow! that is 8 chars shorter)
const add = (x, y) => x + y;
// use it
console.log(add(1, 2)); // 3
// someone tries to mutate the function
add = (x, y) => x - y; // Uncaught TypeError: Assignment to constant variable.
// the intruder fails and your function remains unchanged
It has been three years since this question was asked, but I am just now coming across it. Since this answer is so far down the stack, please allow me to repeat it:
Q: I am interested if there are any limits to what types of values can be
set using const in JavaScript—in particular functions. Is this valid?
Granted it does work, but is it considered bad practice for any
reason?
I was motivated to do some research after observing one prolific JavaScript coder who always uses const
statement for functions
, even when there is no apparent reason/benefit.
In answer to "is it considered bad practice for any reason?" let me say, IMO, yes it is, or at least, there are advantages to using function
statement.
It seems to me that this is largely a matter of preference and style. There are some good arguments presented above, but none so clear as is done in this article:
Constant confusion: why I still use JavaScript function statements by medium.freecodecamp.org/Bill Sourour, JavaScript guru, consultant, and teacher.
I urge everyone to read that article, even if you have already made a decision.
Here's are the main points:
Function statements have two clear advantages over [const] function
expressions:
Advantage #1: Clarity of intent
When scanning through
thousands of lines of code a day, it’s useful to be able to figure out
the programmer’s intent as quickly and easily as possible.
Advantage #2: Order of declaration == order of execution
Ideally, I want to declare my code more or less in the order that I
expect it will get executed.
This is the showstopper for me: any value declared using the const
keyword is inaccessible until execution reaches it.
What I’ve just described above forces us to write code that looks
upside down. We have to start with the lowest level function and work
our way up.
My brain doesn’t work that way. I want the context before the details.
Most code is written by humans. So it makes sense that most people’s
order of understanding roughly follows most code’s order of execution.
There are some very important benefits to the use of const
and some would say it should be used wherever possible because of how deliberate and indicative it is.
It is, as far as I can tell, the most indicative and predictable declaration of variables in JavaScript, and one of the most useful, BECAUSE of how constrained it is. Why? Because it eliminates some possibilities available to var
and let
declarations.
What can you infer when you read a const
? You know all of the following just by reading the const
declaration statement, AND without scanning for other references to that variable:
- the value is bound to that variable (although its underlying object is not deeply immutable)
- it can’t be accessed outside of its immediately containing block
- the binding is never accessed before declaration, because of Temporal Dead Zone (TDZ) rules.
The following quote is from an article arguing the benefits of let
and const
. It also more directly answers your question about the keyword's constraints/limits:
Constraints such as those offered by let
and const
are a powerful way of making code easier to understand. Try to accrue as many of these constraints as possible in the code you write. The more declarative constraints that limit what a piece of code could mean, the easier and faster it is for humans to read, parse, and understand a piece of code in the future.
Granted, there’s more rules to a const
declaration than to a var
declaration: block-scoped, TDZ, assign at declaration, no reassignment. Whereas var
statements only signal function scoping. Rule-counting, however, doesn’t offer a lot of insight. It is better to weigh these rules in terms of complexity: does the rule add or subtract complexity? In the case of const
, block scoping means a narrower scope than function scoping, TDZ means that we don’t need to scan the scope backwards from the declaration in order to spot usage before declaration, and assignment rules mean that the binding will always preserve the same reference.
The more constrained statements are, the simpler a piece of code becomes. As we add constraints to what a statement might mean, code becomes less unpredictable. This is one of the biggest reasons why statically typed programs are generally easier to read than dynamically typed ones. Static typing places a big constraint on the program writer, but it also places a big constraint on how the program can be interpreted, making its code easier to understand.
With these arguments in mind, it is recommended that you use const
where possible, as it’s the statement that gives us the least possibilities to think about.
Source: https://ponyfoo.com/articles/var-let-const
There's another scenario where a constant function might be useful. If you have lots of constants in your code and need a function that specifically operates on those constants, it might be a good idea to turn that function into a constant itself:
const FLAG_ONE = 1;
const FLAG_TWO = 2;
const FLAG_THREE = 4;
// etc.
// resolves flag into string for debugging purposes:
const FLAG_NAME = flag => {
switch ( flag ) {
case FLAG_ONE: return 'one';
// etc.
}
};
It's not necessary in any way to define FLAG_NAME as a constant, but it will improve the legibility of your code to do so.
For my experience, I would suggest to use inheritance in ES6:
class Animal {
constructor(_type) {
this._type = _type;
}
speak() {
console.log(this._type === 'dog' ? 'Grruuu' : 'Meowww');
}
}
class Dog extends Animal {
constructor() {
super('dog');
}
set speak(value) {
return super.speak;
// or throw any exception message here...
// throw new Error('This method cannot be overriden.');
}
get speak() {
return (...args) => super.speak.call(this, ...args);
}
}
class Cat extends Animal {
constructor() {
super('cat');
}
set speak(value) {
return super.speak;
}
get speak() {
return (...args) => super.speak.call(this, ...args);
}
}
let dog = new Dog();
dog.speak();
let cat = new Cat();
cat.speak();
cat.speak = 'foo';
cat.speak(); // Meowww