JS scope: static, dynamic, and runtime-augmented

The purpose of this note is to have a distinct, and exact terminology for different types of scopes used in JS, and in programming languages in general.
Audience: experienced engineers, professionals.

Many JavaScript developers know, that JS (as well as most of the existing today languages) uses static scope.

We have already discussed this topic in detail previously. If you’re not familiar with the concept of a scope in general, or don’t know well differences between static vs. dynamic scope, you may address these two articles:

And in this note, we’ll briefly show that JS has actually all three types of scope: static, dynamic, and runtime-augmented.

Static scope

Definition 1: Static scope: a language implements static scope, if only by looking at the source code one can determine in which environment a binding is resolved.

Technically static scope is implemented by the concept of closures. And vice-versa, closures can naturally be implemented in languages with static scope.

There might be other implementations of closures: e.g. when free variables are captured as private properties of a Closure-class instance, though it’s an implementation detail, and by the theoretical concept it’s still “capturing” of the lexical environment.

Example of static scope:

JavaScript static scope
NOTE: variable x for the print_x function is free, since it’s neither a parameter, nor a local variable of this function.

In the example above we see, that variable x is captured when print_x function is created. And local variable x of the run function doesn’t affect execution of the print_x function.

Definition 2: Static scope: technically static scope is naturally implemented by closures, through the mechanism of capturing free-variables in lexical environments.
NOTE: example above works in languages without closures as well — through only the global scope.

Dynamic scope

The dynamic scope specifically for variables is not widely used on practice today. A notable example might be Perl programming language, which has support for both scope types.

Definition 3: Dynamic scope: a language implements dynamic scope, if a caller defines an activation environment of a callee.
Perl static and dynamic scopes

In other words, a caller (not a callee as in static scope) defines values for the free variables used by a callee. We see the example from above resulting to value 20 when print_x function is called from the dynamic function, since the dynamic function provides own value for variable x.

One could argue that dynamic scope simply doesn’t make sense! And really, how can we implement predictable functions, if we don’t even know from which environments our functions will be executed in the future?

However, a practical purpose of the dynamic scope is not in predictability, but vice-versa — in creating generic interface functions, which provide just the implementation, and then can work from any environment as long as this environment provides values for needed variables.

And such dynamic bindings is actually something we still use in many languages today, even if might not noticing it. And JavaScript is not an exception — it’s the value of this.

Definition 4: This value: in JavaScript this value is dynamically scoped, unless used in an arrow function.

That’s correct: as we know, the value of this is determined and provided exactly by the caller.

JavaScript dynamic this-value

In this case we define a generic interface function produce, which works with any object, which implements x property. And all three callers provide a correct value for this, which is dynamic in this case.

NOTE: by this principle works OOP in most of the languages: a class is just a storage of methods, which are executed in needed context, working with dynamic this value.

Some languages, e.g. Python or Rust prefer passing an explicit self argument as the first parameter. In fact, dynamic scope for variables can be simply replaced by passing all the needed values as function arguments. That is, turning free variables into bound variables — through the parameters.

As we also mentioned, introduced in ES2015 arrow functions use lexical (i.e. static) scope, and capture this in the lexical environment, same as other variables.

Runtime-augmented scope

Definition 5: Runtime-augmented scope: happens when an activation frame is not statically determined, and can be mutated by the callee itself.

This type of scope in JavaScript is introduced by using with, and eval instructions. Both are discouraged to be used: the with is even deprecated, and eval in strict mode is executed in a sandbox environment; so let’s just quickly look at the example:

JavaScript runtime-augmented scope

That’s said, on practice try to avoid both, with, and eval (they also break all the optimizations!), however it’s still good to know that such type of scope exists.

To recap:

  • Static (lexical) scope is when function knows the resolution environment for free variables at time of creation.
  • Closures is a natural continuation of the static scope. One can say: “closure == static scope”.
  • Dynamic scope is when a caller provides the callee’s resolution environment at activation.
  • Runtime-augmented scope is when own activation environment is not determined at static time, and can be mutated by the callee itself.

In case you have any questions, I’ll be glad to answer, and discuss them in comments.