Web & Mobile Development JLW288

01.Advanced.JS

JavaScript?

What is the world's most popular language?

Yeah, JavaScript!

Java(Script)?

JavaScript is to Java as Ham is to Hamster
Dixit Jeremy Keith, Cartoon by Brad Colbow

JavaScript: History

  • Scripting language originally developed for Netscape 2.0
    • First known as Mocha or as LiveScript, later on renamed to JavaScript
  • Internet Explorer 3 included its own implementation, named JScript
    • Implemented due to gaining popularity of JavaScript
    • Named differently to avoid trademark issues
  • Eventually standardized in the ECMA-266 specificiation, known as ECMAScript

ECMAScript Dialects

  • Several languages/implementations are based on (some version of) ECMAScript
    • Adobe's ActionScript 3 (Flash, Flex)
    • JavaScript as we know it today
    • ...
  • Officially, JavaScript is maintained by Mozilla nowadays.
    • Current version JavaScript 1.8.5
    • Non-Mozilla implementations claim “JavaScript Compliance” but actually target the ECMAScript standard.

JavaScript Engines

JavaScript 101 with Forrest Gump

Forrest Gump

Because Forrest Gump is one of those movies ...

JavaScript 101

Robert Nyman
Photo by Måns Sandström

... and because Robert Nyman made an awesome presentation about it, which formed the starting point for these slides.

Whilst I'm at it

Mathias Bynens
Photo by Addy Osmani

Another thank you goes out to Mathias Bynens (2nd one from the left) who gave some handy remarks.

JavaScript 101: Variables

Life is like a box of chocolates

Life is like a box of chocolates,
you never know what you're gonna get

Before we dive in

  • Variables have a name and a value
  • JavaScript is case sensitive
    • firstName and firstname are not the same!
  • JavaScript is dynamically typed
    • When declaring a variable, you cannot specify the type. JavaScript will guess the data type when assigning a value
    • viz. a variable given the value 3 will be a number
  • JavaScript is weakly typed
    • JavaScript will adjust the type of a var when a new value is assigned
      • This phenomenon is known as implicit type conversion or coercion
    • viz. if that same variable is changed to "Hello" it'll become a string

Declaring variables (1)

  • Use the var keyword and assign a value to a name
    var firstName = 'Forrest';
    alert(firstName);
  • Remember: JavaScript is dynamically typed and weakly typed
    // Dynamic typing
    var firstName = 'Forrest';
    alert(typeof firstName); // string
    
    // Weak typing
    firstName = 3;
    alert(typeof firstName); // JavaScript has changed the type to number

Declaring variables (2)

  • It's possible to declare multiple vars at once.
  • Just separate them by a comma
    var firstName = 'Forrest',
        lastName = 'Gump';
    
    alert(firstName);
    alert(lastName);

JavaScript Data Types (1)

  • JavaScript supports these Primitive Data Types
    • string
    • number
      • No real distinction for float/double/int!
    • boolean
    • null
      • empty value
    • undefined
      • variable does not exist

JavaScript Data Types (2)

  • What about Array? And Date? And ...?
    • Well, those are Objects really
  • And oh, next to string you also have something like String
    • The former is the data type
    • And the latter is ... an Object
  • And then you have functions
    • Those are functions
    • ... but JavaScript treats them as First Class Objects

Not Sure If ...

Not Sure If ...

Let me explain on the next few slides ;)

