Every script runs in its own engine. The default engine has the name "main" (as revealed by $.engineName) and is not supposed to preserve data and lexical entities beyond the execution of the script. That's why this special workspace is also referred to as the non-persistent, implicit engine.

You can also set up a persistent engine using the formal directive #targetengine "myEngineName", provided that "myEngineName" != "main". Your script will then recover, from one execution to another, its lexical scope, assigned data and references as you left them. That's the most popular way to save and restore settings within an InDesign session.

Note. - Persistent does not mean permanent. We sometimes use the term “session-persistent” to indicate the contrast with “application-persistent” storage. Examples of application-persistent entities are custom menu structures, labels managed through app.insertLabel() / app.extractLabel(), env strings accessed using $.setenv() / $.getenv(), not to mention file-based solutions.

Given the engine your script is running into, assume that anything declared or done outside of an explicit function body is processed from an implicit, global, unnamed function that encompasses the entire program. (The jsxbin encoding shows that things are handled exactly that way.) What this hidden function determines is the top-level lexical scope, also referred to as the [[global]] scope.

Declared vs. Undeclared Variables

Most identifiers declared in the [[global] scope are reflected into the $.global object, but there are crucial rules to know, especially since they operate in a pretty similar way at lower lexical levels.

The top rule is that every assignment or use of some undeclared identifier involves or calls upon the [[global] scope. You can think of it as the highest-level namespace. Hence you need const… or var… to specifically apply a scope restriction to the current function body — including inner function(s) when a closure or similar feature is involved.

Note. - Another technique, widely used in the IdExtenso framework, relies on the use of formal parameters, i.e. function arguments, working as pre-declared identifiers. I will not discuss the advantages of this approach here.

Now, here is the second fundamental rule: contrary to appearances, the line

var x = 123;
 

embeds two completely separate instructions in time and space. Here is what the interpreter actually understands:

// At the top of the current function body
// and before any processing.
// ---
var x; // => Appends the identifier in the scope (value: undefined)
 
// At the exact location where the code
// “var x = 123;” appears.
// ---
x = 123; // => Changes x's value to 123.
 

Hence, when you're working within the [[global]] scope's hidden function body, a subtle distinction exists between

var x = 123;
 

and

x = 123;
 

In both cases the variable is ‘globally’ known. However, it is known to exist at any point of your code only in the first case, because the declaration “var x;” was implicitly parsed prior to any assignment, making $.global.x already registered (and set to undefined).

That's why the following code doesn't fail:

alert( x ); // => undefined
var x = 123; 
 

while this one throws a runtime error:

alert( x ); // => ERROR: "x is undefined"
x = 123;
 

See the difference? Thanks to the var keyword, $.global.hasOwnProperty('x') is true in the first case, false in the 2nd case. This example is a bit silly when taken in isolation, but think about what is implied in large-scale programs with nested functions and so.

Note. - This example also shows that the word undefined is extremely ambiguous. There are identifiers that are technically undefined and will cause errors if invoked, whereas others are technically defined even though they hold the primitive value undefined.

Function Declaration vs. Function Expression

Along the same lines, a function declaration, as opposed to a function expression, is registered early in the scope (although not yet parsed). To quote the ECMAScript standard, “at the top level of a function, or script, function declarations are treated like var declarations”.

That's why the below code perfectly works:

myFunc(); // OK
function myFunc(){ alert("hello") }
 

while this one does not:

myFunc(); // ERROR: myFunc is not a function
var myFunc = function(){ alert("hello") };
 

Analyze carefully the origin of the runtime error in the second case: the identifier myFunc is declared upstream due to the ‘var...’ prefix. Hence it is registered as undefined in the lexical scope until further assignment. At this stage, no function is formally known so the statement myFunc(); just fails as would do undefined().

The deep difference between case 1 and case 2 is that the pattern “… = function(){…}” forcibly introduces a function expression. Whenever the function(){...} syntax is nested in a larger JavaScript statement (assignment or any operation), it is no longer a genuine function declaration. It becomes a functional expression.

Note. - These rules are extensively documented in the ECMA-262 spec. ExtendScript complies with the 3rd edition, 1999, but the core principles of scope management remain unchanged today.

Anonymous and Named Functions

Intrinsically, a function is a callable object (type Function) which has a fixed name property. An anonymous function is, in all and for all, a Function instance whose name has been automatically set to "anonymous" because of the functional expression it emanates from. Although this characterizes function expressions over function declarations (the latters require syntactic naming), this is not connected to closure, self-execution, and similar phenomena.

A function name is a read-only property of the Function object. If not "anonymous", that name becomes a self-identifier within the lexical scope of the function. In ExtendScript, any function (incl. anonymous) can also point to itself using the callee reserved keyword from within its own body.

Naming a function remains possible with function expressions. Compare the two expressions below:

( function(){ alert(callee.name) } )(); // => 'anonymous'
( function myFunc(){ alert(callee.name) } )(); // => 'myFunc'
 

The second expression is a source of confusion for beginners, because it looks deceptively like a function declaration, which it is not. Remember: a function declaration MUST specify a name which, as an identifier, gets available to the outer function scope; a function expression MAY specify a name which, as an identifier, gets only available to the inner function scope.

A slightly more convoluted syntax is sometimes used:

const myRef = function myFunc(){/*...*/};
 
// or:
 
$.myProp = function myFunc(){/*...*/};
 

In the above code, myRef and $.myProp bear outer references to a function whose internal name is “myFunc” (not “myRef” or “MyProp”.) Such lexical subtleties are specific to functional expressions.

In the case of regular function declarations, the name property and the lexical identifier seem to play the same role, though they are in fact of quite a different nature. From within the body of a named function, its name-as-identifier takes precedence over any externally-scoped identifier:

// Function decl. → ‘myFunc’ is a [[global] identifier.
// ---
function myFunc(){ alert( myFunc.name ) }
 
// Variable decl. → ‘tmp’ is another [[global]] identifier...
// ---
var tmp = myFunc; // ...now pointing to myFunc&
 
// Now the identifier myFunc is re-assigned
// ---
myFunc = { name:"something else" };
 
// But executing the function (via tmp)
// still shows the original name:
// ---
tmp(); // => "myFunc" :-)
 

If you understand everything happening in the code above, you have fully grasped the concepts of both the lexical identification of an object and the transfer of references via identifiers. These are fundamental concepts that are often poorly understood and inadequately explained.

• Further reading:
IdExtenso $$.Settings module (the annotated BACKGROUND and NOTICE sections shed light on various aspects of scope management in an InDesign script.)