Scope Chain in Javascript
To understand the scope chain you must know how closures work.
A closure is formed when you nest functions, inner functions can refer to the variables present in their outer enclosing functions even after their parent functions have already executed.
JavaScript resolves identifiers within a particular context by traversing up the scope chain, moving from locally to globally.
Consider this example with three nested functions:
var currentScope = 0; // global scope
(function () {
var currentScope = 1, one = 'scope1';
alert(currentScope);
(function () {
var currentScope = 2, two = 'scope2';
alert(currentScope);
(function () {
var currentScope = 3, three = 'scope3';
alert(currentScope);
alert(one + two + three); // climb up the scope chain to get one and two
}());
}());
}());
Recommended reads:
- JavaScript Closures
- Closures
What is scope chain in JavaScript?
The scope chain is simply the locations where identifiers are declared that is searched to resolve the value of an identifier.
It's important to distinguish where something is declared vs. when something is declared. Just because you declare q
right before a function call that uses q
doesn't mean that that is the q
that will get used - there can be multiple q
variables, each existing it their own scope.
Before we look at your examples, imagine this scenario. You are in a large room, divided up into cubicles big enough for two workers. You and your co-worker "Joe" share one cubicle (since you are there, that is your local scope and you share it with Joe). If you and Joe were both sitting in your shared cubicle at the same time and you wanted to talk to Joe, you'd just say "Hey Joe" and Joe would immediately respond. But, what if Joe gets up to get some water at the office water cooler? If you then said, "Hey Joe", he wouldn't be found in your small cubicle scope, so you'd have to widen your search to the entire office (a higher scope that contains the scope your cubicle is in). But, by widening your search to the next higher scope (going up the scope chain), you'd eventually find Joe. Now imagine that the water cooler is shared between two offices on the same floor. You wouldn't find Joe in your cubicle, he wouldn't be in your office, so you'd have to once again widen your search to include other scopes visible to you (the office next door).
Still with me? Now, here's a wrinkle... What if there were different "Joe's" in each of those locations and you the Joe you wanted to talk to was in the other office? When you say "Hey Joe!", the closest one would reply, but that may not be the Joe you had in mind. This is what's happening with your q
variable. You have more than one declared at different scope levels and the one you expect isn't the closest one to the scope you are calling it from.
function a(){ // q is declared in this scope and this is the next highest scope to where it is // used for the console.log, so 1 is the output var q = 1; function b(){ // q isn't declared in this function's scope, so the next higher scope // needs to be checked. console.log(q); } b();}
// Even though this code runs just before the function call,// this is creating a different "q" variable in the higest scope// -- the global scope. When the function runs, it won't need// to search all the way up to this level to find a declaration// for q, so 2 isn't what is outputtedvar q= 2;
// Now, run the functiona();
What is the scope of variables in JavaScript?
TLDR
JavaScript has lexical (also called static) scoping and closures. This means you can tell the scope of an identifier by looking at the source code.
The four scopes are:
- Global - visible by everything
- Function - visible within a function (and its sub-functions and blocks)
- Block - visible within a block (and its sub-blocks)
- Module - visible within a module
Outside of the special cases of global and module scope, variables are declared using var
(function scope), let
(block scope), and const
(block scope). Most other forms of identifier declaration have block scope in strict mode.
Overview
Scope is the region of the codebase over which an identifier is valid.
A lexical environment is a mapping between identifier names and the values associated with them.
Scope is formed of a linked nesting of lexical environments, with each level in the nesting corresponding to a lexical environment of an ancestor execution context.
These linked lexical environments form a scope "chain". Identifier resolution is the process of searching along this chain for a matching identifier.
Identifier resolution only occurs in one direction: outwards. In this way, outer lexical environments cannot "see" into inner lexical environments.
There are three pertinent factors in deciding the scope of an identifier in JavaScript:
- How an identifier was declared
- Where an identifier was declared
- Whether you are in strict mode or non-strict mode
Some of the ways identifiers can be declared:
var
,let
andconst
- Function parameters
- Catch block parameter
- Function declarations
- Named function expressions
- Implicitly defined properties on the global object (i.e., missing out
var
in non-strict mode) import
statementseval
Some of the locations identifiers can be declared:
- Global context
- Function body
- Ordinary block
- The top of a control structure (e.g., loop, if, while, etc.)
- Control structure body
- Modules
Declaration Styles
var
Identifiers declared using var
have function scope, apart from when they are declared directly in the global context, in which case they are added as properties on the global object and have global scope. There are separate rules for their use in eval
functions.
let and const
Identifiers declared using let
and const
have block scope, apart from when they are declared directly in the global context, in which case they have global scope.
Note: let
, const
and var
are all hoisted. This means that their logical position of definition is the top of their enclosing scope (block or function). However, variables declared using let
and const
cannot be read or assigned to until control has passed the point of declaration in the source code. The interim period is known as the temporal dead zone.
function f() {
function g() {
console.log(x)
}
let x = 1
g()
}
f() // 1 because x is hoisted even though declared with `let`!
Nested Function scope chain in javascript
Every time you call a function, you create a new scope for that invocation of the function.
Any variables defined (with var
, function declarations, or as a named parameter) in that function are scoped to that function.
function foo (bar) { };
foo(1);
foo(2);
In the above, two scopes are created. In one of them, bar
is 1
in the other bar
is 2.
If a function is defined in another function, then it has access to the scope it was created in.
function foo (bar) { return function () { console.log(bar); } };
var foo1 = foo(1);
var foo2 = foo(2);
The function assigned to foo1
has access to the scope where bar
is 1
.
The function assigned to foo2
has access to the scope where bar
is 2
.
Scope chain look-up vs prototype look-up - Which is when
Variables are looked up on the scope chain, starting with the current execution context and going up the tree of enclosing execution contexts.
Properties are looked up firstly on the base object, then on that object's [[Prototoype]]
chain (i.e. its internal prototype).
So if you do:
foo
foo will be treated as a variable and looked up on the scope chain. Variable names are never qualified, you can't direct them to a specific execution context to be looked up in. If there are two variables on the scope chain with the same name, you can only access the one that is first encountered when going along the chain (there is a way around this specifically for global variables), e.g.
var a = 'global a';
function foo() {
var a = 'local a';
return a;
}
console.log(foo()); // local a
In the above, a within the function resolves to the local variable a. In the case of global variables, they are made properties of the global object so you can access them even if they are "shadowed" by a same named local property, e.g.
function foo() {
var = 'local a';
// this will reference the global object
return this.a;
}
console.log(foo()); // global a
In contrast, property names are always preceded by a base object on which they are looked up (as in the example above, where this references the global object), e.g.
foo.bar
will be split into foo and bar. Firstly, foo will be resolved on the scope chain and, if found, property resolution will try to find a bar property. So for properties, you can direct which object the property is looked up on. So if there are two objects with a same named property, you can look up both properties as long as both objects are in scope.
So the first part of any reference is treated as a variable, the subsequent parts are treated as properties. Except when with is used, but that is discouraged. Don't go there.
But for completeness… with places the specified object on the start scope chain so that variables are first looked up as properties of that object before using the scope chain, so you can do:
var cos = function(arg){return 'my cos function: ' + arg};
function foo() {
// cos is resolved on the scope chain
console.log(cos(0.5)); // my cos function: 0.5
with (Math) {
// cos is first resolved as a property of Math, and only on the
// scope chain if not found there
console.log(cos(0.5)) // 0.8775825618903728
}
}
foo();
JavaScript: understanding scope chain
Yes this is because the variables declared with var
have function scope and they are hoisted to the top of the function
. It logs undefined
due to hoisting.
Variables declared with let
have block scope so until the if
block is not executed the variable name
is not declared.
Even if you change it with if(true)
it will not work because the variable will only be available inside the block. {}
not outside.
Here is the example how let
works.
if(true){ let x = 3}console.log(x);
Javascript prototype chain vs scope chain
In which stage is prototype object aka
Foo.prototype
created? When the interpreter loadsFoo
to global scope?
Yes, the prototype object is created when the Foo
function is created.
In which lexical scope is its reference stored? (since there's no such ref in visualiser)
In none at all. It's stored only in a property of Foo
.
Should
foo
andbar
share the methodspeak()
which belongs toFoo.prototype
rather than owning their own copies as shown in visualiser?
Yes. It seems like that visualiser was built for Python and does not support prototype links at all.
Are prototype chain and scope chain unrelated? For example, when
foo.speak()
is called, first we trace scope chain to get value offoo
, then prototype chain to getspeak()
?
Yes, yes.
Notice however that the visualiser you found does not display the scope chain, it only does display the call stack, and does very bad at visualising lexical scope and closures correctly.
Related Topics
How to Filter JSON Data in JavaScript or Jquery
How to Stop a Window.Setinterval in JavaScript
How to Remove Only the Parent Element and Not Its Child Elements in JavaScript
Validate That a String Is a Positive Integer
Which Browsers Support Import and Export Syntax for Ecmascript 6
Browser Event When Downloaded File Is Saved to Disk
Check If Option Is Selected with Jquery, If Not Select a Default
Google Maps JavaScript API Referernotallowedmaperror
Jquery - Get Text for Element Without Children Text
Convert Time Interval Given in Seconds into More Human Readable Form
Returning a Value from Callback Function in Node.Js
Normalizing Mousewheel Speed Across Browsers
Canvas Image Crossplatform Insecure Error
Getting Error "Form Submission Canceled Because the Form Is Not Connected"
Jqgrid: Change Background Color of Row Based on Row Cell Value by Column Name