Global Objects and Literals (1)

  • In JavaScript nearly everything is built upon Object
    • Object is the core of the language
    • With it, you can declare anything you want if you extend upon it.
  • JavaScript provides some global objects such as String, Number, Boolean, Array, etc.
    • Derived from Object
    • Define a structure to store something in
    • Instances have some properties
      • eg. myString.length; (Also noted as String#length)
    • Instances have some methods (to manipulate/access the stored value)
      • eg. myString.toUpperCase(); (Also noted as String#toUpperCase)

Global Objects and Literals (2)

  • Normally, one would do something like this
    var firstName = new String('Forrest');
    • What you get back is an instance of String
    • The type of the instance itself is object
    • The type of the value inside that instance is string
  • Let's pull out some code to test this
    var firstName = new String('Forrest');
    alert(firstName instanceof String); // true
    alert(typeof firstName); // object
    alert(typeof firstName.toString()); // string

Global Objects and Literals (3)

  • But next to that syntax, JavaScript also provides something named the literal notation
  • Literals allow you to immediately assign a value to a variable
    var firstName = 'Forrest';
    • What you get back is the value
    • The type of the value is string (dynamic typing in action)
  • Again, some code
    var firstName = 'Forrest';
    alert(firstName instanceof String); // false
    alert(typeof firstName); // string

Still Not Sure?

Not Sure If ...

It's not that hard, really!

Global Objects and Literals (4)

  • You know, this literal thing isn't new, you've been doing it all along; in Java for example:
    Integer num = new Integer(3); // Not Literal
    Integer num = 3; // Literal
  • JavaScript provides literals for:
    • String
    • Number
    • Boolean
    • RegExp
    • Function (*)
    • Array
    • Object
(*) Function is the odd one here (see further)

Global Objects and Literals (5)

  • Example (1)
    var firstName = new String('Forrest'); // String Constructor
    var firstName = 'Forrest'; // String Literal
    
    var birthYear = new Number('1945'); // Number Constructor
    var birthYear = 1945; // Number Literal
    
    var isRunning = new Boolean('false'); // Boolean Constructor
    var isRunning = false; // Boolean Literal
    
    var hasR = new RegExp('r', 'i'); // RegExp Constructor
    var hasR = /r/i; // RegExp Literal
    
    var says = new Function('sentence', 'alert(sentence)'); // Function Constructor
    var says = function(sentence) { alert(sentence); }; // Function Literal
    function says(sentence) { alert(sentence); }; // The function statement

Global Objects and Literals (6)

  • Example (2)
    var forrestFriends = new Array('Bubba', 'Lieutenant Dan');
    var forrestFriends = ['Bubba', 'Lieutenant Dan']; // Array Literal
    
    var forrest = new Object();
    forrest.firstName = 'Forrest';
    forrest.lastName = 'Gump';
    
    var forrest = { // Object Literal
    	firstName : 'Forrest', // semi-colons to assign
    	lastName : 'Gump',
    	says : function() { // works with functions too!
    		return 'Stupid is as stupid does';
    	} // no trailing comma (!)
    };

Global Objects and Literals (7)

  • Remember that if it exists as a primitive data type (viz. string, number, boolean), you'll get the primitive returned when using the literal notation!
    // String Instance
    var lastName = new String('Gump');
    alert(typeof lastName); // object
    alert(lastName instanceof String); // true
    
    // String Literal
    var firstName = 'Forrest';
    alert(typeof firstName); // string
    alert(firstName instanceof String); // false

Global Objects and Literals (8)

  • If no primitive variant exists (Object, Array, RegExp, Function), the literal will return an instance when using the literal notation.
    // Array Instance
    var forrestFriends = new Array('Bubba', 'Lieutenant Dan');
    alert(typeof forrestFriends); // object
    alert(forrestFriends instanceof Array); // true
    
    // Array Literal
    var forrestFriends = ['Bubba', 'Lieutenant Dan'];
    alert(typeof forrestFriends); // object
    alert(forrestFriends instanceof Array); // true

Oh, I See ...

Oh, I see ...

Or at least I hope you do

Global Objects and Literals (9)

  • Remember when I told you all Global Objects (such as String) derive from Object?
  • Here's some code proof:
    // Number Instance
    var birthYear = new Number('1945');
    alert(typeof birthYear);
    alert(birthYear instanceof Number);
    alert(birthYear instanceof Object);
    
    // Array Instance
    var forrestFriends = ['Bubba', 'Lieutenant Dan'];
    alert(typeof forrestFriends);
    alert(forrestFriends instanceof Array);
    alert(forrestFriends instanceof Object);
    
    // Function literal
    var says = function() { alert('Stupid is as stupid does'); };
    alert(typeof says); // <-- the odd one out
    alert(says instanceof Function);
    alert(says instanceof Object);

Global Objects and Literals (10)

  • What you should remember from all this
    • Literal Notations are shorthands
    • Literal Notations return a direct value instead of an object if possible
  • What you must know about Literals
    • Literal Notations are the preferred way to write JavaScript
    • Literal Notations are all around, even some of the current techniques (think JSON) are built upon this principle

Still seeing it?

Still seeing it?

Now, back to variables

Accessing Object Properties

  • Via square brackets, or via dot notation
    // Object Literal
    var forrest = {
    	firstName : 'Forrest',
    	lastName : 'Gump',
    	sentence : 'Stupid is as stupid does',
    	says : function() {
    		return this.sentence;
    	}
    };
    
    // test
    console.log(forrest);
    alert(forrest.firstName);
    alert(forrest['firstName']);
    alert(forrest.says());
    • The keys of the Object Literal may be placed between quotes. When using spaces/special chars in the keys (not recommended though), quotes must be used
    • The dot notation to accessing properties is the preferred way. Square brackets mandatory when properties have spaces in their name

Implicit Type Conversion (1)

  • As mentioned: JavaScript will change variable types on the fly (coercion)
  • This not only happens when assigning values, but also when using variables
    // Various 'false' values
    var nullVal = null;
    var undefinedVal = undefined;
    var zeroVal = 0;
    var falseVal = false;
    var emptyString = '';
    
    if (emptyString) {
    	alert('truthy');
    } else {
    	alert('falsy'); // false-ish
    }
    console.log('5' + 6 + 7);

Implicit Type Conversion (2)

  • You can prevent coercion by using the identity operator
    // Equality
    alert((7 == '7') ? 'equal' : 'not equal');
    
    // Identity
    alert((7 === '7') ? 'equal (identity)' : 'not equal (identity)');
  • Or by explicitly casting a variable to being a certain type
    // Type Coercion (default)
    alert('5' + 6 + 7);
    
    // Prevent Type Coercion
    alert(parseInt('5', 10) + 6 + 7);

Scope (1)

  • Global or Local
    // Global
    var quote = 'I had run for 3 years, 2 months, 14 days, and 16 hours.';
    
    var foo = function() {
    	var saying = 'My name is Forrest, Forrest Gump'; // Local
    	alert(saying);
    
    	alert(quote); // Access to Global
    
        question = 'Why don\'t you love me, Jenny?'; // Local ?
    	alert(question);
    };
    
    foo();
    alert(quote);
    // alert(saying); // disabled, will fail
    alert(question); // Nope, Chuck Testa! Erm, I mean global!
    • The scope is also known as the environment in which a variable lives
    • Note: In Strict Mode, the var keyword is mandatory.

Scope (2)

  • Best practice: Always use the var keyword inside functions, loops, etc.
    • Prevents pollution of the global scope
    • Prevents overwriting (by others)
    • Works in Strict Mode

Variable Properties and Methods (1)

  • Each instance of a global object provides some properties and/or methods
    • eg. String#length
    • eg. String#toUpperCase
  • Next to instance methods, they also can provide static methods
    • eg. Array#isArray
  • Full list of properties/methods to be found on Mozilla Developer Network or the ECMAScript Spec

Variable Properties and Methods (2)

  • Properties and methods are also there if a simple type is returned when using literals.
    • JavaScript does some magic in the background to make that work
  • Example
    // String Instance
    var firstName = new String('Forrest'); // an instance of String
    alert(firstName.toUpperCase());
    
    // String Literal
    var firstName = 'Forrest'; // a variable of the type string
    alert(firstName.toUpperCase());
    
    // Look ma, no var
    alert('Forrest'.toUpperCase());
    alert(['Bubba', 'Lieutenant Dan'].length);

Variables 101: Summary

  • JavaScript is weakly and dynamically typed
    • Coercion!
  • In JavaScript, about everything is an Object
    • JavaScript provides properties and methods which you can access/call
  • JavaScript provides a shorthand (Literal Notation)
    • Primitive returned when possible (String, Number, Boolean)
  • Variables have a scope in which they are accessible.
    • It's possible to access variables that live in an outer scope

JavaScript 101: Control Structures

I had run for 3 years, 2 months, 14 days, and 16 hours.

If-Else & Switch-Case(-Default)

// If statement
var badGrades = true;
if (badGrades) {
	alert('Mom sleeps with teacher');
} else {
	alert('Mom does not sleep with teacher');
}

// Switch statement
var age = 10,
	lifeState;

switch (age) {
	case 10:
		lifeState = 'Young';
	break;
	case 60:
		lifeState = 'Old';
	break;
	default:
		lifeState = 'unknown';
	break;
}

alert(lifeState);

For

// Var we'll be using
var forrestFriends = ['Bubba', 'Lieutenant Dan'];

// For
for (var i = 0; i < forrestFriends.length; i++) {
	alert(forrestFriends[i]);
}

For (optimized)

Performance can be improved by caching variables. That way JavaScript doesn't have to look up everything again.

// Var we'll be using
var forrestFriends = ['Bubba', 'Lieutenant Dan'];

// For
for (var i = 0, len = forrestFriends.length; i < len; i++) {
	alert(forrestFriends[i]);
}

For-In

// Vars we'll be using
var forrest = {
	firstName : 'Forrest',
	lastName : 'Gump' // no trailing comma (!)
};
var forrestFriends = ['Bubba', 'Lieutenant Dan'];

// For - In (Array)
for (var friend in forrestFriends) {
	alert(forrestFriends[friend]);
}

// For - In (Object)
for (var prop in forrest) {
	alert(prop + ' = ' + forrest[prop]);
}

Note: When using arrays, the order is not guaranteed! Better to use Array#forEach

While & Do-While

// While
var count = 5;
while (count > 0) {
	console.log(count);
	count--;
}

// Do - While
var count = 5;
do {
	console.log(count);
	count--;
} while (count > 0);

// Tip: retry this example with count = 0 as starting value

With

// Vars we'll be using
var forrest = {
	firstName : 'Forrest',
	lastName : 'Gump' // no trailing comma (!)
};

// With
with (forrest) {
	alert(firstName);
	alert(lastName);
}

Note: With is considered harmful and performs badly. Not suggested.

Try-Catch(-Finally)

// Vars we'll be using
var forrest = {
	firstName : 'Forrest',
	lastName : 'Gump',
	says : function() {
		console.log('Stupid is as stupid does');
	}
};

// Try-Catch
try {
	forrest.functionDoesNotExist();
} catch (error) {
	console.error('ERROR! ' + error.name + ' : ' + error.message);
}

// Try-Catch-Finally
try {
	forrest.functionDoesNotExist();
} catch (error) {
	console.error('ERROR! ' + error.name + ' : ' + error.message);
} finally {
	forrest.says();
}

Not a real control structure, but found it fitting here

Break (1)

  • Jumps to the end of the statement & aborts current task
  • Valid inside switch, for, for-in, while, and do-while
  • Example
    for (var x = 1; x <= 5; x++) {
    	var y = 1;
    	while (y <= 7) {
    		if (y == 5) { break; }
    		console.log(x + '-' + y);
    		y++;
    	}
    }

Break (2)

  • To break the outer loop, one can make use of a label
    myForLoop:
    for (var x = 1; x <= 5; x++) {
    	var y = 1;
    	while (y <= 7) {
    		if (y == 5) { break myForLoop; }
    		console.log(x + '-' + y);
    		y++;
    	}
    }

Continue (1)

  • Jumps back to start of statement and continues with next item
  • Valid inside for, for-in, while, and do-while
  • Example
    for (var x = 1; x <= 5; x++) {
    	for (var y = 1; y <= 7; y++) {
    		if (y == 5) continue;
    		console.log(x + '-' + y);
    	}
    }

Continue (2)

  • Using a label is also possible
    myForLoop:
    for (var x = 1; x <= 5; x++) {
    	for (var y = 1; y <= 7; y++) {
    		if (y == 5) { continue myForLoop; }
    		console.log(x + '-' + y);
    	}
    }

In this example you get the same result as the first break example

JavaScript 101: Little Big Details

Feather

My name is Forrest, Forrest Gump.

Ternary Operator

// A var we'll need
var looking = true;

// How you'd probably write it at first
var forrestSays;
if (looking) {
	forrestSays = 'I gotta find Bubba!';
} else {
	forrestSays = 'It\'s OK';
}
alert(forrestSays);
// A var we'll need
var looking = true;

// Better: Ternary operators
var forrestSays = looking ? 'I gotta find Bubba!' : 'It\'s OK';
alert(forrestSays);

Shorthand Assignment

// How you'd probably write it at first
var lifeIs = function(boxOfChocolates) {
	var life;

	if (!boxOfChocolates) {
		life = 'a Snickers bar';
	} else {
		life = boxOfChocolates;
	}

	return 'Life is like ' + life;
};
alert(lifeIs('a Box of Chocolates'));
// Better: Shorthand assignment
var lifeIs = function(boxOfChocolates) {
	return 'Life is like ' + (boxOfChocolates || 'a Snickers bar');
};
alert(lifeIs('a Box of Chocolates'));

Short-Circuit Logic

// An Object we'll need
var forrest = {
	firstName : 'Forrest',
	lastName : 'Gump',
	says : function() {
		alert('Stupid is as stupid does');
	}
};

// How you'd probably write it at first
if ((forrest != null) && (forrest.says != null)) {
	forrest.says();
}
// An Object we'll need
var forrest = {
	firstName : 'Forrest',
	lastName : 'Gump',
	says : function() {
		alert('Stupid is as stupid does');
	}
};

// Short circuit logic
forrest && forrest.says && forrest.says();

Chaining

  • It's possible to chain methods, as long as a — for the next method — usable value is returned
    var example = 'Forrest'.substr(0,5).split('').join('-').toUpperCase();
    alert(example);
  • Interpreted from left to right

Advanced JavaScript: Functions

Run Forrest, Run

Run Forrest, Run()

Functions Recap

  • Functions can be defined in three ways
    // Function Constructor
    var says = new Function('sentence', 'alert(sentence)');
    
    // function statement or function declaration
    function says(sentence) { alert(sentence); };
    
    // function expression or function operator
    var says = function(sentence) { alert(sentence); };
  • Preferred way is via the function expression

Function arguments (1)

  • Basic usage
    var says = function(sentence) {
    	alert(sentence);
    };
    
    says('Stupid is as stupid does');
  • Arguments can be passed on
    var says = function(sentence) {
    	alert(sentence);
    };
    
    var forrestSays = function(whatForrestSays) {
    	says('Forrest Says "' + whatForrestSays + '"');
    };
    
    forrestSays('Stupid is as stupid does');

Function arguments: Non-Primitives

  • Non-primitives are passed by Reference
    var change = function(obj) {
    	obj.sentence = 'My name is Forrest, Forrest Gump';
    };
    
    // Object Literal => Object Returned
    var forrest = {
    	sentence : 'Stupid is as stupid does'
    };
    
    alert(forrest.sentence);
    change(forrest);
    alert(forrest.sentence);
    var change = function(arr) {
    	arr.unshift('Jenny');
    };
    
    // Array Literal => Array (essentially Object) Returned
    var forrestFriends = ['Bubba', 'Lieutenant Dan'];
    
    alert(forrestFriends.length);
    change(forrestFriends);
    alert(forrestFriends.length);

Function arguments: Primitives

  • Primitives are passed by value
    var change = function(sentence) {
    	sentence = 'My name is Forrest, Forrest Gump';
    };
    
    var sentence = 'Stupid is as stupid does'; // String literal (string)
    alert(sentence);
    change(sentence);
    alert(sentence);
  • Beware: if a primitive variant of the non-primitive argument exists, it's also passed by value!
    var change = function(sentence) {
    	sentence = 'My name is Forrest, Forrest Gump';
    };
    
    var sentence = new String('Stupid is as stupid does'); // String Instance (Object)
    alert(sentence);
    change(sentence);
    alert(sentence);

Function arguments: Functions (1)

  • Function arguments can be anything, even other functions!
    var logs = function(sentence) {
    	console.log(sentence);
    };
    
    var says = function(sentence) {
    	alert(sentence);
    };
    
    var forrestDoes = function(fn, sentence) {
    	fn(sentence);
    };
    
    forrestDoes(says, 'Stupid is as stupid does');
    forrestDoes(logs, 'Stupid is as stupid does');

Function arguments: Functions (2)

  • Internally, JavaScript will replace fn with the actual passed in function
  • Can be checked by calling Function#toString
    var logs = function(sentence) {
    	console.log(sentence);
    };
    
    var says = function(sentence) {
    	alert(sentence);
    };
    
    var forrestDoes = function(fn, sentence) {
    	alert(fn.toString());
    	fn(sentence);
    };
    
    forrestDoes(says, 'Stupid is as stupid does');
    forrestDoes(logs, 'Stupid is as stupid does');

Function arguments: Functions (3)

  • You can even directly pass in a function on the fly, without needing to declare it first
    var forrestDoes = function(fn, sentence) {
    	fn(sentence);
    };
    
    forrestDoes(function(sentence) { // says from the previous example
    	alert(sentence);
    }, 'Stupid is as stupid does');
    
    forrestDoes(function(sentence) { // logs from the previous example
    	console.log(sentence);
    }, 'Stupid is as stupid does');
  • The passed in functions we call anonymous functions, as they have no name.
    • Also known as Lambda Functions

Function arguments:
Functions Passed by Ref/Val?

  • An example
    function change(fn) {
    	fn = function() {
    		alert('changed');
    	};
    };
    var says = function() {
    	alert('Stupid is as stupid does');
    };
    
    says();
    change(says);
    says();
  • Functions are passed by value!
  • Rather logical, if we remember this
    var says = function() { };
    alert(typeof says); // Not an object!

Functions Passed by Value (1)

  • Functions being passed by value has consequences!
    var forrest = {
    	sentence : 'Stupid is as stupid does',
    	say : function() {
    		return this.sentence;
    	}
    };
    
    var show = function(fn) {
    	alert(fn());
    };
    
    show(forrest.say);
    • Meaning of this has hanged!

Functions Passed by Value (2)

  • If you know the function: fixable by passing the object
    var forrest = {
    	sentence : 'Stupid is as stupid does',
    	say : function() {
    		return this.sentence;
    	}
    };
    
    var show = function(obj) {
    	alert(obj.say());
    };
    
    show(forrest);

Functions Passed by Value (3)

  • If you don't know the function: fixable by passing both the object and the function and then using Function#call
    var forrest = {
    	sentence : 'Stupid is as stupid does',
    	say : function() {
    		return this.sentence;
    	}
    };
    
    var show = function(obj, fn) {
    	alert(fn.call(obj));
    };
    
    show(forrest, forrest.say);
    • Function#call lets you call a function and give a new meaning to this when calling it
    • More on Function#call later

Self Invoking Functions (1)

  • Consider this function
    var run = function() {
    	alert('Run Forrest, Run');
    };
    run();
  • No harm in adding some extra brackets
    var run = (function() {
    	alert('Run Forrest, Run');
    });
    run();
  • When calling run();, JavaScript will replace the run part of that statement with the actual contents of the variable run

Self Invoking Functions (2)

  • So eventually, you'll end up with this
    (function() {
    	alert('Run Forrest, Run');
    })();
  • As you can see, the function is now
    • anonymous
    • invoked immediately
  • We call this an Immediately-Invoked Function Expression (IIFE, pronounced iffy)
  • This is also possible
    (function() {
    	alert('Run Forrest, Run');
    }()); // The Crockford Way

Self Invoking Functions (3)

  • IIFE's work with function parameters too
    (function(sentence) {
    	alert(sentence);
    })('Run Forrest, Run');
    • Which is comparable to calling
      run('Run Forrest, Run');
  • Alternative example
    var toSay = 'Run Forrest, Run';
    (function(sentence) {
    	alert(sentence);
    })(toSay);

Nesting Functions (1)

  • It's possible to nest functions
    var forrestSays = function(whatForrestSays) {
    	var says = function(sentence) {
    		alert(sentence);
    	};
    
    	says(whatForrestSays);
    };
    
    forrestSays('Stupid is as stupid does');
  • Keep the scope in mind!
    var forrestSays = function(whatForrestSays) {
    	var says = function(sentence) {
    		alert(sentence);
    	};
    
    	says(whatForrestSays);
    };
    
    forrestSays('Stupid is as stupid does');
    says('Stupid is as stupid does'); // will fail, not in scope!

Nesting Functions (2)

  • Scope also allows you to re-use names
    var says = function(sentence) {
    	alert('Outer says, says "' + sentence + '"');
    };
    
    var forrestSays = function(whatForrestSays) {
    	var says = function(sentence) {
    		alert('Inner says, says "' + sentence + '"');
    	};
    	says(whatForrestSays);
    };
    
    forrestSays('Stupid is as stupid does');
    says('Stupid is as stupid does'); // won't fail
  • But that's not a best practice, as things can (and will) get complicated.
    • Above that, what if you want to run the outer says from within forrestSays?

Return Values (1)

  • Functions can return values if they want
  • Use the return statement
    var says = function(what, verb) {
    	return what + ' ' + verb;
    };
    
    var shitHappens = says('Shit', 'happens');
    alert(shitHappens);
  • Return values can be anything, even functions!
    var says = function(what) {
        return function(verb) {
            return what + ' ' + verb;
        };
    };
    var shit = says('Shit');
    alert(shit('happens'));
    
    // also possible
    alert(says('Shit')('happens'));

Wow, what just happened there?

  • Turns out JavaScript does something magical known as creating closures when you create a function
  • A closure a special kind of object that holds two components:
    • The created function
    • The environment in which it was created (LexicalEnvironment)
  • The environment consists of all local variables that were in scope at the time the closure was created.
  • The function can access the environment, thus can access the (non-local) variables

Not sure if ...

Not Sure If ...

Yeah, I understand ... lets just check an example

Closures: Example (1)

  • Example
    var firstName = 'Forrest';
    
    var forrestFriends = function() {
    	var friends = ['Bubba', 'Lieutenant Dan'];
    	var friendsCount = function() {
    		return friends.length;
    	};
    	var joinFriends = function() {
    		return firstName + ' has ' + friendsCount() + ' friends: ' + friends.join(', ');
    	};
    	return joinFriends();
    };
    
    alert(forrestFriends());
    • When joinFriends (or any of the other functions) is created, a closure is created
    • The closure holds the function itself and the variables in scope.
    • Now, what is the scope when the function is created?

Closures: Example (2)

  • Example (continued)
    var firstName = 'Forrest';
    
    var forrestFriends = function() {
    	var friends = ['Bubba', 'Lieutenant Dan'];
    	var friendsCount = function() {
    		return friends.length;
    	};
    	var joinFriends = function() {
    		return firstName + ' has ' + friendsCount() + ' friends: ' + friends.join(', ');
    	};
    	return joinFriends();
    };
    
    alert(forrestFriends());
    • The scope of joinFriends is everything “on the same level of it”, thus everything inside the curly brackets of forrestFriends
      • So the LexicalEnvironment of the created closure holds the variables
        friends, friendsCount and joinFriends

Closures: Example (3)

  • Example (continued)
    var firstName = 'Forrest';
    
    var forrestFriends = function() {
    	var friends = ['Bubba', 'Lieutenant Dan'];
    	var friendsCount = function() {
    		return friends.length;
    	};
    	var joinFriends = function() {
    		return firstName + ' has ' + friendsCount() + ' friends: ' + friends.join(', ');
    	};
    	return joinFriends();
    };
    
    alert(forrestFriends());
    • What about firstName? That wasn't part of the current scope?
      • When a function can't find a variable inside the current LexicalEnvironment, it will try the outer LexicalEnvironment (go up one level) to (hope to) find it there
      • So it goes up to the LexicalEnvironment of the closure created when forrestFriends was created; which has access to the vars in the global scope

Closures: Summarized

  • In short, when a function is created, it can access the non-local variables (outer variables) thanks to the closure that was created.
  • And it doesn't stop there ...

Oh boy ...

Still seeing it?

Please, bear with me, there are some facts you should know

Closures: Vars linked by Reference

  • Fact: The variables are linked by reference to the LexicalEnvironment
    var firstName = 'Forrest';
    
    var forrestFriends = function() {
    	var friends = ['Bubba', 'Lieutenant Dan'];
    	var friendsCount = function() {
    		return friends.length;
    	};
    	var joinFriends = function() {
    		return firstName + ' has ' + friendsCount() + ' friends: ' + friends.join(', ');
    	};
    	friends.unshift('Jenny'); // Change friends, after the function was created
    	return joinFriends();
    };
    
    alert(forrestFriends());

Closures: Returning functions

  • Fact: With nested functions: when the outer function has finished, the closure still lives on
    var firstName = 'Forrest';
    
    var forrestFriends = function() {
    	var friends = ['Bubba', 'Lieutenant Dan'];
    	var friendsCount = function() {
    		return friends.length;
    	};
    	var joinFriends = function() {
    		return firstName + ' has ' + friendsCount() + ' friends: ' + friends.join(', ');
    	};
    	return joinFriends;
    };
    
    var ff = forrestFriends();
    // at this moment, the outer function has ended
    // only joinFriends() was returned
    // what about friends and friendsCount? Will they still exist?
    alert(ff());

Closures: Topping things off

  • Final example
    var add = function(x) {
    	return function(y) {
    		return x + y;
    	};
    };
    var add5 = add(5),
        add6 = add(6);
    var eleven = add5(6),
        twelve = add6(6);
    alert(eleven);
    alert(twelve)
    
  • Internally add5 has become
    function(y) {
    	return x + y;
    }; // How I know? Try alert(add5.toString()); in the example above ;)
  • Now, how come add5 knows the value of x?
    • Yes, closures!

Closures: In Closing

Function arguments: Again (1)

  • Say you have a function that should work given any number of parameters
    var sum = function(a, b, c, d, e) {
    	return a + b + c + d + e
    };
    alert(sum(1,2)); // NaN (!)
    alert(sum(1,2,3,4,5)); // 15
    alert(sum(1,2,3,4,5,6)); // What now?
    • You can tell by just looking at it: that's bound to fail and is cumbersome to work with

Function arguments: Again (2)

  • JavaScript provides an arguments variable inside each function
    var sum = function() {
    	alert(typeof arguments);
    	alert(arguments instanceof Object);
    	alert(arguments instanceof Array);
    };
    sum(1,2);
    • But beware, that arguments variable is an Object, and not an Array, as you can see!
    • Not a real problem, as you can loop over an Object with a for loop just as you'd do with an Array

Function arguments: Again (3)

  • Working example
    var sum = function() {
    	var sum = 0;
    	for (var i = 0, len = arguments.length; i < len; i++) {
    		sum += parseInt(arguments[i]);
    	}
    	return sum;
    };
    alert(sum(1,2));
    alert(sum(1,2,3,4,5));
    alert(sum(1,2,3,4,5,6));

Function arguments: Again (4)

  • What if you'd want to pass the arguments to an other function?
    var otherFunc=function() {
    	alert('otherFunc : ' + arguments.length);
    };
    
    var myFunc = function() {
    	alert('myFunc : ' + arguments.length);
    	otherFunc(arguments);
    };
    
    myFunc(1,2,3,4,5,6,7,8,9,10);
  • JavaScript has an answer to that: Function#apply and Function#call

Function#apply

  • Function#apply allows you to call an other function and give it a new meaning of this. Arguments passed in using an Array-like Object
    Function#apply(this, Array(arg1[, arg2[, ...[, argN]]]));
  • Updated example
    var otherFunc = function() {
    	alert('otherFunc : ' + arguments.length);
    };
    
    var myFunc = function() {
    	alert('myFunc : ' + arguments.length);
    	otherFunc.apply(this, arguments);
    };
    
    myFunc(1,2,3,4,5,6,7,8,9,10);
    • More on the meaning of this later

Function#call

  • Function#call does the same as Function#apply, but passed in arguments must be passed in one by one (comma separated)
    Function#call(this, arg1[, arg2[, ...[, argN]]]);
  • We prefer Function#apply in this context though, as we can immediately pass in arguments or any other Array or Object

Function#bind

  • To make things complete, Function#bind also exists, but we'll touch that later

Function arguments: DIY (1)

  • Say we have this
    var says = function() {
    	var firstName = arguments[0],
    	    sentence = arguments[1];
    
    	return firstName + ' says "' + sentence + '"';
    };
    
    alert(says('Forrest', 'Stupid is as stupid does'));
  • Not so difficult to remember that index 0 is who and index 1 is what in this example
    • But what if we have a function that can take up to ten arguments?
    • But what if we have a function that can take up to ten arguments, of which not all are mandatory?

Function arguments: DIY (2)

  • Would be neat if we could have named arguments or something like an associative array ...
  • ... and we can! By using an Object
    var says = function(person) {
    	return person.firstName + ' says "' + person.sentence + '"';
    };
    
    var forrest = {
    	firstName : 'Forrest',
    	lastName : 'Gump',
    	sentence : 'Stupid is as stupid does',
    	birthYear : 1945
    };
    
    alert(says(forrest));
  • Benefits
    • Named variables
    • Sequence of parameters not important
    • Easy working with optional parameters

Function arguments: DIY (3)

  • It's even possible to force having defaults using this
    var extend = function(params, defaults) {
    	for (var i in defaults) {
    		if (!params[i]) {
    			params[i] = defaults[i];
    		}
    	}
    };
    
    var foo = function(params) {
    	var defaults  = { position: 'left', start: 0, stop: 10 };
    	extend(params, defaults);
    	console.log(params);
    };
    
    foo({ position: 'left', start: 3 });

Functions Summary

  • In JavaScript, functions are First Class Objects
    • They can be created on the fly (at run-time)
    • They can be passed as a parameter
    • They can be assigned into a variable
    • They can be returned from an other function
  • Functions can be anonymous
  • Functions can be self-invoking
  • When creating a function, a closure is created.
    • The function can have access to the non-local variables, which are linked by reference to the LexicalEnvironment
  • Anything can be a function argument
    • Also possible to use built-in arguments or pass in your own Object

Advanced JavaScript: Objects

To do whatever you tell me, drill sergeant!

Gump! What's your sole purpose in this army?
To do whatever you tell me, drill sergeant!

Objects Recap

  • Creating an Object
    // Object Constructor
    var forrest = new Object();
    forrest.firstName = 'Forrest';
    forrest.lastName = 'Gump';
    forrest.says = function() {
    	return 'Stupid is as stupid does';
    };
    
    alert(forrest.says());
    
    // Object Literal
    var forrest = {
        firstName : 'Forrest',
        lastName : 'Gump',
        says : function() {
            return 'Stupid is as stupid does';
        } // no trailing comma (!)
    };
    
    alert(forrest.says());

Accessing inner properties

  • Just use the this keyword
    // Object Literal
    var forrest = {
        firstName : 'Forrest',
        lastName : 'Gump',
        says : function() {
            return this.firstName + ' says "Stupid is as stupid does"';
        }
    };
    
    alert(forrest.says());

What is this?

  • It depends, actually
    1. If the function is invoked using Function#call or Function#apply, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object.
    2. If the function being invoked was created using Function#bind, this will be the first argument that was passed to bind at the time the function was created.
    3. If the function is being invoked as a method of an object, this will refer to that object.
    4. Otherwise, the function is being invoked as a standalone function not attached to any object, and this will refer to the global object.

Multiple Objects

  • Creating multiple Object
    var forrest = { // Forrest
        firstName : 'Forrest',
        lastName : 'Gump',
        says : function() {
            return this.firstName + ' says "Stupid is as stupid does"';
        }
    };
    
    var dan = { // Lieutenant Dan
    	firstName : 'Dan',
    	lastName : 'Taylor',
    	says : function() {
    		return this.firstName + ' says "I\'m here to try out my sea legs"';
    	}
    };
    
    // Jenny
    // ...
    
    • Yeah, that's not DRY
    • What if we could implement some of the OO stuff we already know?

Object Constructor

  • In JavaScript, you can create a constructor by using a function
    var Person = function(firstName, lastName, sentence) {
        this.firstName = firstName;
    	this.lastName = lastName;
    	this.sentence = sentence;
    
        this.says = function() {
        	return this.firstName + ' says "' + this.sentence + '"';
        };
    };
    
    var forrest = new Person('Forrest', 'Gump', 'Stupid is as stupid does');
    var dan = new Person('Dan', 'Taylor', 'I\'m here to try out my sea legs');
    
    alert(forrest.says());
    alert(dan.says());
    
    • When documenting, we can talk about Person#firstName, Person#lastName, Person#sentence and Person#says as those are properties/methods of Person

Object.prototype (1)

  • Internally, JavaScript stores Person#firstName and the others in the .prototype of Person
    • When we say Person#firstName, we actually say Person.prototype.firstName
  • If you add a new property/method to .prototype, you're basically changing the class definition
  • You can take a look of what is in .prototype
    console.log(String.prototype);

Object.prototype (2)

  • If you add a new property/method to .prototype, you're basically changing the class definition
    var Person = function(firstName, lastName) {
        this.firstName = firstName;
    	this.lastName = lastName;
    };
    
    Person.prototype.isRunning = false;
    Person.prototype.run = function() {
    	this.isRunning = true;
    };
    
    var forrest = new Person('Forrest', 'Gump');
    var dan = new Person('Dan', 'Taylor');
    
    alert(forrest.isRunning ? 'Forrest is running' : 'Forrest is not running');
    alert(dan.isRunning ? 'Dan is running' : 'Dan is not running');
    forrest.run();
    alert(forrest.isRunning ? 'Forrest is running' : 'Forrest is not running');
    alert(dan.isRunning ? 'Dan is running' : 'Dan is not running');

Extending Built-In Objects (1)

  • You can also add new functionality to the global objects by this
    Number.prototype.fahrenheitToCelcius = function() {
    	return (parseInt(this, 10) - 32) / 1.8;
    };
    Number.prototype.celciusToFahrenheit = function() {
    	return (parseInt(this, 10) * 1.8) + 32;
    };
    
    var c = 40;
    var f = 104;
    
    alert(c.celciusToFahrenheit());
    alert(f.fahrenheitToCelcius());
    

What about inheritance? (1)

  • Inheritance is possible, by doing something oddlooking like so
    var Person = function(firstName, lastName) {
        this.firstName = firstName;
    	this.lastName = lastName;
    };
    
    Person.prototype.fullName = function() {
    	return this.firstName + ' ' + this.lastName;
    };
    
    var Actor = function(firstName, lastName) {
    	this.firstName = firstName;
    	this.lastName = lastName;
    };
    Actor.prototype = new Person; // Inheritance
    
    var forrest = new Person('Forrest', 'Gump');
    var tomhanks = new Actor('Tom', 'Hanks');
    
    alert(forrest.fullName());
    alert(tomhanks.fullName());

What about inheritance? (2)

  • We can omit repeating the constructor by making use of Function#call
    var Person = function(firstName, lastName) {
        this.firstName = firstName;
    	this.lastName = lastName;
    };
    
    Person.prototype.fullName = function() {
    	return this.firstName + ' ' + this.lastName;
    };
    
    var Actor = function(firstName, lastName) {
    	Person.call(this, firstName, lastName);
    };
    Actor.prototype = new Person;
    
    var forrest = new Person('Forrest', 'Gump');
    var tomhanks = new Actor('Tom', 'Hanks');
    
    alert(forrest.fullName());
    alert(tomhanks.fullName());
    • This way of inheriting is known as pseudoclassical inheritance

What about inheritance? (3)

  • Note that when extending the prototype of the parent class, the derived class gets it too!
    var Person = function(firstName, lastName) {
        this.firstName = firstName;
    	this.lastName = lastName;
    };
    
    var Actor = function(firstName, lastName) {
    	Person.call(this, firstName, lastName);
    };
    Actor.prototype = new Person;
    
    Person.prototype.isHuman = true;
    
    var forrest = new Person('Forrest', 'Gump');
    var tomhanks = new Actor('Tom', 'Hanks');
    
    alert(forrest.isHuman ? 'Human' : 'Not Human');
    alert(tomhanks.isHuman ? 'Human' : 'Not Human');

Module Pattern

  • The module pattern, popularized by Douglas Crockford, allows having private datamembers
    var forrest = (function() {
    
    	var firstName = 'Forrest'; // Private var
    
    	var says = function() { // Private function
    		return 'Stupid is as stupid does';
    	};
    
    	return {
    		lastName : 'Gump', // Public var
    		getLastName : function() { // Public function
    			return this.lastName; // use this when accessing a public var
    		},
    		getFirstName : function () {
    			return firstName; // no this when accessing private vars!
    		}
    	};
    
    })(); // IIFE
    
    alert(forrest.getFirstName() + ' ' + forrest.getLastName());

Revealing Module Pattern

  • Variant of the module pattern: make everything private and provide pointers to them in the public part
    var forrest = (function() {
    
    	var firstName = 'Forrest';
    	var lastName = 'Gump';
    	var says = function() {
    		return 'Stupid is as stupid does';
    	};
    	var getLastName = function() {
    		return lastName;
    	};
    	var getFirstName = function () {
    		return firstName;
    	};
    
    	return {
    		getFirstName: getFirstName,
    		getLastName: getLastName,
    		whatDoesHeSay: says
    	};
    
    })(); // IIFE
    
    alert(forrest.whatDoesHeSay());

JavaScript in the browser

Stupid is as stupid does

Stupid is as stupid does

Document Object Model

  • JavaScript is implemented in the browser
    • Not all browsers implement the same version of JavaScript (eg. Array#forEach is only available since JavaScript 1.6)
    • Not all browsers implement it in the same way (different interpreters!), or have their own additions
  • The browser also provides an API named the Document Object Model (DOM)
    • The DOM provides a structural representation of the document, enabling one to modify its content and visual presentation.
    • The DOM differs between all browsers (and browser versions)
  • Via JavaScript you can access the DOM
    • The code you'll write is written in JavaScript, but it uses the DOM to access the web page and its elements.

Warning: oldIE

  • oldIE, or Old Internet Explorer
    • Term to name Internet Explorer 6, 7 & 8 in one gasp
  • Things mentioned here most likely won't work in oldIE
    • Because they implement the DOM differently
    • Because the DOM wasn't formalized back then
    • Because Microsoft took a loose take on web standards back then
  • Thankfully things have gotten better (but not ideal) with IE9, and by the looks of the previews IE10 will be quite standards compliant

The DOM (1)

  • The DOM is one big tree, representing all the nodes of the document.
  • Consider this document ...
    <!DOCTYPE HTML>
    <html>
    	<head>
    		<title>The document</title>
    	</head>
    	<body>
    		<div>Data</div>
    		<ul>
    			<li>Warning</li>
    			<li></li>
    		</ul>
    		<div>Top Secret!</div>
    	</body>
    </html>

The DOM (2)

  • ... the DOM tree would look like this
Figure by JavaScript.info

The DOM (3)

  • Actually, it would look like this
Figure by JavaScript.info

Yes, whitespace counts as a node too!

Selecting Elements

  • window.document offers quite a few ways of selecting elements from the document
    // get one element with the given id
    var el = document.getElementById('siteWrapper');
    
    // get all links (<a> elements)
    var links = document.getElementsByTagName('a');
    document.links; // or use the built-in property!
    
    // get all elements with the class foo
    var foos = document.getElementsByClassName('foo');
    
    // get elements based on a CSS selector
    var els = document.querySelectorAll('code pre');
    
    // get elements based on CSS selector
    var el = document.querySelector('code pre'); // only returns the first match

Manipulating Elements (1)

  • Once you have selected an element, you can manipulate it
    // change CSS properties
    document.getElementById('manipulating-demo').style.border = '1px solid #FFF';
    document.getElementById('manipulating-demo').style.padding = '20px';
    
    // change text
    document.getElementById('manipulating-demo').innerHTML = 'Code has been run, I am changed';
    
    // set 'test' as classname
    document.getElementById('manipulating-demo').className = 'test';
    
    // give notice
    alert('The element #' + document.getElementById('manipulating-demo').id + ' was changed');

    Run example above to change me

Manipulating Elements (2)

  • Fragment can heavily be optimized by caching the variable so that the DOM lookup takes place only once
    // cache the variable
    var el = document.getElementById('manipulating-demo2');
    
    // change CSS properties
    el.style.border = '1px solid #FFF';
    el.style.padding = '20px';
    
    // change text
    el.innerHTML = 'Code has been run, I am changed';
    
    // set 'test' as classname
    el.className = 'test';
    
    // give notice
    alert('The element #' + el.id + ' was changed');

    Run example above to change me

Manipulating Elements (3)

  • Here's another unoptimized example
    var allSpans = document.querySelectorAll('#manipulating-unoptimized span');
    
    for (var i = 0; i < allSpans.length; i++) {
    	allSpans[i].className = 'test';
    	allSpans[i].style.border = '1px solid #FFF';
    	allSpans[i].style.padding = '5px';
    	allSpans[i].style.margin = '5px';
    }

    123

Manipulating Elements (4)

  • Optimized version
    var allSpans = document.querySelectorAll('#manipulating-optimized span');
    
    for (var i = 0, len = allSpans.length; i < len; i++) {
    	var curSpan = allSpans[i];
    	curSpan.className = 'test';
    	curSpan.style.border = '1px solid #FFF';
    	curSpan.style.padding = '5px';
    	curSpan.style.margin = '5px';
    }

    456

Manipulating Elements (5)

Traversing (1)

  • Once you have an element selected, you can go up/down the DOM tree in a parent/children/sibilings methodology
Figure by JavaScript.info
Figure by JavaScript.info

Traversing (2)

  • Beware though: those functions take whitespace into account
    • Up the DOM tree: Node#parentNode
    • Down the DOM tree: Node#childNodes, Node#firstChild, Node#lastChild
    • Left/Right on the DOM tree: Node#previousSibling, Node#nextSibling
  • These don't, and work with actual elements
    • Up the DOM tree: Node#parentElement
    • Down the DOM tree: Node#children, Node#firstElementChild, Node#lastElementChild
    • Left/Right on the DOM tree: Node#previousElementSibling, Node#nextElementSibling

Events

  • It's possible to listen to certain events that occur in the document/on an element
  • An event can be seen as an action that takes place
  • Events can be things like
    • a mouse interaction (click, mouseover, mouseout, ..)
    • a keyboard interaction (keypress, keydown, keyup)
    • a form action (submit, reset)
    • a general event such as something finished loading, a resize of the window, an element gaining focus, etc.
    • (Handy one-page overview on Wikipedia)
  • Basically they form the core of the scripts we'll be writing

Hooking Events (1)

  • The old way: #onEventname
    var p = document.querySelector('#onevent-demo p');
    
    p.onclick = function() { alert('You clicked me!'); };
    p.onmouseover = function() { this.style.backgroundColor = '#333'; };
    p.onmouseout = function() { this.style.backgroundColor = ''; };

    I am the demo

  • Note that when you hook an event twice, it will be overwritten
    var p = document.querySelector('#oneventtwice-demo p');
    
    p.onclick = function() { alert('You clicked me (1)!'); };
    p.onclick = function() { alert('You clicked me (2)!'); };
    p.onmouseover = function() { this.style.backgroundColor = '#333'; };
    p.onmouseout = function() { this.style.backgroundColor = ''; };

    I am the 2nd demo

Hooking Events (2)

  • The new, formalized, way: #addEventListener
    var p = document.querySelector('#addeventlistener-demo p');
    
    p.addEventListener('click', function() {
    	alert('You clicked me!');
    });
    p.addEventListener('mouseover', function() {
    	this.style.backgroundColor = '#333';
    });
    p.addEventListener('mouseout', function() {
    	this.style.backgroundColor = '';
    });

    I am the demo

Hooking Events (3)

  • Calling #addEventListener multiple times for the same event
    var p = document.querySelector('#addeventlistenertwice-demo p');
    
    p.addEventListener('click', function() {
    	alert('You clicked me (1)!');
    });
    p.addEventListener('click', function() {
    	alert('You clicked me (2)!');
    });
    p.addEventListener('mouseover', function() {
    	this.style.backgroundColor = '#333';
    });
    p.addEventListener('mouseout', function() {
    	this.style.backgroundColor = '';
    });

    I am the demo

Removing an Event Listener

  • Possible by #removeEventListener, but only if you've attached a named function
    var p = document.querySelector('#removeeventlistener-demo p');
    
    var clickHandler = function() {
    	alert('You clicked me!');
    };
    
    p.addEventListener('click', clickHandler);
    p.removeEventListener('click', clickHandler);
    
    p.addEventListener('mouseover', function() {
    	this.style.backgroundColor = '#333';
    });
    p.addEventListener('mouseout', function() {
    	this.style.backgroundColor = '';
    });

    I am the demo

Event Handling: Nested Events

  • If an element and one of its ancestors have an event handler for the same event, what happens?
    1
    2
    3
  • If I were to click the red box, which click handler will fire (if all 3 have click handlers)?
    • Will only one handler fire?
    • Will all handlers fire?
    • If all are fired, which one will fire first?

Event Handling: Bubbling (1)

  • What will happen is that the browser will fire all the events, one after the other
  • The order in which they are fired is from the most inner element outwards. We call this bubbling

Event Handling: Bubbling (2)

  • Graphical (example where you'd click a cell of a table)
    Adjusted figure, based upon a figure by W3C

Event Handling: Bubbling Example

  • Run the JS code and then click the center box to see bubbling in action
    var divs = document.querySelectorAll('#bubbling-demo div');
    
    for (var i = 0, len = divs.length; i < len; i++) {
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'yellow';
    		alert(this.className);
    		this.style.backgroundColor = '';
    	});
    }
    1
    2
    3

Event Handling: Stop bubbling

  • Possible with Event#stopPropagation
    var divs = document.querySelectorAll('#stoppropagation-demo div');
    
    for (var i = 0, len = divs.length; i < len; i++) {
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'yellow';
    		alert(this.className);
    		this.style.backgroundColor = '';
    		event.stopPropagation(); // stop bubbling!
    	});
    }
    1
    2
    3

