The first example is not “terrible”, that’s just how lexical scope works. I don’t really see the point of complaining about language features like this - either learn how it works or ignore at your peril.
Considering anything that transpiled to ES5 would have to use var anyway, I'm curious why this was done in the source itself and not as a plugin/build step.
Indeed, `let`s and `const`s incur a significant performance penalty. This is also why the Scala.js compiler emits `var`s by default, even when targeting very recent versions of ECMAScript.
The good news is that we can still write our Scala `val`s and `var`s (`const` and `let`) in the source code, enjoying good scoping and good performance.
And here I was thinking I should finally embrace using const and let for performance reasons .. shouldn't in theory the compiler has more room for optimization if it knows the variable won't be changed? Or is apperently all the scope checking more expensive?
Need some help understanding what’s going on here.
In
function example(measurement) {
console.log(calculation); // undefined - accessible! calculation leaked out
console.log(i); // undefined - accessible! i leaked out
<snip>
Why does the author say `calculation` and `i` are leaking? They’re not even defined at that point (they come later in the code), and we’re seeing “undefined” which, correct me if I’m wrong, is the JS way of saying “I have no idea what this thing is”. So where’s the leakage?
I've tried to get V8 at least to implement smarter TDZ elision for a long time, which would eliminate the need for these shenanigans.
There are some relatively simple heuristics where you can tell without escape analysis that a variable will not be referenced before initialization.
The obviously bad constructions are references in the same scope that happen before the declaration. It'd be nice if these were an early errors, but alas, so keep the TDZ check. The next is any closed over reference that happens before the initializer. These may run before the initializer, so keep the TDZ check. Then you have hoisted closures even if they're after the initializer (eg, var and function keyword declarations). These might run before the initializer too.
But everything else that comes after the initializer: access in the same or nested scope and access in closures in non-hoisted declarations, can't possibly run before the initializer and doesn't need the TDZ check.
I believe this check is cheap enough to run during parsing. The reason for not pursuing it was that there wasn't a benchmark that showed TDZ checks were a problem. But TypeScript showed they were!
It feels like the root of the issue is the scoping design of JS itself, which makes tracking TDZ more costly for the interpreter, and the fact that JS is JIT rather than AOT compiled.
I laud the recent efforts to remove the JS from JS tools (Go in TS compiler, esbuild, etc), as you don't need 100% of your lang utils written in the same interpreted lang, especially slow/expensive tasks like compilation.
The article is slightly wrong. The TDZ is the zone before the variable is declared, not after. Referencing variables before they're declared isn't valid for `let` and hence it needs a TDZ check.
Consider
console.log(foo)
let foo
vs
console.log(foo)
var foo
I think the article confuses "in scope" with "declared", and "declared and initialised" with "initialised".
I wish they had made lexical scope work like Lua, where the binding simply does not exist before the declaration – including in the initialization expression:
local x = 42
do
print(x) -- 42
local x = x * 2
print(x) -- 84
end
print(x) -- 42
13 comments
[ 4.3 ms ] story [ 23.5 ms ] threadI can always rely on FAANGs to make things unnecessarily confusing and ugly.
The good news is that we can still write our Scala `val`s and `var`s (`const` and `let`) in the source code, enjoying good scoping and good performance.
In
Why does the author say `calculation` and `i` are leaking? They’re not even defined at that point (they come later in the code), and we’re seeing “undefined” which, correct me if I’m wrong, is the JS way of saying “I have no idea what this thing is”. So where’s the leakage?There are some relatively simple heuristics where you can tell without escape analysis that a variable will not be referenced before initialization.
The obviously bad constructions are references in the same scope that happen before the declaration. It'd be nice if these were an early errors, but alas, so keep the TDZ check. The next is any closed over reference that happens before the initializer. These may run before the initializer, so keep the TDZ check. Then you have hoisted closures even if they're after the initializer (eg, var and function keyword declarations). These might run before the initializer too.
But everything else that comes after the initializer: access in the same or nested scope and access in closures in non-hoisted declarations, can't possibly run before the initializer and doesn't need the TDZ check.
I believe this check is cheap enough to run during parsing. The reason for not pursuing it was that there wasn't a benchmark that showed TDZ checks were a problem. But TypeScript showed they were!
I laud the recent efforts to remove the JS from JS tools (Go in TS compiler, esbuild, etc), as you don't need 100% of your lang utils written in the same interpreted lang, especially slow/expensive tasks like compilation.
Consider
vs I think the article confuses "in scope" with "declared", and "declared and initialised" with "initialised".