This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the architecture category.
Last Updated: 2024-11-21
I found myself perenially confused about JS scoping of this
and realized that one key in the puzzle is to understand lexical vs. dynamic scoping.
Lexical: the caller
(where the function is defined) sets the scope of bindings (i.e. values assigned to variables)
Dynamic: the callee
(where the function is called) sets the scope of bindings
See the perl code below for an example
x = 10;
sub print_x {
# In lexically scoped code, this ($x) is always set to 10 since the free variable
# was equal to 10 in the context where it was defined. However, as we'll see,
# this can vary when dynamic scoping is in use
print $x;
}
sub static {
# NB: my creates a lexical variable.
my $x = 20; # now this setting to 20 doesn't affect $x in the subsequent call
# to print_x()
print_x(); # 10, not 20
}
static();
sub dynamic {
# the effect of local, technically, is to save the old value on a stack when
# entering the scope with the local statement, and upon exiting, it will restore
# the old value
local $x = 20; # affects!
print_x(); # 20, not 10!
}
dynamic();
this
in JavaScript:JavaScript does not have dynamic scope (WRT to where names and variables are
looked up). However the this
keyword has a dynamic scope-like mechanism. It
cares about where a function was called.
function produce() {
console.log(this.x);
}
const alpha = {produce, x: 1};
const beta = {produce, x: 2};
const gamma = {produce, x: 3};
console.log(
alpha.produce(), // 1
beta.produce(), // 2
gamma.produce(), // 3
);
As you can see, the produce
function takes a different x
value depending on
which object it is part of. It is dynamically scoped to the object received.
This makes sense actually and goes to the core of how instances of an object
work (they use "generic" methods that have access to a localized state)
Compare to the following, which uses arrow functions to use lexical scope
// With an arrow function this within the function is the same as whatever this was
// outside the function when it was created. It is not bound to obj in your
// example, but instead to whatever it was already bound to where that obj is being
// created. - https://stackoverflow.com/questions/48295265/lexical-scope-in-javascript
const produce = () => {
console.log(this.x);
}
const alpha = {produce, x: 1};
const beta = {produce, x: 2};
const gamma = {produce, x: 3};
console.log(
alpha.produce(), // undefined
beta.produce(), // undefined
gamma.produce(), // undefined
);
Here, this
is called at the top-level, despite the receiver objects (e.g.
alpha.produce()
. Since x
isn't defined there, we get undefined.
Python makes this potential confusion super explicit by having a self
argument as the first parameter when calling instance vars on objects.