Variables in JavaScript are lexically scoped. But, I wonder, is the this
keyword, referring to the receiver of a method, an example of dynamic scoping. Or is this
unrelated to the lexical/dynamic scoping discussion?
var foo = { x = "how am I scoped?" }
function bar() {
console.log(this.x) // the free variable this is decided by the calling context, like I see dynamic scoping described
}
foo.bar()
2 Answers 2
Dynamic scope seems to imply, and for good reason, that there's a model whereby scope can be determined dynamically at runtime, rather than statically at author-time. That is in fact the case.
Let's illustrate via code:
function foo() {
console.log( a ); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
Lexical scope holds that the RHS reference to a
in foo()
will be resolved to the global variable a
, which will result in value 2 being output.
Dynamic scope, by contrast, doesn't concern itself with how and where functions and scopes are declared, but rather where they are called from. In other words, the scope chain is based on the call-stack, not the nesting of scopes in code.
So, if JavaScript had dynamic scope, when foo()
is executed, theoretically the code below would instead result in 3 as the output.
function foo() {
console.log( a ); // 3 (not 2!)
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
How can this be? Because when foo()
cannot resolve the variable reference for a
, instead of stepping up the nested (lexical) scope chain, it walks up the call-stack, to find where foo()
was called from. Since foo()
was called from bar()
, it checks the variables in scope for bar()
, and finds an a
there with value 3.
Strange? You're probably thinking so, at the moment.
But that's just because you've probably only ever worked on (or at least deeply considered) code which is lexically scoped. So dynamic scoping seems foreign. If you had only ever written code in a dynamically scoped language, it would seem natural, and lexical scope would be the odd-ball.
To be clear, JavaScript does not, in fact, have dynamic scope. It has lexical scope. Plain and simple. But the this
mechanism is kind of like dynamic scope.
The key contrast: lexical scope is write-time, whereas dynamic scope (and this!) are runtime. Lexical scope cares where a function was declared, but dynamic scope cares where a function was called from.
Finally: this
cares how a function was called, which shows how closely related the this
mechanism is to the idea of dynamic scoping.
-
2This is confusing values and variables.
this
cares cares how a function is called the same way parameters care how a function is called - they get their value at runtime when the function is called. But the scope of all variables (includingthis
) is lexical and not dynamic.JacquesB– JacquesB08/22/2019 06:55:12Commented Aug 22, 2019 at 6:55 -
Clear explanation! (The link is broken)AliN11– AliN1104/11/2021 11:49:54Commented Apr 11, 2021 at 11:49
No, this
is lexically scoped to a function, just like a function parameter. It basically works like an implicit parameter.
Dynamic scoping means that if a variable is defined in a function, it is also visible inside functions called from this function (and functions called from those functions and so on).
This is not what happens with this
though. Each function invocation have its own this
binding which is set at the time of the function invocation (just like regular parameter values are set at the function invocation). The this
of the calling function is not visible for the called function, so there is no dynamic scoping. (When a function is called without dot-notation, this
is just set to global
or undefined
.)
A useful way of thinking of this
is as a hidden parameter, like this:
Actual syntax: What happens conceptually:
function bar() { } -> function bar(this) {}
foo.bar(); -> bar(foo);
bar(); -> bar(undefined);
So this
is basically a syntactic trick to pass the stuff left of the dot as a parameter. But it doesn't affect scoping, and isn't anymore "dynamic" or "runtime" than regular parameters.
And just to dispel any doubt - the ECMAScript spec is clear that this
is lexically scoped:
8.3.2 GetThisEnvironment ( )
The abstract operation GetThisEnvironment finds the Environment Record that currently supplies the binding of the keyword this. GetThisEnvironment performs the following steps:
- Let lex be the running execution context’s LexicalEnvironment.
Repeat
- a. Let envRec be lex’s EnvironmentRecord.
- b. Let exists be envRec.HasThisBinding().
- c. If exists is true, return envRec.
- d. Let outer be the value of lex’s outer environment reference.
- e. Let lex be outer.
NOTE The loop in step 2 will always terminate because the list of environments always ends with the global environment which has a this binding.
Source: http://www.ecma-international.org/ecma-262/6.0/#sec-getthisenvironment
-
I think adding an example on what happens with
const b = foo.bar; b();
would be usefulgcali– gcali08/22/2019 17:02:06Commented Aug 22, 2019 at 17:02 -
@gcali: Callig b() is equivalent to calling bar() in the example. It doesn't matter that the function is also attached to an object, it only matters if it is called using "dot" notation or not.JacquesB– JacquesB03/31/2020 08:43:16Commented Mar 31, 2020 at 8:43
-
Sure, that's the reason I'm saying that adding the example would be useful; I think it could help clarify the subject. Something like
const b = foo.bar; b(); -> bar(undefined);
. Just my two cents!gcali– gcali05/28/2020 07:16:28Commented May 28, 2020 at 7:16 -
@JacquesB Maybe .call() and .apply() worth mentions for "dot-like" behaviour? and .bind() as "currying" this approach?Nikita Shchypyplov– Nikita Shchypyplov11/25/2020 10:51:55Commented Nov 25, 2020 at 10:51
this
is more like an invisible function argument. It is not an example of dynamic scoping. Languages that feature dynamic scoping include shell/Bash and Perl via thelocal
keyword, and a huge amount of template languages.