Event Handling: Stop default action

  • In case you want to block the following of a link, submitting of a forms, etc.
  • Possible with Event#preventDefault
    var link = document.querySelector('#stopdefault-demo a');
    
    link.addEventListener('click', function(event) {
    	alert('Link is linked to ' + this.href + ' but won\'t be followed');
    	event.preventDefault(); // prevent the default action from happening
    });

    Link to ikdoeict.be

  • In the past, this was done by return false;
    • Don't use this, as it also stops the bubbling

Event Handling: what was clicked?

  • It's possible to know which was the original element that was clicked during the bubbling
    var divs = document.querySelectorAll('#bubblingtarget-demo div');
    
    for (var i = 0, len = divs.length; i < len; i++) {
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'yellow';
    		alert(this.className + ' (click was on ' + event.target.className + ')');
    		this.style.backgroundColor = '';
    	});
    }
    1
    2
    3

Bubbling: Practical Use Case

  • Say you have a table with 1000 cells, and you bind a click event on those cells
    • Very resourceful, document will have to keep an eye on 1000 els
  • By taking advantage of bubbling and event.target it's possible to bind the handler on the <table> itself.
    var table = document.querySelector('#bubbling-advantage-demo table');
    
    table.addEventListener('click', function(event) {
    	alert('Table click event (click was on ' + event.target.innerHTML + ')');
    });
    1 2 3
    4 5 6

