Javascript

The Esoteric Parts

By Philippe Poulard

Javascript

The Esoteric Parts

Not a complete course on Javascript

Just a focus on some of the counterintuitive parts of the language

Agenda

  • 5 shades of this
  • Variable scope
  • Variable hoisting
  • IIFE
  • Closure
  • Passing variable

5 shades of 'this'

The value of this depends on how a function is invoked.

Invoke as a function

function foo() {};
foo(); //=> this === window
var bar = function() {};
bar(); //=> this === window



Hey ! Did you notice the controls in the corner ? ↘︎

Invoke as a method

function foo() {
  return this;
}
// invoke as a function
foo(); //=> this === window
var bar = {
  'foo': foo
};
// invoke as a method of 'bar'
bar.foo(); //=> this === bar

Invoke as a constructor

function Foo() {
  this.bar = function() {
    return this;
  }
}
// exemple 1
var foo = new Foo();
console.log(foo); //=> Foo
console.log(foo.bar() instanceof Foo); //=> true

// exemple 2
var foo1 = Foo();
console.log(typeof foo1); //=> undefined
console.log(typeof window.bar); //=> function
					

Invoke by passing this

function foo() {
 console.log(this); //=> this === element
 console.log(arguments); //=> 'a', 'b', 'c'
}
var element = document.querySelector('#foo');
element.addEventListener('click', function(e){
 console.log(this); //=> element
 console.log(arguments); //=> e === Event
 foo.call(element, 'a', 'b', 'c');
});

or

  foo.apply(element, ['a', 'b', 'c']);

Invoke with binding

var Foo = function() {
  this.counter = 0;
  this.inc = function() {
    this.counter += 1;
    console.log(this);
  };
};

var foo = new Foo();
var element = document.querySelector('#foo');

// #1
element.addEventListener('click', foo.inc); //=> this === element
// #2
element.addEventListener('click', function(){ foo.inc(); }); //=> this === foot
// #3
element.addEventListener('click', foo.inc.bind(foo)); //=> this === foo

Bind example 2

ES3

var obj = {
  doIt: function() {},
  handle: function() {
    var that = this;
	document.addEventListener('click', function(e) {
	  that.doIt();
	});
  }
}

ES5

var obj = {
  doIt: function() {},
  handle: function() {
	document.addEventListener('click', function(e) {
	      this.doIt();
      }.bind(this)
	);
  }
}

Bind example in ES6

var obj = {
  doIt: function() {},
  handle: function() {
    document.addEventListener('click', (e) => this.doIt());
  }
}

Variable scope

What will be print ?

10

Variable scope

What will be print ?

1

Variable scope

What will be print ?

10

Variable scope

What will be print ?

You expect in other language 121

122

C-family languages have block-level scope.

Variable scope

What will be print ?

121

Javascript has function-level scope.

Variable hoisting

Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter.

In JavaScript, a name enters a scope in one of four basic ways:

  1. Language-defined: All scopes are, by default, given the names this and arguments.
  2. Formal parameters: Functions can have named formal parameters, which are scoped to the body of that function.
  3. Function declarations: These are of the form function foo() {}.
  4. Variable declarations: These take the form var foo;

Variable hoisting

This code :

function foo() {
	bar();
	var x = 1;
}

...is interpreted like this :

function foo() {
	var x;
	bar();
	x = 1;
}

Variable hoisting

The 2 following functions :

function foo() {
    if (false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x, y;
    if (false) {
        x = 1;
    }
    return;
    y = 1;
}

...are equivalent

IIFE

Every function, when invoked, creates a new execution context.

⇒ Allow to create privacy.

IIFE

var foo = function(){ /* code */ }
foo();

can we define a shorter way ?

function(){/* code */}(); // SyntaxError: Unexpected token (
function foo(){/* code */}(); // SyntaxError …
// no, because it is still a declaration

Curiously, this one works :

function foo(){ /* code */ }( 1 );

because it is really just equivalent to this:

// a function declaration …
function foo(){ /* code */ }
// … followed by a completely unrelated expression:
( 1 );

Immediately-Invoked Function Expression (IIFE)

Crockford recommends this one :

(function(){ /* code */ }());

But this one works just as well :

(function(){ /* code */ })(); 

When the parser already expect an expression, parens can be omitted

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

Closure

This doesn't work like you might think :

var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
  elems[ i ].addEventListener( 'click', function(e){
    e.preventDefault();
    alert( 'I am link #' + i );
  }, false );
}

Saving state with closure

Closure

var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
  (function( lockedInIndex ){
    elems[ i ].addEventListener( 'click', function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex + "/" + i );
    }, false );
  })( i );
}
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
  elems[ i ].addEventListener( 'click',
    (function( lockedInIndex ){
      return function(e){
        e.preventDefault();
        alert( 'I am link #' + lockedInIndex );
      };
    })( i ), false );
}

The module pattern

var counter = (function(){
  var i = 0;

  return {
    get: function(){
      return i;
    },
    set: function( val ){
      i = val;
    },
    increment: function() {
      return ++i;
    }
  };
}());

// `counter` is an object with properties,
// which in this case happen to be methods.
counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.i; // undefined

Passing variable

by reference / by value ?

An easy way to determine whether something is "pass by reference" is whether you can write a "swap" function.

For example, in C, you can do :

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *i = *j;
    *j = t;
}

If you can't do the equivalent of that in Javascript, it is not "pass by reference".

There is no "pass by reference" available in JavaScript.

References are passed by value.