ECMAScript proposal for allowing variable declarations inside conditional statements (e.g. if
/while
).
Authors:
- Devin Rousso
Stage: 1
When programming in C++, an extremely useful feature is to be able to declare a variable and have it be evaluated inside a conditional:
if (auto* ptr = getPtr()) {
/* ... */
}
Adding this capability to JavaScript would be very useful for the following reasons:
- avoid having to retype the variable name
- finer-grain "control" over the visibility of the variable
- allow authors to write performance-"safe" code without having to know the specific details of the code being called (see example below)
In the case of JavaScript, however, there should be some limitations:
- only using
let
andconst
- only for
if
andwhile
- destructuring is not allowed (even if only a single variable is "pulled out"), as there's potential confusion as to what's actually being checked (the value vs. whether the desired key/index was present in the object/array)
There would be no "new" syntax (it would be the same as declaring a single let
/const
variable, except without an ending semicolon) and the variable would only be visible inside the if
/while
block.
Here's an example of where allowing declarations in conditionals could be useful:
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
if (foo.data) {
for (let item of foo.data) {
/* A */
}
} else {
/* B */
}
could be replaced by
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
if (let data = foo.data) {
for (let item of data) {
/* A */
}
} else {
/* B */
}
which allows foo.data
to only have to be evaluated once, and is much more stylistically succinct.
One could create another variable (e.g. let data = foo.data;
), but that could potentially keep foo.data
(via data
) alive much longer than needed and would "pollute" the scope with an additional variable.
This can (mostly) be transpiled into
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
(() => {
{
let data = foo.data;
if (data) {
for (let item of data) {
/* A */
}
return;
}
}
/* B */
})();
or
class Foo {
get data() {
let result = [];
/* ... do some expensive work ... */
return result;
}
}
let foo = new Foo;
let __test = () => {
let data = foo.data;
if (!data)
return false;
for (let item of data) {
/* A */
}
return true;
};
if (!__test()) {
/* B */
}
but it wouldn't be exactly the same due to the fact that the transpiled code would change what the last evaluated value in the outer scope would be, thereby changing the evaluation result of the entire program.
This is likely not that big of an issue, however, as this is probably pretty rare (most code tends to be written inside functions, which don't use the last evaluation result as the returned value) and any author/transpiler could just "fall back" to what's currently available (declare the variable outside the conditional).
The bigger issue would be if any of the code inside A
(and/or B
, depending on the transpilation approach) has a return
, as that would need to be propagated outside the wrapper function, which may involve other workarounds (e.g. a Symbol
could differentiate between a generated value and a transpiled "path").
This could be extended to allowing multiple names to be initialized (such as through destructuring) with a "normal" conditional to be written after the assignment with a ;
(similar to a for
).
if (let x = 1, y = 2; x || y) {
/* ... */
}
if (let {x, y} = data; x && y) {
/* ... */
}