Event Handling: Capturing & Bubbling

  • So we now know this thing named bubbling which happens when an event is handled: the event bubbles up the DOM tree
  • But actually, event handling consists out of three phases:
    1. Capturing of the event
      • Goes from the outer most element inwards (down the DOM tree)
      • By default, no event handlers are invoked
    2. Defining of the target
      • viz. What was clicked?
    3. The bubbling of the event
      • Goes inside out (up the DOM tree)
      • By default, event handlers will be invoked

Event Handling: Capturing & Bubbling

  • Graphical (example where you'd click a cell of a table)
    Figure by W3C

Event Handling: Capturing

  • Of course, it's possible to make the event handlers fire during the capturing phase, and not the bubbling phase.
    var divs = document.querySelectorAll('#capturing-demo div');
    
    for (var i = 0, len = divs.length; i < len; i++) {
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'cyan';
    		alert(this.className);
    		this.style.backgroundColor = '';
    	}, true); // phase parameter!
    }
    1
    2
    3

Event Handling: Capturing+Bubbling

  • FYI: To combine them both, hook two handlers
    var divs = document.querySelectorAll('#capturing-and-bubbling-demo div');
    
    for (var i = 0, len = divs.length; i < len; i++) {
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'cyan';
    		alert(this.className);
    		this.style.backgroundColor = '';
    	}, true); // capturing phase
    	divs[i].addEventListener('click', function(event) {
    		this.style.backgroundColor = 'yellow';
    		alert(this.className);
    		this.style.backgroundColor = '';
    	}, false); // bubbling phase
    }
    1
    2
    3

