Scope & Closures
 "this"

Presenter: Yauheni Pozdnyakov

 t.me/youhy

We'll talk about...

  • Scope and closures
    Compiler, lexical scope, functions, hoisting

  • This or That?
    Binding

Compiler view

JS is compiled language!

It is not compiled in advance, as C++, nor are the results of compilation portable among various distributed systems as Java or C# do

Compilation steps:

  • Tokenizing/Lexing
    var a = 2 should be presented as var, a, =, 2 and ;
  • Parsing (Abstract Syntax Tree)
  • Code-Generation

Any snippet of JavaScript has to be compiled before (usually right before!) it's executed

Scope: the entry point

  • Engine
    Start-to-finish compilation and execution of JavaScript code
  • Compiler
    Handles code files parsing and code-generation
  • Scope
    holds a lookup list of the declared identifiers, enforces a strict set of rules of how these are accessible to currently executing code.

Scope: the entry point

var a = 2;

Compiler:

  • Gets var a
  • Checks scope if a is presented there
  • If so, no need to declare it
  • If not, scope should allocate the variable first
    Declare a new variable called a for that scope collection
  • Pass code to Engine for later execution (a = 2)

Engine:

  • Gets the code to handle
  • Perform a scope lookup if a is accessible
  • If so, use this variable
  • If not, look for it through the upper level

Left and Right hand lookup

Side???  What side???

