Currently, all function and class declarations are made with var
scope - that is to say, they can be accessed above or below their definition[1]. This, along with var
itself, means that a parser can't know what an unknown identifier refers to until it has finished parsing the entire file.
Well-written modern ECMAScript code generally eschews the use of var
, for this and other reasons, but the only alternative to var
-scoped function declarations is to use function expressions, instead:
function varScopedFunction() {}
let letScopedFunction = function() {}
const constScopedFunction = function() {}
This is a programmer-unfriendly syntax, and (perhaps largely because of this) it isn't often used. How about, instead, a syntax that allows specifying the binding type as part of the declaration:
let function letScopedFunction(); // forward declaration
let function letScopedFunction() {}
const function constScopedFunction() {}
let class letScopedClass;
let class letScopedClass {}
const class constScopedClass {}
While classes can't be used before their declaration, they can be referenced in earlier parts of the file, as long as those references aren't immediately-evaluated. The let
and const
forms of the class (and function) declarations, on the other hand, cannot be referenced in earlier parts of the source file, which is why the forward-declared forms (with no function/class body or class heritage clause, and ending with a semicolon) exist. There is no forward-declared form of the const
declaration, as there is no forward-declared form of const
variables, either.
These syntaxes offer only slight utility to the developer (it allows for declaring functions/classes that can't be reassigned, without using a function expression assigned to a const
variable), but it offers a significant advantage to a parser: these forms, like let
and const
variable declarations themselves, can be differentiated from classic var
-scoped function declarations as early as scan time, even before parse time. This opens the door to a possible fast-path parsing mechanism that, like asm.js, utilizes a syntactically-restricted form of ECMAScript. It might be declared as follows:
// As part of directive prologue, one per statement according to spec
"use_strict"; // should be first in case an engine doesn't follow spec
"use_forward_declarations";
// As a Parser Augmentation directive, multiple per statement, order-agnostic
syntax "strict", "forward_declarations";
// As a PA directive, explicitly marking "forward_declarations" as optional
syntax "strict", from ["forward_declarations", null];
An engine might see the forward_declarations
directive and switch to a parse mode that makes early judgments about identifiers found in the source text, speeding up parsing considerably; if it then encounters a var
declaration or a var
-scoped function or class, or if it encounters an import
statement anywhere after the module prologue, it could throw a SyntaxError or perhaps restart the parse in non-forward-declarations mode. This can't be done today partly because (a) there is currently no syntax for forward declarations of function or class bindings, and (b) the amount of work the parser would have to do to verify that a forward_declarations
directive is accurate would negate any benefits from being able to do one-shot parsing.
Even if programmers don't use these forms directly, a smart transpiler could convert bare function and class declarations to their let
-scoped variants when set to a target environment with a high-enough language version to support them, inserting forward declarations wherever necessary. In this way, it would provide a middle ground between existing ECMAScript (requires out-of-order parsing) and a full-on Binary AST implementation (cannot be parsed at all except by engines with explicit BinAST support).
A class cannot be accessed before its definition, temporally speaking, but it can be accessed above its definition, lexically speaking. ↩︎