Events: The Loop Problem (1)

  • Say that we have this piece of code, what will be the result?
    var link,
        target = Reveal.getCurrentSlide().querySelector('.result');
    
    target.innerHTML = ''; // clear the target
    
    for (var i = 0; i < 3; i++) { // add three links
        link = document.createElement('a');
    	link.style.marginRight = '10px';
        link.innerHTML = 'Link ' + i;
        link.onclick = function() {
            alert('I am link ' + i);
        };
    	target.appendChild(link);
    };
    (Run the code example to see the result appear here)
  • Hint: where you attach the onclick, a function is created

Closures

Not Sure If ...

Here we go again!

Events: The Loop Problem (2)

  • Maybe we can fix it with a local variable?
    var link,
        target = Reveal.getCurrentSlide().querySelector('.result');
    
    target.innerHTML = ''; // clear the target
    
    for (var i = 3; i < 6; i++) { // add three links
        link = document.createElement('a');
    	link.style.marginRight = '10px';
        link.innerHTML = 'Link ' + i;
        var j = i; // introduction of a local variable
        link.onclick = function() { // function created, ergo closure created!
            alert('I am link ' + j);
        };
    	target.appendChild(link);
    };
    (Run the code example to see the result appear here)
  • Alas, when copying variables in JavaScript: by reference

