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.
function f() {
return x; // Syntax parsing fails here.
}
let x = 4;
return f();
you would get the equivalent of a ReferenceError for x when f() tried to use it (well, refer to it) at the commented line. But in JavaScript, this successfully returns 4, because `let` inherits the weird hoisting behavior of `var` and `function`. And it has to, because otherwise this would be really weird: function f1() { return x; }
let x = 4;
function f2() { return x; }
return Math.random()
Would that have a 50/50 chance of returning the outer x? Would the engine have to swap which x is referred to in f1 when x gets initialized?TDZ is also terrible because the engines have to look up at runtime whether a lexical variable's binding has been initialized yet. This is one reason (perhaps the main reason?) why they're slower. You can't constant fold even a `const`, because `const v = 7` means at runtime "either 7 or nothing at all, not even null or undefined".
In my opinion, TDZ was a mistake. (Not one I could have predicted at the time, so no shade to the designers.) The right thing to do when introducing let/const would have been to make any capture of a lexical variable disable hoisting of the containing function. So the example from the article (trimmed down a little)
return Math.random()
would raise a ReferenceError for `useX`, because it has not yet been declared at that point in the syntactic scope. Same with the similar return Math.random()
which in current JavaScript also either returns 1 or throws a ReferenceError. I'm not against hoisting functions, and removing function hoisting would have not been possible anyway. The thing is, that's not "just a function", that's a closure that is capturing something that doesn't exist yet. It's binding to something not in its lexical scope, an uninitialized slot in its static environment. That's a weird special case that has to be handled in the engine and considered in user code. It would be better to just disallow it. (And no, I don't think it would be a big deal for engines to detect that case. They already have to compute captures and bindings.)Sadly, it's too late now.