Scope, the most important thing in JavaScript

Updated

cope and scope chain, the most important mechanism in JavaScript, are barely clearly explained though specified in ECMA-262 version 5.1. Without scope mechanism, there wouldn’t be closure and functional programming. This post is aim to elaborate what happens behind from the very beginning when control enters the global code to execution end.

Firstly, for easy literal, let’s agree on the following abbreviation: GE: Global Environment VE: Variable Environment LE: Lexical Environment OLE: Outer Lexcial Environment Reference ER: Environment Record EC: Execution Context go: global object (window for browser, global for NodeJS)

Take following foo.js as example, let’s dive into it line by line

// foo.js
var a = 1;
function Foo(b) {
	b = 3;
	console.log(b); // 3
	console.log(a); // 1
}
console.log(a);
Foo(2);
// step 1
GlobalEC: {
	VE: { 
		ER: { window: go }
		OLE: null
	},
	LE: {
		ER: { window: go }
		OLE: null
	}
	this: window
}
// step 2
GlobalEC: {
	VE: { 
		ER: { window: go, a: undefined, Foo: foo }
		OLE: null
	},
	LE: {
		ER: { window: go, a: undefined, Foo: foo }
		OLE: null
	}
	this: window
}

The tricky part is when control scans to Foo’s function declaration, it will create a new function object foo and associate it with Foo. The common internal properties and special internal properties for function will be assigned to foo. One of these internal properties is [[scope]], from here the scope chain mechanism starts. foo.[[scope]] is set to current EC.LE

// step 3
GlobalEC: {
	VE: { 
		ER: { window: go, a: undefined, Foo: foo }
		OLE: null
	},
	LE: {
		ER: { window: go, a: 1, Foo: foo }
		OLE: null
	}
	this: window
}
// step 4
FooEC: {
	VE: { 
		ER: null
		OLE: foo.[[scope]] // GlobalEC.LE
	},
	LE: {
		ER: null
		OLE: foo.[[scope]] // GlobalEC.LE
	}
	this: window
}
// step 5
FooEC: {
	VE: { 
		ER: {b: 2}
		OLE: foo.[[scope]] // GlobalEC.LE
	},
	LE: {
		ER: {b: 2}
		OLE: foo.[[scope]] // GlobalEC.LE
	}
	this: window
}
// step 5
FooEC: {
	VE: { 
		ER: {b: 2}
		OLE: foo.[[scope]] // GlobalEC.LE
	},
	LE: {
		ER: {b: 3}
		OLE: foo.[[scope]] // GlobalEC.LE
	}
	this: window
}

When control comes to console.log(b), it will first find the variable b in LE.ER and value 3 is retrieved. When control comes to console.log(a), it will first try to find the variable a in LE.ER. Unluckily , this time there is no a to be found, the control keeps finding a in LE.OLE which is GlobalEC.LE, finally a is found in GlobalEC.LE.ER with value bound to 1.


ecma-262 5.1 sec-13.2

ecma-262 5.1 sec-10.4.3