Events: The Loop Problem (3)

  • What now?
  • Well ...
    • Remember this?
      var toSay = 'Run Forrest, Run';
      
      (function(sentence) {
      	console.log(sentence);
      })(toSay);
    • And remember that variables are passed by value to functions (if a primitive variant exists)?
  • That gives us a foot in the door

Events: The Loop Problem (4)

  • IIFE to the resue!
    var link,
        target = Reveal.getCurrentSlide().querySelector('.result');
    
    target.innerHTML = ''; // clear the target
    
    for (var i = 6; i < 9; i++) { // add three links
        link = document.createElement('a');
    	link.style.marginRight = '10px';
        link.innerHTML = 'Link ' + i;
        link.onclick = function(j) { // This one just became an IIFE
    		return function() { // and returns a function
            	alert('I am link ' + j);
    		};
        }(i);
    	target.appendChild(link);
    }
    (Run the code example to see the result appear here)

Attaching scripts

  • There are two main options for attaching a script to a document
    1. Using an embedded script
    2. Using an external script

Embedded script

  • Example:
    <!doctype html>
    <html>
    	<head>
    		...
    		<script>
    			alert('hello');
    		</script>
    		...
    	</head>
    <body>
    	...
    </body>
    </html>
  • Note that scripts are interpreted immediately!
    • In this example you'll see an alert before the page is being rendered!

