links:


Lookup is mostly Conceptual

The concept of lookup an identifier(variable) in it’s scope or any of it’s chain of parent scope (upward) is conceptual. By the time program reaches runtime, JS engine contains all the necessary information about identifiers, so it can avoid lookup.

The scope of identifiers in a program is immutable and this (meta) information is known from compilation. Engine doesn’t need to lookup bunch of scopes to determine the from where the variable comes from. Avoiding the need for a runtime lookup is the benefit of lexical scope. The runtime performs more performantly without spending time on all those lookups.

There are scenarios where scope is not determined during compilation.

In JS each file is it’s separate program. Consider this scenario where a reference variable is not found in all lexically available scope in a file. If no declaration is found that’s not necessarily an error. That variable scope is marked as not determined. Another file(program) in the runtime may indeed declare that variable in the shared global scope.

So the ultimate determination of whether the variable was ever appropriately declared in some accessible bucket may need to be deferred to the runtime.

Any reference to a variable that’s initially undeclared is marked as uncolored(not determined) during that file’s compilation. This color cannot be determined until the relevant files have been compiled and the application runtime commences. That deferred lookup will eventually resolve to whichever scope the variable is found.

Shadowing

When a variable is created in a current scope that is of same name on outer scope, the declared variable will shadow the outer scope variable and you cannot access the outer scope variable in the current scope

For example:

var currencySymbol = "$";
 
function showMoney(amount) {
  var currencySymbol = "€";
  console.log(currencySymbol + amount);
}
 
showMoney("100");

In this example, the currencySymbol inside showMoney function shadows the currencySymbol at the window scope. Which means, you cannot access the window scope currencySymbol inside showMoney function

Global unshadowing trick

NOTE: Do not use this trick in any of the programs

It is possible to access the global variable from a scope where that variable is shadowed, but not through a typical lexical identifier reference.

In the global scope var declarations and function declarations also expose themselves as properties (of the same name as the identifier) on the global object — essentially an object represents global scope.

Consider this program

var studentName = "Suzy";
 
function printStudent(studentName) {
    console.log(studentName);
    console.log(window.studentName);
}
 
printStudent("Frank");
// "Frank"
// "Suzy"

The studentName in printStudent function shadows the global studentName. This is the only way to access global variables.

This little trick only works only when accessing from global scope and also that are declared with var and function

Other forms of global scope declarations doesn’t create mirrored global object properties

var one = 1;
let two = 2;
const three = 3;
class Four {}
 
console.log(window.one)   // 1
console.log(window.two)   // undefined
console.log(window.three) // undefined
console.log(widnow.Four)  // undefined

Variables that exist in any other scope than the global scope are completely inaccessible from the scope where they’ve been shadowed

var special = 42
 
function lookingFor(special) {
	// The identifier `special` (parameter) in this
    // scope is shadowed inside keepLooking(), and
    // is thus inaccessible from that scope.
	
	function keepLooking() {
		var special = 3.14;
		console.log(special)
		console.log(window.special)
	}
	
	keepLooking()
}
 
lookingFor(129318281)
// 3.14
// 42

In above example, the global special is shadowed by the function lookingFor special and lookingFor special itself is shadowed by the keepLooking special. We can still access global specail using window.special but there is no way of accessing lookingFor special inside keepLooking scope.

Copying is not Accessing

Sometimes we can think of copying as accessing a variable but both are different. Consider this modified example of the above one

var special = 42;
 
function lookingFor(special) {
    var another = {
        special: special
    };
 
    function keepLooking() {
        var special = 3.141592;
        console.log(special);
        console.log(another.special);  // Ooo, tricky!
        console.log(window.special);
    }
 
    keepLooking();
}
 
lookingFor(112358132134);
// 3.141592
// 112358132134
// 42

In this example, inside the lookingFor function we are copying the special argument into another object, which can be changed in the life of the function. Here we cannot access the special argument, only can copy it to something else. Here you are copying the special variable to another container because, you cannot reassign special with something else

Illegal Shadowing

Not all combinations of shadowing are allowed. let can shadow var, but var cannot shadow let

function someThing() {
	var special = "JavaScript"
	{
		let special = 42 // totally fine shadowing
	}
}
 
function another() {
	let special = "JavaScript"
	{
		var special = 42
		// Syntax Error
	}
}

Even though the Syntax error is misleading, The real reason it’s raised as Syntax Error is because the var is trying to “cross the boundary” of let declaration of the same name which is not allowed.

The boundary-crossing prohibition effectively stops at each function boundary, so this variant raises no exception

function another() {
	let special = "JavaScript"
	ajax("https://some.url", function callback() {
		// Totally fine shadowing
		var special = 42
	})
}

Function name Scope

A function declaration looks like this

function makePizza() {
	// ...
}

The above function declaration will create an identifier in the enclosing scope. In this case global scope named makePizza

What about this program?

var makePizza = function() {
	// ...
}

The same is true for the variable makePizza being created. But since it’s function expression, — so the function is used as value instead of a standard declaration. (the function itself will not hoist)

The major difference between function declaration and function expression is what happens to the name identifier of the function. Consider a named function expression

var makePizza = function fromDominos() {
	// ....
}

The makePizza ends up in the outer scope. But what about fromDominos. For formal declaration functions, the name identifier ends up in the outer/enclosing scope, so it may be reasonable that’s the case here. But fromDominos is declared as an identifier inside the function itself

function makePizza = function fromDominos() {
	console.log(fromDominos)
}
 
makePizza()
// fromDominos function
 
console.log(makePizza)
// ReferenceError: makePizza is not defined

Not only makePizza is declared inside the function rather than outside, it’s also defined as read-only

function makePizza = function fromDominos() {
	"use strict";
	fromDominos = 10; // TypeError
}
 
makePizza()

The assignment failure is reported because of strict mode, but in non strict mode it silently fails with no exception.

What about when a function expression has no name identifier?

function makePizza = function() {
	// ...
}

The function expression with a name identifier is called “named function expression”, but one without a named identifier is called as “anonymous function expression”. Anonymous function expressions clearly have no name identifier that affects the scope.

Arrow Functions

Arrow functions are introduced in ES6, as additional function expression form to the language

The arrow functions has same lexical scope as regular functions. Also Arrow functions are lexically anonymous, meaning they don’t have directly related identifiers that references the function.

const sayHi = () => {
	// ...
}
 
sayHi.name // sayHi

The assignment to sayHi creates an inferred name of “sayHi”, but that’s not the same thing as non anonymous

The main difference between regular and arrow function is being non declarative form and anonymous.

Well both function forms have same lexical scope and creates new bucket of scope.

Backing Out

When a function (expression or declaration) is defined, a new scope is created. The positioning of scopes nested inside one another creates a natural scope hierarchy throughout the program, called the scope chain. The scope chain controls variable access, directionally oriented upward and outward.

Each new scope offers a clean slate, a space to hold it’s own set of variables. When a variable name is repeated at different levels of the scope, shadowing occurs which prevents the access to the outer variable from that point inward.


tags: ydkjs , javascript