Of an assignment operation!

 RHS look-up - a look-up of the value of some variable, like "go get the value of.." (who's the source of the assignment )

LHS look-up is trying to find the variable container itself, so that it can assign (who's the target of the assignment)

console.log( a ); //RHS

var a = 2;//LHS
function foo(a) {
	console.log( a ); // 2
}

foo( 2 );
  • foo(..) - RHS
  • Hidden a = 2, so LHS
  • console.log(a) //  2 RHS

Left and Right hand lookup

function foo(a) {
	var b = a;
	return a + b;
}

var c = foo( 2 );

3 -  LHS look-ups

4 - RHS look-ups

LHS:

c =...
a = 2
b = ..

RHS:

foo(2..

 = a;

 a + ..

 .. + b

Nested Scope

function foo(a) {
	console.log( a + b );
}

var b = 2;

foo( 2 ); // 4

RHS reference for b cannot be resolved inside the function foo!

What happens?

  • Check the foo Scope for b with RHS
  • No b :((
  • Check the outer Scope for b with RHS
  • Ha-Ha b is there!!!

Where do ReferenceError and TypeError come from?

function foo(a) {
	console.log( a + b );
	b = a;
}

foo( 2 );
function foo(a) {
        b = a;
	console.log( a + b );
}

foo( 2 );

ReferenceError: b is not defined

4

  • RHS lookup for b the first time
  • b is not found
  • results in a ReferenceError
  • LHS lookup for b the first time
  • comes to the top of the Scope
  • creates new one in the global Scope

RHS lookup - succeeded! But you're trying to perform something impossible - TypeError

Conclusion

  • Scope is the set of rules that determines where and how a variable (identifier) can be looked-up.
  • The JavaScript Engine first compiles code before it executes
  • Both LHS and RHS reference look-ups start at the currently executing Scope
  • ReferenceError is Scope resolution-failure related, but TypeError shows that Scope resolution was successful, but there was an  attempt to perform an illegal action against the result.

Lexical Scope

lexical scope is scope that is defined at lexing time

Block and Function Scope

function foo(a) {
	var b = 2;

	// some code

	function bar() {
		// ...
	}

	// some more code

	var c = 3;
}

 

bar(); // fails

console.log( a, b, c ); // also fails

Block and Function Scope

var a = 2;

function foo() {

	var a = 3;
	console.log( a );

} 
foo();

console.log( a ); 
// 2
var a = 2;

(function IIFE(){

	var a = 3;
	console.log( a ); 

})();

console.log( a ); 
// 3
// 2
var a = 2;

(function IIFE( global ){

	var a = 3;
	console.log( a ); 
	console.log( global.a ); 

})( window );

console.log( a ); 
// 3
// 2
// 2

IFFE: Immediately Invoked Function Expression

Hoisting

a = 2;

var a;

console.log( a );

//2

var a;

a = 2;

console.log( a );
console.log( a );

var a = 2;

//undefined

var a; //undefined

console.log( a );

a = 2;
foo();

function foo() {
	console.log( a ); // undefined

	var a = 2;
}
foo(); // not ReferenceError, but TypeError!

var foo = function bar() {
	// ...
};
var foo; //undefined
foo(); // not ReferenceError, but TypeError!

foo = function bar() {
	// ...
};

Functions Hoist First

foo(); // 1

var foo;

function foo() {
	console.log( 1 );
}

foo = function() {
	console.log( 2 );
};
function foo() {
	console.log( 1 );
}

foo(); // 1

foo = function() {
	console.log( 2 );
};
foo(); // 3

function foo() {
	console.log( 1 );
}

var foo = function() {
	console.log( 2 );
};

function foo() {
	console.log( 3 );
}

Closures

Closure is all around you in JavaScript, you just have to recognize it

function foo() {
	var a = 2;

	function bar() {
		console.log( a );
	}

	return bar;
}

var baz = foo();

baz(); 
// 2

bar() still has a reference to that scope, and that reference is called closure.

Closure lets the function continue to access the lexical scope it was defined in at author-time.

Closures

var fn;

function foo() {
	var a = 2;

	function baz() {
		console.log( a );
	}

	fn = baz; // assign `baz` to global variable
}

function bar() {
	fn(); // look ma, I saw closure!
}

foo();

bar(); // 2

Closures

function CoolModule() {
	var something = "cool";
	var another = [1, 2, 3];

	function doSomething() {
		console.log( something );
	}

	function doAnother() {
		console.log( another.join( " ! " ) );
	}

	return {
		doSomething: doSomething,
		doAnother: doAnother
	};
}

var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

This or That

 This is contextual based on the conditions of the function's invocation

Depends on the manner in which the function is called

When a function is invoked, an activation record (execution context) is created. It contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. 

This call side

function baz() {
    


    console.log( "baz" );
    bar(); 
}

function bar() {
    


    console.log( "bar" );
    foo(); 
}

function foo() {
    


    console.log( "foo" );
}

baz(); 
// call-stack is: `baz`
// so, our call-site is in the global scope
// <-- call-site for `bar`
// call-stack is: `baz` -> `bar`
// so, our call-site is in `baz`
// <-- call-site for `foo`
// call-stack is: `baz` -> `bar` -> `foo`
// so, our call-site is in `bar`
// <-- call-site for `baz`

This binding

function foo() {
	console.log( this.a );
}

var a = 2;

foo(); // 2

default binding for this applies to the function call

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2,
	foo: foo
};

var bar = obj.foo; // function reference

var a = "oops, global"; // `a` also property on global object

bar(); // "oops, global"

un-decorated call, the default binding applies

Explicit Binding

bind(..), call(..) and apply(..) methods

function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

foo.call( obj ); // 2

From point of this binding, call(..) and apply(..) are identical. They do behave differently with their additional parameters.

function foo(something) {
	console.log( this.a, something );
	return this.a + something;
}

var obj = {
	a: 2
};

var bar = foo.bind( obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

bind(..) returns a new function that is hard-coded to call the original function with the this context set as you specified.

How to determine this?

  • Is the function called with new (new binding)? If so, this is the newly constructed object.

    var bar = new foo()

  • Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.

    var bar = foo.call( obj2 )

  • Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object.

    var bar = obj1.foo()

  • Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the globalobject.

    var bar = foo()

Conclusion

  1. Called with new? Use the newly constructed object.

  2. Called with call or apply (or bind)? Use the specified object.

  3. Called with a context object owning the call? Use that context object.

  4. Default: undefined in strict mode, global object otherwise.

Determining the this binding for an executing function requires finding the direct call-site of that function.

Any questions?

THANK YOU FOR ATTENTION!!!

Scope & Closures

By Yauheni Pozdnyakov

Scope & Closures

  • 130