External Script (1)

  • Example
    <!doctype html>
    <html>
    	<head>
    		...
    		<script src="path/to/my.js"></script>
    		...
    	</head>
    <body>
    	...
    </body>
    </html>
  • Best practice: place the scripts at the bottom of the page, just before </body>, to not block parallel downloads
    • The HTTP/1.1 specification suggests that browsers download no more than two components (scripts, images, etc) in parallel per hostname

External Script (2)

  • Updated example
    <!doctype html>
    <html>
    	<head>
    		...
    	</head>
    <body>
    	...
    	<script src="path/to/my.js"></script>
    </body>
    </html>

Attaching scripts: Extra

  • Event handlers can be added directly into your HTML
    <a href="#" onclick="alert('clicked'); return false;">Click me!</a>

    Click me!

  • Or like this
    <a href="javascript:alert('clicked');">Click me!</a>

    Click me!

  • Don't do this.
    • Never.
    • Ever.

Script execution (1)

  • Scripts are executed immediately, even if the document is still loading
  • In most cases you'll only want to execute scripts when the document has finished loading. There's an event for that.
    <!doctype html>
    <html>
    	<head>
    		...
    	</head>
    <body>
    	...
    	<script src="path/to/my.js"></script>
    </body>
    </html>
    window.onload = function() {
    	// ... your code here
    };
    • Old syntax + Only allows for one function to be executed when the document (including images!) has finished loading

