Scope Chain in JavaScript

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:

  1. Global - visible by everything
  2. Function - visible within a function (and its sub-functions and blocks)
  3. Block - visible within a block (and its sub-blocks)
  4. 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:

  1. How an identifier was declared
  2. Where an identifier was declared
  3. Whether you are in strict mode or non-strict mode

Some of the ways identifiers can be declared:

  1. var, let and const
  2. Function parameters
  3. Catch block parameter
  4. Function declarations
  5. Named function expressions
  6. Implicitly defined properties on the global object (i.e., missing out var in non-strict mode)
  7. import statements
  8. eval

Some of the locations identifiers can be declared:

  1. Global context
  2. Function body
  3. Ordinary block
  4. The top of a control structure (e.g., loop, if, while, etc.)
  5. Control structure body
  6. 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 loads Foo 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 and bar share the method speak() which belongs to Foo.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 of foo, then prototype chain to get speak()?

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



Leave a reply



Submit