Script execution (2)

  • Syntax can be improved, by making use of addEventListener
    document.addEventListener('load', function(event) {
    	// ... your code here
    }, false);
  • load is fired when everything (including images) is loaded. Most of the time you'll want to invoke your scripts when the DOM has loaded.
    
    document.addEventListener('DOMContentLoaded', function(event) {
    	// ... your code here
    }, false);

Namespacing

  • Say I have a function animate, but then include a library that defines the same function with the same name
    • Yes, things will go wrong
  • A simple fix would be to prefix all my functions with something (like ikdoeict_) but that isn't quite professional
    • Above that all the defined functions/variables would live in the global namespace
  • A better solution is to make use of namespacing, a technique to avoid collisions with other objects or variables in the global namespace
    • Several strategies exist, we'll cover 2

Namespacing: Object Literal (1)

  • The simplest way is to make use of the Object Literal. Just think of a name and you're good to go
    var IKDOEICT = {
    	foo : {
    		bar : function(x) { return x; },
    		baz : function(x) { return x; }
    	},
    	foobar : {
    		foo : function(x) { return x; },
    		bar : function(x) { return x; },
    		baz : function(x) { return x; }
    	}
    };
    
    alert(IKDOEICT.foobar.foo('test'));

Namespacing: Object Literal (2)

  • If your code spans over multiple files, then you still can use this
    // file 1
    var IKDOEICT = IKDOEICT || {}; // use existing IKDOEICT, or create a new Object
    
    IKDOEICT.foo = {
    	bar : function(x) { return x; },
    	baz : function(x) { return x; }
    };
    
    // file 2
    var IKDOEICT = IKDOEICT || {}; // use existing IKDOEICT, or create a new Object
    
    IKDOEICT.foobar = {
    	foo : function(x) { return x; },
    	bar : function(x) { return x; },
    	baz : function(x) { return x; }
    };
    
    // test
    alert(IKDOEICT.foo.baz('test'));

Namespacing: IIFE

  • Using an IIFE is also possible
    var IKDOEICT = IKDOEICT || {};
    
    (function(o){
    
    	o.foo = {
    		bar : function(x) { return x; },
    		baz : function(x) { return x; }
    	}
    
    	o.foobar = {
    		foo : function(x) { return x; },
    		bar : function(x) { return x; },
    		baz : function(x) { return x; }
    	}
    
    })(IKDOEICT);
    
    // test
    alert(IKDOEICT.foo.baz('test'));

Namespacing: Module Pattern

  • The Module Pattern can also be used. Allows you to have private functions/datamembers.
    var IKDOEICT = (function(){
    
    	var privateFunction = function() { return; }
    
    	return {
    		foo : {
    			bar : function(x) { return x; },
    			baz : function(x) { return x; }
    		},
    		foobar : {
    			foo : function(x) { return x; },
    			bar : function(x) { return x; },
    			baz : function(x) { return x; }
    		}
    	};
    
    })();
    
    // test
    alert(IKDOEICT.foo.baz('test'));

Best Practices

  • Place JavaScripts at the bottom of the page
    • Just before </body>
  • Make use of DOMContentLoaded
  • Namespace your code
    • Does not pollute the global namespace
    • Prevents conflicts
  • Cache your variables
  • Don't extend built-in Objects
    • Because you might implement it differently
    • Because you might use a name that later will be used by the browser/JavaScript
    • Because when using a for...in these will appear inside your loop

Questions?

Sources

ikdoeict.be