JavaScript Fundamentals
agenda
- Introduction
- Functional JavaScript
- JavaScript Object Programming
- Async JavaScript
- ECMAScript 6 is coming
- JavaScript Testing
- Resources guide
introduction
- JavaScript history
- Why JavaScript?
1995 - 1997
- Created by Brendan Eich for Netscape Navigator ...
in only ten days! - Mocha ⇒ LiveScript ⇒ JavaScript ⇒ ECMAScript
- Influenced by:
- C/Java ⇒ Sintaxis
- Scheme ⇒ FP
- Self ⇒ Prototypal inheritance
- Adopted by Microsoft with the name JS for IE 3.0 ...
first browser wars! - Standarized by ECMA International.
history
1998 - 2002
- W3C: DOM Level 1 and DOM Level 2
- Microsoft created XMLHttpRequest
- Microsoft won the war with IE 6
history
2003 - 2008
- Mozilla Firefox was born
- AJAX acronym is coined
- Google becomes more than just a search engine: GMail, Maps
- First JavaScript libraries: Prototype, Scriptaculous, etc
- JQuery: now DOM is easy
history
2009 -
- Google Chrome was born
- ECMAScript 5 was approved
- It began the era of mobile applications (SPA, mobile first)
- HTML5 & CSS3
- MV* frameworks move to the client: Angular, Backbone, etc.
- Server side JavaScript: Node.js
- JavaScript grows up: The Good parts, testing frameworks, etc
history
- JavaScript is a misunderstood language
- The name suggests that it should be similar to Java, but it is a different beast
- The language was created too quickly and has errors that many programmers do not care to avoid
- Still exists the idea that serious programming is done on the server and many programmers do not bother to learn JavaScript seriously
- But in JavaScript there are great ideas too
- It supports multiple paradigms: imperative or functional
- It is very flexible and expressive
- There have been significant improvements in performance
- The language is improving with the latest versions
- Whenever there are better books, tools, libraries, etc
Why JavaScript?
- It is easy to learn
- It is easy to write, execute, test and debug
- It is the most widely used language
-
On the client has beaten all its rivals:
- VBScript
- ActionScript (Flash)
- Applets (Java)
- Will mobile development platforms be the next victims?
- On the server is becoming a major player (Node.js)
-
Web applications can be built using only JavaScript:
- Front-end development
- Back-end development
- Server services (Web servers, message queuing services, authentication services, etc)
- JSON for client-server communication
- Even database structures (BSON) and query language (MongoDB, CouchDB)
- Isomorphic JavaScript: The Future of Web Apps?
Why JavaScript?
functional JavaScript
- It is a controversial term.
- Some key points:
- It is a declarative programming paradigm, which means programming is done with expressions
- The output value of a function depends only on the arguments that are input to the function
- Has its roots in lambda calculus, a formal system developed in the 1930s to investigate computability
What is FP?
- The central principle of OOP is that data and operations on it are closely coupled
- The central tenet of FP is that data is only loosely coupled to functions
- FP is about expressions (immutable state) / OOP is about methods (mutable state)
- Whereas the object-oriented approach tends to break problems into groupings of nouns or objects, a functional approach breaks the same problem into groupings of verbs or functions
- OOP = complex structures with specific manipulation functions
- FP = simple structures with generic manipulation functions
- Most OOP-specific design patterns are pretty much irrelevant in functional languages
OOP versus FP

Why FP matters?
- Some pure functional languages:
- Haskell
- Miranda
- Some impure functional languages:
- F#
- Erlang
- JavaScript
- Lisp
- Clojure
- Scala
- Python
- Ruby
- C#
- Java (since version 8)
FP languages
- First-class functions
- High-order functions
- Pure functions ⇒ no side-effects ⇒ immutability
- Recursion ⇒ No state ⇒ no variables or loops ⇒ Tail call optimization
- Lazy evaluation
- Type inference
- Lexical closures
- Pattern matching
- Partial application
- Continuations
Characteristics of FP
Bash FP
$ cat /etc/passwd | cut -f1 -d: | xargs -I{} sh -c "echo {} | grep -i -o '[aeiou]' | sort | uniq | wc -l" | sort -n | tail -1
How many different vowels has the username that has more different vowels?
in JavaScript without FP
function getMaxVowels() {
var fs = require('fs');
var contents = fs.readFileSync('/etc/passwd', 'utf8');
var userPattern = /^[^:]+/gm;
var userNames = contents.match(userPattern);
var maxVowels = 0;
for (var i = 0; i < userNames.length; i++) {
var userName = userNames[i].toLowerCase();
var countVowels = 0;
var vowels = ['a', 'e', 'i', 'o', 'u'];
for (var j = 0; j < vowels.length; j++) {
if (userName.indexOf(vowels[j]) !== -1) {
countVowels++;
}
}
if (maxVowels < countVowels) {
maxVowels = countVowels;
}
}
return maxVowels;
}
in JavaScript with FP
function readFile(fileName) {
var fs = require('fs');
return fs.readFileSync(fileName, 'utf8');
}
function getUserNames() {
var contents = readFile('/etc/passwd');
var userNamePattern = /^(\w|[-])+:/gm;
return contents.match(userNamePattern).map(function(value) {
return value.replace(/:$/, '');
});
}
function getVowels(string) {
return string.split('').filter(isVowel);
}
function isVowel(char) {
return /[aeoiuAEIOU]/.test(char[0]);
}
function uniq(array) {
return array.reduce(function(result, value) {
if (result.indexOf(value) === -1) {
result.push(value);
}
return result;
}, []);
}
function compareNumbers(numberA, numberB) {
return numberA - numberB;
}
function last(array) {
return array[array.length - 1];
}
function count(array) {
return array.length;
}
function maxVowels(array) {
var vowelsCount = array.map(getVowels).map(uniq).map(count).sort(compareNumbers);
return last(vowelsCount);
}
maxVowels(getUserNames());
Basic Functions: Passing and returning
parameters
function greet() {
return 'Hi!';
}
function greet(name) {
return 'Hi ' + name + '!';
}
function greet(greeting, name) {
return greeting + ' ' + name + '!';
}
function greet() {
return arguments[0] + ' ' + arguments[1] + '!';
}
Fisrt-class functions
//Function declaration
function greet() {
return 'Hi!';
}
typeof greet === 'function'; //true
var greet2 = greet;
greet2 === greet; //true
//Function expression
var greet3 = function() {
return 'Hi!';
}
greet2 === greet3; //false
greet2() === greet3(); //true
High-order functions
//functions as parameters
function map(array, fn) {
var i;
var result = [];
for (i = 0; i < array.length; i++) {
result.push(fn(array[i]));
}
return result;
}
function double(x) {
return x * 2;
}
map([1, 2, 3], double); //[2, 4, 6];
//returned functions
function greet(greeting) {
return function(name) {
return greeting + ' ' + name + '!';
};
}
var sayHello = greet('Hi');
sayHello('Peter'); //"Hi Peter!"
sayHello('Anna'); //"Hi Anna!"
Variable Scope:
Global Scope
var firstName = 'Peter';
//global scope <- bad
'firstName' in window; //true
firstName; //"Peter"
window.firstName; //"Peter"
function greet() {
return 'Hi!';
}
//global scope <- bad
'greet' in window; //true
greet(); //"Hi!"
window.greet(); //"Hi!"
Variable Scope:
Missing var
function greet() {
//missing var
greeting = 'Hi!';
}
'greeting' in window; //false
greet();
'greeting' in window; //true
Variable Scope:
use strict
function greet() {
"use strict";
greeting = 'Hi!';
}
'greeting' in window; //false
try {
greet();
} catch(e) {
console.log('Error: ' + e);
}
'greeting' in window; //false
Variable Scope:
function scope
//JavaScript has function scope
function greet(firstName) {
var greeting = 'Hi';
//parametrs and inner variables are visible inside the function
console.log(greeting + ' ' + firstName + '!');
}
//Here greeting and firstName are not accesible
console.log(firstName); //error firstName is not declared
greeting = 'Bye'; //Create a global variable
"greeting" in window; //true
var greeting = 'Bye';
//Inner variables hide outer variables
function greet() {
var greeting = 'Hi';
console.log(greeting); //"Hi"
}
greet(); //"Hi"
console.log(greeting); //"Bye"
Variable Scope:
block scope
//javascript has no block scope
function sum(from, to) {
var result = 0;
for (var i = from ; i <= to; i++) {
result += i;
}
//console.log(i) -> to + 1 -> i variable is accessible outside the loop
return result;
}
for (var i = 0; i < 10; i++) {
....
}
for (var i = 0; i < 10; i++) { //bad: No complaints but it is a variable redeclaration
....
}
Variable hoisting
function greet() {
console.log(greeting); //undefined -> Declarations are hoisted
var greeting = 'Hi';
console.log(greeting); //Hi
}
function greet() {
//Internally this is what is happening
var greeting;
console.log(greeting);
greeting = 'Hi';
console.log(greeting); //Hi
}
Function hoisting
function greet(isHello) {
if (isHello) {
function g(name) {
return 'Hi ' + name + '!';
}
} else {
function g(name) { //functions declarations are hoisted too -> Bad: redefinition
return 'Bye ' + name + '!';
}
}
return g;
}
greet(true)('Peter'); //"Bye Peter!" <- unexpected?
//Fixed
function greet(isHello) {
var g;
if (isHello) {
g = function(name) {
return 'Hi ' + name + '!';
};
} else {
g = function(name) {
return 'Bye ' + name + '!';
};
}
return g;
}
//Better
function greet(isHello) {
if (isHello) {
return function(name) {
return 'Hi ' + name + '!';
};
} else {
return function(name) {
return 'Bye ' + name + '!';
};
}
}
Closures(i)
function greet(greeting) {
return function(firstName) {
//inner functions can access outer variables -> Closure
return greeting + ' ' + firstName + '!';
};
//outer functions can not access inner variables -> Variables have function scope
}
var sayHello = greet('Hello');
sayHello('Peter'); //"Hello Peter" -> Variables survive the execution of the function.
//External variables are accessible even when the function has returned.
Closures(ii)
function greet() {
var i = 1;
for(; i <= 10; i++) {
setTimeout(function() {
console.log('Hi ' + i);
}, i * 1000);
}
}
greet(); //Hi 11 Hi 11 Hi 11 Hi 11 ...
//why? Closures access the current value of the variables ->
//Access is by reference even in primitive types
//Solution 2: Using IIFE
function greet() {
var i = 1;
for(; i <= 10; i++) {
(function(i) {
setTimeout(function() {
console.log('Hi ' + i);
}, i * 1000);
}(i));
}
}
greet(); //Hi 1 Hi 2 Hi 3 Hi 4 ...
//Why does this work? Now i inner variable is a copy of the outer i variable ->
//Primitive variables are passed by value
//Solution 1: Extracting the internal function of the loop
function show(msg) {
return function() {
console.log(msg);
};
}
function greet() {
var i = 1;
for(; i <= 10; i++) {
setTimeout(show('Hi ' + i), i * 1000);
}
}
greet(); //Hi 1 Hi 2 Hi 3 Hi 4 ...
//Why does this work? show(msg) is executed synchronously ->
//i has the correct value ->
//setTimeout executes the inner function returned by show(msg)
FP in practice
function multiply(x, y) {
return x * y;
}
multiply(2, 5); //10
//code reuse
function double(x) {
return multiply(2, x);
}
double(5); //10
//another cool way -> Partial application
var double = multiply.bind(null, 2);
double(5); //10
FP in practice
//Private static variables
var count = (function() {
var counter = 0;
return function() {
return ++counter;
};
}());
count(); //1
count(); //2
count(); //3
FP in practice
function double(x) {
return 2 * x;
}
function addThree(x) {
return 3 + x;
}
//composition
double(addThree(5)); //16
//Generic composition: compose two functions with one argument
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
var addThreeThenDouble = compose(double, addThree);
addThreeThenDouble(5); //16
FP in practice
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
//Composite is great but difficult to read in JavaScript
multiply(add(multiply(2, 4), 5), 3); //39
//chainable functions
function chain(fns) {
var _fns = {};
var result;
fns.forEach(function(fn) {
_fns[fn.name] = function() {
var args = Array.prototype.slice.call(arguments);
if (result !== undefined) {
args = [result].concat(args);
}
result = fn.apply(null, args);
return _fns;
};
});
_fns.value = function() {
var _result = result;
result = undefined;
return _result;
};
return _fns;
}
var c = chain([add, multiply]);
//Fluent style
c.multiply(2, 4).add(5).multiply(3).value(); //39
FP in practice
//Lazy chainable version
function chain(fns) {
var _fns = {};
var _chain = [];
fns.forEach(function(fn) {
_fns[fn.name] = function() {
var args = Array.prototype.slice.call(arguments);
_chain.push(function() {
return fn.apply(null, Array.prototype.slice.call(arguments).concat(args));
});
return _fns;
};
});
_fns.execute = function() {
if (_chain.length === 0) {
return;
}
var result = _chain.slice(1).reduce(function(result, fn) {
return fn(result);
}, _chain[0]());
_chain = [];
return result;
};
return _fns;
}
FP in practice
//classical way
function double(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(arr[i] * 2);
}
return result;
}
double([1, 2, 3, 4]); //[2, 4, 6, 8];
function isEven(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(arr[i] % 2 === 0);
}
return result;
}
//Bad code reuse -> same structure, different function -> Break the single responsibility principle
isEven([1, 2, 3, 4]); //[false, true, false, true]
function double(x) {
return 2 * x;
}
function isEven(x) {
return x % 2 === 0;
}
//creates a new array with the results of calling a provided function on every element in the array
function map(arr, fn) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(fn(arr[i]));
}
return result;
}
map([1, 2, 3, 4], double); //[2, 4, 6, 8]
map([1, 2, 3, 4], isEven); //[false, true, false, true]
//native JavaScript map
[1, 2, 3, 4].map(isEven); //[false, true, false, true]
FP in practice
//Intrusive and no reusable solution
function sum(arr) {
var result = 0;
var i;
for (i = 0; i < arr.length; i++) {
result += arr[i];
}
return result;
}
sum([1, 2, 3, 4]); //10
function sum(x, y) {
return x + y;
}
function reduce(arr, fn, initValue){
var i = 0;
var result = initValue !== undefined ? initValue : arr[i++];
for(; i < arr.length; i++){
result = fn(result, arr[i]);
}
return result;
}
reduce([1, 2, 3, 4], sum, 0); //10
[1, 2, 3, 4].reduce(sum, 0); //10
FP in practice
//Bad -> intrusive
function evens(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
result.push(arr[i]);
}
}
return result;
}
evens([1, 2, 3, 4]); //[2, 4]
function isEven(x) {
return x % 2 === 0;
}
function filter(fn, arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
if (fn(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
filter(isEven, [1, 2, 3, 4]); //[2, 4]
var evens = filter.bind(null, isEven);
evens([1, 2, 3, 4]); //[2, 4]
[1, 2, 3, 4].filter(isEven); //[2, 4]
FP in practice
//Why use loops at all?
function filter(fn, arr) {
var result = [];
arr.forEach(function(value) {
if (fn(value)) {
result.push(value);
}
});
return result;
}
//forEach does not return a value -> This is not very FP approach
function filter(fn, arr) {
return arr.reduce(function(result, value) {
return fn(value) ? result.concat(value) : result;
}, []);
}
FP in practice
//Classical way
function diagonalSum(matrix) {
var i;
var sum = 0;
for (i = 0; i < matrix.length; i++) {
sum += matrix[i][i];
}
return sum;
}
diagonalSum([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); // 1 + 5 + 9 = 15
function zipWith(fn, array1, array2) {
var result = [];
var i = 0;
var size = Math.min(array1.length, array2.length);
while (i < size) {
result.push(fn(array1[i], array2[i]));
i++;
}
return result;
}
function getArrayValue(arr, index) {
return arr[index];
}
//other way: Object.keys(arr)
function indexes(arr) {
return arr.map(function(value, index) {
return index;
});
}
function sum(x, y) {
return x + y;
}
function diagonalSum(matrix) {
return zipWith(getArrayValue, matrix, indexes(matrix)).
reduce(sum);
}
FP in practice
//Classical way
function reverse(arr) {
var result = [];
var i = arr.length - 1;
for (; i >= 0; i--) {
result.push(arr[i]);
}
return result;
}
reverse([1, 2, 3, 4]); //[4, 3, 2, 1]
//Using reduce function
function reverse(arr) {
return arr.reduce(function(result, value) {
return [value].concat(result);
}, []);
}
//Recursive way
function reverse(arr) {
return arr.length === 0 ? arr : reverse(arr.slice(1)).concat(arr[0]);
}
//Reverse native function
[1, 2, 3, 4].reverse(); //Warning: Array.prototype.reverse produce a state mutation
Exercise
arr.reduce(callback[, initialValue]);
where callback receives: callback(previousValue, currentValue, index, array);
Implement the diagonalSum() function using arr.reduce() method
Help: This is the declaration of reduce() method:
You can see a solution here
Exercise
function head(arr) {
return arr[0];
}
function tail(arr) {
return arr.slice(1);
}
function last(arr) {
return arr[arr.length - 1];
}
function first(arr) {
return arr.slice(0, arr.length - 1);
}
Implement a pure functional version of the zipWith() function.
Help: You can use some of these helper functions:
You can see a solution here
Exercise
function map(arr, fn) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(fn(arr[i]));
}
return result;
}
Implement map() function using reduce() function
As a remainder this is our actual implementation of the map() function:
You can see a solution here
Exercise
function reduce(arr, fn, initValue){
var i = 0;
var result = initValue !== undefined ? initValue : arr[i++];
for(; i < arr.length; i++){
result = fn(result, arr[i]);
}
return result;
}
Implement a pure functional version of the reduce() function
As a remainder this is our actual implementation of the reduce() function:
Note: For a cleaner implementation, suppose that initValue param is always passed.
You can see a solution here
Exercise
frequency(['Peter', 'Anna', 'Rose', 'Peter', 'Peter', 'Anna']); //[["Anna", 2], ["Peter", 3], ["Rose", 1]]
frequency([1, 10, 12, 2, 1, 10, 2, 2]); //[[1, 2], [2, 3], [10, 2], [12, 1]]
Implement the frequency(arr, options) function.
The function takes an array and calculates the frequency of each value of the array sorted naturally. For example:
Optionally, the function can take sorting and grouping criterias. See CodeWars kata
Exercise (hard)
//SELECT * FROM numbers
var numbers = [1, 2, 3, 4, 5, 6];
query().select().from(numbers).where(isEven).execute(); //[2, 4, 6]
function isEven(number) {
return number % 2 === 0;
}
If you have completed the previous exercise, this is the continuation. Resolve Functional SQL kata:
JavaScript OP
JavaScript Objects
//objects are maps of (key, value) pairs
var obj = {
x: 1,
y: 2
};
//keys are always strings
var obj = {
1: 1000, //The key is "1" not 1. Non string keys are converted to strings
2: -200
};
//quotes are optional
var obj = {
"1": 1000,
"2": -200
};
//values can be of any type
var obj = {
foo: 1000,
bar: 'Peter',
baz: function() {},
zot: [1, 2, 3],
foobar: /^a/
};
Getting single values
var obj = {
x: 1,
y: 2
};
//You can use dot notation...
obj.x; //1
//...or array notation
obj["x"]; //1
//array notation is useful with expressions
var key = 'x';
obj[key]; //1
Getting multiple values
var obj = {
x: 1,
y: 2
};
//You can use for..in loop...
for (var key in obj) {
obj[key];
}
//...but FP style is preferred
Object.keys(obj).forEach(function(key) {
obj[key];
});
//Important: key iteration order is not guaranteed.
Dynamic objects
var obj = {
x: 1,
y: 2
};
"x" in obj; //true
"z" in obj; //false
//keys can be added...
obj.z = 23;
"z" in obj; //true
//...or removed
delete obj.z;
"z" in obj; //false
//Common error: This do not remove the key
obj.x = undefined;
"x" in obj; //true
//Rather, it assigns the undefined value
obj.x === undefined; //true
Expressions in keys
//You can not define a key in this way
var key = "x" + "y"
var obj = {
key : 3 //the key is "key" and not "xy";
};
//..neither this way is valid
var obj = {
"x" + "y": 3 //expressions are not allowed here
};
//But you can do this
var obj = {};
obj["x" + "y"] = 3;
//Attention
var obj = {};
var arr = [1, 2];
obj[arr] = 4;
"1,2" in obj; //true
var obj2 = {};
obj[obj2] = 3;
"[object Object]" in obj; //true
//Remember: keys are converted to strings
Arrays
//Arrays are a special type of objects
typeof [] === 'object'; //true
//Arrays can have holes
var arr = [1,,,2];
//indexes are keys
"0" in arr; //true
//holes are not keys
"1" in arr; //false
//holes are equal to undefined
arr[1] === undefined; //true
//but careful: It is not the same be undefined than it be equal to undefined
arr[1] = undefined; //this is not a hole
arr[1] === undefined; //true
"1" in arr; //true <- this is the different
//Arrays are dynamic
var arr = [1, 2, 3];
arr[500] = 4; //from index 3 to index 499 there are holes
//The length property is not what it seems
arr.length; //501
Arrays (ii)
//You should not use for..in loop to iterate through arrays...
//because the iteration order is not guaranteed
//There are plenty of methods to iterate over arrays:
//forEach, map, reduce, filter, some, every, ...
//Careful: delete comand make holes, it does not remove the element
var arr = [1, 2, 3];
delete arr[3];
arr.length; //3
//To remove properly, you should use splice method instead
arr.splice(2, 1);
arr.length; //2
Object vs. primitive types
//As in Java, JavaScript difference between objects and primitive types.
//Primitive types are compared/passed by value
"Peter" === "Peter"; //true
1 === 1; //true
false === false; //true
//Objects types are compared/passed by reference
{} === {}; //false
[1] === [1]; //false
function(){} = function(){}; //false
//You can use primitive as objects (I do not think this is helpful at all)
new String('Peter') === new String('Peter'); //false
//You can extract the primitive of an object
new String('Peter').valueOf() === new String('Peter').valueOf(); //true
//Or convert to a primitive
String(1) === String(1); //true
Object vs. primitive types (ii)
//Primitive types have no methods but are converted to objects when used as such
"Peter".toUpperCase(); //PETER
//The wrapper object is deleted when the method returns
//Therefore the primitive may not have keys
//as they are added to the discarted wrapper object
var person = "Peter";
person.age = 3;
person.age; //undefined
"age" in person; //error <- person is primitive
//However, this can be done with objects
var person = new String('Peter');
person.age = 3;
"age" in person; //true
=== vs ==
// == do coercion over primitives
// All primitives are truthy except: 0 NaN '' false null undefined
0 == false; //true
null == undefined; //true
false == false; //true
Boolean(1) == Boolean(0); //false
NaN == NaN; //false <- wierd
[1] == 1; // true
!!0 == !!''; //true
//Objects are never coerced
new Boolean(false) == new Boolean(false); //false
//Rule of thumb: Always use ===
comparations with null
//Be careful with this:
function fn(x) {
if (x) {...}
}
fn(false); //Probably this work as you are expecting, but ...
fn(0); //What about this? Are you sure 0 is false?
//Better
if (x !== false)
//Same considerations can be done with
if (!x) {...}
//Better
if (x === false) {...}
//To check nullity, do not use
if (!x) {...}
//Instead use
if (x === undefined || x === null) {...}
//Rule of thumb: prefer undefined over null. Then you can use
if (x === undefined) {...}
//Be careful with this
fn(isEven) {
isEven = isEven || true; //bad: there is no way to assign false
}
fn(false); //isEven will be true
fn('Peter'); //isEven will be "Peter"
//Corrected and more secure
fn(isEven) {
isEven = !!isEven || false; //truthy values will be true and falsy values will be false
}
Creating objects
- JavaScript is a very powerful and flexible language
- Objects can be created in different ways
- In this section we are going to discuss a few of them
The problem of creating objects
//Note that we are using the object 'this'. We will talk more about 'this' later. By now you avoid making too many assumptions about it and its behavior, because it is quite different from other languages
var person1 = {
name: 'peter',
greet: function() {
return 'Hi ' + this.name + '!';
}
};
We know that objects can be created literally
This approach has some drawbacks:
- Code duplication
var person2 = {
name: 'Anna',
greet: function() {
return 'Hi ' + this.name + '!';
}
};
person1.name = 'Michael';
person1.lastName = 'Smith'; //bad, but possible
delete person1.name; //worse
Module pattern based solution
function createPerson(spec) {
"use strict";
var name = spec.name;
return Object.freeze({
greet: function() {
return 'Hi ' + name + '!';
}
}); // Object.freeze() makes public interface immutable
}
Module pattern based solution (ii)
var person1 = createPerson({name: 'Peter'});
var person2 = createPerson({name: 'Anna'});
person1.greet(); //"Hi Peter!"
person1.name; //undefined
delete person1.greet;
person1.greet(); //"Hi Peter!"
person1.lastName = 'Smith';
person1.lastName; //undefined
Advantages
- Code reuse
- Private access
- Immutable interface
- Avoid this and prototypes
var person1 = createPerson({name: 'Peter'});
var person2 = createPerson({name: 'Anna'});
person1.greet === person2.greet; //false
person1 instanceof Person; //Error Person is not defined
Disadvantages
- Waste of memory
- Lost class concept
Constructor functions solution
function Person(name) {
this.name = name;
this.greet = function() {
return 'Hi ' + this.name + '!';
};
}
var person1 = new Person('Peter');
person1.greet(); //"Hi Peter!"
//This solution exposes the name variable
person1.name; //"Peter"
//But we can avoid this easily
function Person(name) {
var _name = name;
this.greet = function() {
return 'Hi ' + _name + '!';
};
//Object.freeze(this); //To add immutability
}
In JavaScript, any function can be used to construct objects if it is called with the 'new' operator
It is generally accepted that the name of a constructor function begin with a capital letter
Constructor functions are similar to constructors in classical OOP languages
Constructor functions solution (ii)
var person1 = new Person('Peter');
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
Advantages
- Code reuse
- Private access
- Immutable interface
- Avoid prototypes
- instanceof works
var person1 = new Person('Peter');
var person2 = new Person('Anna');
person1.greet === person2.greet; //false
Disadvantages
- Waste of memory
- Use of this is error prone
prototype based solution
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return 'Hi ' + this.name + '!';
};
var person1 = new Person('Peter');
person1.greet(); //"Hi Peter!"
JavaScript can emulate the class concept through prototypes
prototype based solution (ii)
var person1 = new Person('Peter');
person1.greet === person2.greet; //true
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
Advantages
- efficient use of memory
- instanceof works
var person1 = new Person('Peter');
person1.name = 'Anna';
//Prototype does not change. Instead, a public `greet` variable is added to `this` object <- danger
person1.greet = function() {
return 'Bye!';
}
Disadvantages
- Public access
- Mutable object
- Use of this is error prone
Object.create() based solution
function createPerson(name) {
var personPrototype = {
greet: function() {
return 'Hi ' + this.name + '!';
}
};
return Object.create(personPrototype, {
name: {
value: name,
writable: true
}
});
}
var person1 = new createPerson('Peter');
person1.greet(); //"Hi Peter!"
Object.create() based solution (ii)
var person1 = new Person('Peter');
person1.greet === person2.greet; //true
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
Advantages
- Similar to prototype solution
- JavaScript preferred way
var person1 = new Person('Peter');
person1.name = 'Anna';
//Prototype does not change. Instead, a public `greet` variable is added to `this` object <- danger
person1.greet = function() {
return 'Bye!';
}
Disadvantages
- Weird for newbies
- Public access
- Use of this is error prone
What solution should I use?
- There is no right answer to this question
- It depends on things like:
- Personal code styling
- Available memory
- Is it important to protect the inadequate access?
- Etc.
- Anyway, you should know all methods
- I think I prefer the first way (see this Douglas Crockford video).
- Some key points:
- Learn the JavaScript way and avoid doing it the way you are used to in other languages
- JavaScript has bad parts and you are not forced to use them. For example: 'this' and 'new' were added to mimic the Java language but in javascritpt really are not necessary
Functions are objects
function greet(firstName) {
return 'Hi ' + first.name + '!';
}
greet instanceof Function; //true
greet.name; //"greet"
greet.length; //1 <- named parameters
//Other attributes
greet.prototype
greet.call(...);
greet.apply(...);
greet.bind(...);
//adding attributes
greet.greeting = 'Hi';
greet.greeting; //"Hi"
this object
function Person(firstName) {
this.firstName = firstName;
this.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
}
var person1 = new Person('Peter');
//Seems like Java
person1.firstName; //"Peter"
//But something weird happens if we forget new operator
var person2 = Person('Anna'); //No complainst
//Person is called as a regular function that returns ....
person2; //undefined
//firstName is attached to the global object
"firstName" in window; //true -> bad
this object (ii)
function Person(firstName) {
"use strict";
this.firstName = firstName;
...
}
var person2 = Person('Anna'); //"TypeError: Cannot set property 'firstName' of undefined"
Fix one:
function Person(firstName) {
if (this instanceof Person) {
this.firstName = firstName;
} else {
return new Person(firstName);
}
}
var person2 = Person('Anna');
person2.firstName; //"Anna"
Fix two:
function Person(firstName) {
//Avoid using this object
var _firstName = firstName;
return {
getFirstName: function() {
return _firstName;
}
};
}
//We have seem this before:...
//rename Person to createPerson
var person2 = Person('Anna');
person2.greet('Hi'); //"Hi Anna!"
Fix three:
this object (iv)
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.greet = function(greeting) {
return return greeting + ' ' + this.firstName + '!';;
};
var person1 = new Person('Peter');
//Seems it works
person1.greet(); //"Hi Peter!"
//But it does not
var greet = Person.prototype.greet;
greet('Hi'); //"Hi undefined!"
//Why?
//greet is a global function
//when greet is executed, this becomes the global object <- weird
this object (v)
var person1 = new Person('Peter');
var greet = Person.prototype.greet;
//When greet function is called, it sets the this object to person1 passing 'Hi' as argument
greet.call(person1, 'Hi'); //"Hi Peter!"
Fix one:
//apply is like call but the parameters are passed inside an array -> Useful in some situations
greet.apply(person1, ['Hi']); //"Hi Peter!"
Fix two:
//bind returns a function with the 'this' object applied to the first param for later execution
greetPeter = greet.bind(person1);
greetPeter('Hi'); //"Hi Peter!"
greetPeter('Bye'); //"Bye Peter"
Fix three:
//bind allows partial application parameters
greetPeter = greet.bind(person1, 'Hi');
//greeting parameter has been fixed previously
greetPeter(); //"Hi Peter!"
Fix four:
this object (vi)
function Person(firstName) {
this.firstName = firstName;
this.greets = {
sayHello: function() {
return 'Hello ' + this.firstName + '!';
},
sayBye: function() {
return 'Bye ' + this.firstName + '!';
}
};
}
var person1 = new Person('Peter');
//In sayHello function, this is bound to the global object
person1.greets.sayHello('Hi'); //"Hello undefined!"
this object (vii)
function Person(firstName) {
this.firstName = firstName;
this.greets = {
sayHello: function() {
return 'Hello ' + this.firstName + '!';
}.bind(this)
...
};
}
Fix one:
function Person(firstName) {
this.firstName = firstName;
var self = this;
this.greets = {
sayHello: function() {
return 'Hello ' + self.firstName + '!';
},
...
};
}
Fix two:
person1.greets.sayHello.bind(person1)('Hi');
Fix three:
person1.greets.sayHello.call(person1, 'Hi');
Fix four:
this object (viii)
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + this.firstName + '!');
}, milliseconds);
};
}
var person1 = new Person('Peter');
person1.timedGreet('Hi', 1000); //"Hi undefined"
this object (ix)
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + this.firstName + '!');
}.bind(this), milliseconds);
};
}
Fix one:
function Person(firstName) {
this.firstName = firstName;
var self = this;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + self.firstName + '!');
}, milliseconds);
};
}
Fix two:
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds, self) {
setTimeout(function() {
console.log(greet + ' ' + self.firstName + '!');
}, milliseconds);
};
}
var person1 = new Person('Peter');
person1.timedGreet('Hi', 1000, person1);
Fix three:
JavaScript inheritance
- JavaScript is a free class language, so...
- In JavaScript there is no class inheritance
- However there are some ways to mimic class inheritance
- In any case, why inheritance is necessary?
- Code reuse
- Polymorphism
- Efficient use of memory
- But inheritance also has its drawbacks
- Lack of flexibility
- Coupling
- In fact, one of the GoF principles is:
"Favor composition over inheritance" - Many JavaScript newbies are uncomfortable with prototypes and they demand something that seems like class inheritance
- So let's discuss how to do this in JavaScript
But seriously, think twice before doing this.
JavaScript inheritance (ii)
function Person(firstName) {
this.firstName = firstName;
this.getName = function() {
return this.firstName;
};
}
Person.prototype.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
function Student(firstName, grade) {
//Questions?
//1. How could Student inherit this.getName method from Person?
//2. How could Student.prototype inherit from Person.prototype?
}
Inheriting from 'this' object
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
var student1 = new Student('Peter');
student1.firstName; //"Peter"
student1.getName(); //"Peter"
Inheriting from Parent.prototype
Student.prototype = Person.prototype;
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //true <- Student.prototype and Person.prototype are the same object
Bad solution one:
Student.prototype = new Person();
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //false
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
//Seems all is ok, but...
"firstName" in Student.prototype; //true
Bad solution two (unfortunately widely used):
Inheriting from Parent.prototype (ii)
function Person(firstName) {
this.firstName = firstName;
this.getName = function() {
return this.firstName;
};
}
Person.prototype.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
function F() {}
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //false
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
"firstName" in Student.prototype; //false
Good solution (mix the two bad solutions):
Inheriting from Parent.prototype (iii)
function inherits(Child, Parent) {
function F() {}
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
inherits(Student, Person);
Student.prototype.getGrade = function() {
return this.grade;
};
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
"firstName" in Student.prototype; //false
student1 instanceof Student; //true
student1 instanceof Person; //true
Generalizing the problem:
Inheriting from Parent.prototype (iv)
personPrototype = {
greet: function(greeting) {
return greeting + ' ' + this.firstName + '!';
}
};
var studentPrototype = Object.create(personPrototype, {
getGrade: {
value: function() {
return this.grade;
}
}
});
function createStudent(firstName, grade) {
return Object.create(studentPrototype, {
firstName: {
value: firstName
},
grade: {
value: grade
}
});
}
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
Better approach (using Object.create):
Avoiding classical heritage
function greet(person, greeting) {
return greeting + ' ' + person.firstName + '!';
}
var createStudent = function(firstName, grade) {
var self = {
firstName: firstName,
grade: grade
};
return Object.freeze({
getGrade: function() {
return self.grade;
},
greet: greet.bind(null, self)
});
};
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
//Memory waste
var student2 = createStudent('Peter', 'High school');
student1.greet === student2.greet; //false
student1.getGrade === student2.getGrade; //false
Why mimic class-based languages if JavaScript is classless?
Avoiding classical heritage (ii)
function greet(greeting) {
return greeting + ' ' + this.firstName + '!';
}
var createStudent = (function() {
var studentPrototype = {
greet: greet,
getGrade: function() {
return this.grade;
}
};
return function(firstName, grade) {
return Object.create(studentPrototype, {
firstName: {
value: firstName
},
grade: {
value: grade
}
});
};
}());
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
var student2 = createStudent('Peter', 'High school');
student1.greet === student2.greet; //true
student1.getGrade === student2.getGrade; //true
//public access
"firstName" in student1; //true
"grade" in student1; //true
Prototype solution:
Array-like objects
var persons = {
0: 'Peter',
1: 'Anna',
2: 'Michael',
length: 3
};
Array.prototype.map.call(persons, function(name) {
return name.toUpperCase();
}); //["PETER", "ANNA", "MICHAEL"]
We can benefit from the array methods in objects that seem arrays
Array-like objects (ii)
function add(x, y) {
return x + Number(y);
}
var number = "1234";
Array.prototype.reduce.call(number, add, 0); //10
Strings are array-like objects
function add(x, y) {
return x + y;
}
function sum() {
return Array.prototype.reduce.call(arguments, add, 0);
}
sum(1, 2, 3, 4); //10
arguments is an array-like object
Array-like objects (iii)
Array.prototype.forEach.call(document.querySelectorAll('div'), addClickEvent);
function addClickEvent(div) {
div.addEventListener('click', function(event) {
event.target.style.backgroundColor = 'Green';
});
}
DOM Collections are array-like objects
apply as arguments
function min() {
return Math.min.apply(null, arguments);
}
min(1, 2, 3); //1
We can use Function.prototype.apply with arguments
Async JavaScript
- In JavaScript when a slow I/O action is completed, it is added to a queue called Event Loop (see this video)
- Advantages:
- No time wasted waiting
- No race conditions
- No deadlocks
- Super easy: No synchronized variables, semaphores, etc
- Disadvantages:
- JavaScript apps are single threaded: waste CPU
- No real concurrency
Event Loop
In JavaScript there are different ways to perform pseudo-concurrency:
- Callbacks
- Promises
- Events
- Generators
Async programming in JavaScript
Callbacks allow us to decide the continuation function when the asynchronous operation is complete.
Callbacks
setTimeout(function() {
console.log('Done!');
}, 1000);
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
$.ajax(url, {
success: function(...) { ... },
error: function(...) { ... }
});
Callbacks (ii)
Callbacks are easy but they have some drawbacks:
- Callback hell
- Callback functions can not return a value (passing style)
- Errors get lost easily (try-catch does not work)
- Doing sequences in parallel is hard
- Difficult to debug
One solution: Async.js
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
Promises seem synchronous code
Promises
checkUser(credentials).then(loginSuccess, loginError).then(gotoMainPage);
$.ajax(url).done(function() {...}).fail(function() {...});
Q: a promises implementation
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
Example of naive promise implementation
Promises (ii)
function Promise() {
this._dones = [];
this._errors = [];
}
Promise.prototype.done = function (onSuccess) {
this._dones.push(onSuccess);
return this;
};
Promise.prototype.error = function (onError) {
this._errors.push(onError);
return this;
};
Promise.prototype.resolve = function (result) {
this._complete('resolve', result);
};
Promise.prototype.reject = function (error) {
this._complete('reject', error);
};
Promise.prototype._complete = function (action, data) {
// disallow multiple calls to resolve or reject
this.resolve = this.reject = function () {
throw new Error('Promise already completed.');
};
var functions = action === 'resolve' ? this._dones : this._errors;
functions.forEach(function(fn) {
fn(data);
});
this._dones = [];
this._errors = [];
};
Example of use naive promise implementation
Promises (iii)
function doWork(time, callback) {
if (typeof time !== 'number' || time <= 0) {
setTimeout(function() {
callback('Bad format', null);
}, 200); //Simulates a failure
} else {
setTimeout(function() {
callback(null, 'Done!');
}, time); //Simulates a long work
}
}
function work(time) {
var promise = new Promise();
doWork(time, function(error, result) {
if(error) {
promise.reject(error);
} else {
promise.resolve(result);
}
});
return promise;
}
work(2000).done(function(result) {
console.log(result);
}).error(function(error) {
console.log(error);
});
work(4000).done(function(result) {
console.log(result);
}).done(function(result) {
console.log("In second success handler");
}).error(function(error) {
console.log(error);
});
work(-2000).done(function(result) {
console.log(result);
}).error(function(e) {
console.log("In first error handler");
}).error(function(e) {
console.log(e);
});
Promises (iv)
Why promises are so cool?
- Fluent/Functional style
- Easy aggregation/composition
- Style-error bubbling
Events are great when there are multiple receivers (listeners) interested on the response:
Events
document.addEventListener('click', function(event) {....});
$('#table').on( "click", function() {
...
});
var server = http.createServer(function (req, res) {
...
req.on('data', function (chunk) {
...
});
req.on('end', function () {
...
});
...
});
Example of naive events implementation
Events (ii)
function EventEmitter() {
var events = {};
this.addListener = function (event, listener) {
if (!events[event]) {
events[event] = [];
}
events[event].push(listener);
};
this.emitEvent = function (event) {
if (events[event]) {
events[event].forEach(function(listener) {
listener(event);
});
}
};
}
Example of use naive events implementation
Events (iii)
var ee = new EventEmitter();
function listener1(event) {
console.log('The ' + event + ' event has been recived by listener1.');
}
function listener2(event) {
console.log('The ' + event + ' event has been recived by listener2.');
}
ee.addListener('foo', listener1); //Receiver
ee.addListener('foo', listener2); //Receiver
ee.addListener('bar', listener1); //Receiver
ee.emitEvent('foo'); //Emitter
ee.emitEvent('bar'); //Emitter
Events (iv)
Benefits of using events
- Multiple receivers
- Easy add/remove listeners
- Decoupling between emitter and receiver
Cons of using events
- Difficult to debug/test
- Easy to lost control: chain of events
- Difficult to handle race conditions
New player in ECMAScript 6
Generators
function* foo(x) {
yield x + 1;
var y = yield null;
return x + y;
}
var gen = foo(5);
gen.next(); // { value: 6, done: false }
gen.next(); // { value: null, done: false }
gen.send(8); // { value: 13, done: true }
Co: a generators implementation
co(function *(){
try {
var res = yield get('http://badhost.invalid');
console.log(res);
} catch(e) {
console.log(e.code) // ENOTFOUND
}
})()
Generators (ii)
Pros
- Seem synchronous code
- Simplified lazy evaluation
- Allow infinite sequences
Cons
- Immature technology
- Awful performance
- Are they really neccesary?
Example
var findLargest = require('./findLargest');
findLargest('./path/to/dir', function (er, filename) {
if (er) return console.error(er);
console.log('largest file was:', filename);
});
Example (ii)
Nested callback approach:
var fs = require('fs');
var path = require('path');
module.exports = function (dir, cb) {
fs.readdir(dir, function (err, files) {
if (err) return cb(err);
var counter = files.length;
var errored = false;
var stats = [];
files.forEach(function (file, index) {
fs.stat(path.join(dir, file), function (er, stat) { // <- parallel process
if (errored) return;
if (er) {
errored = true;
return cb(er);
}
stats[index] = stat;
if (--counter == 0) {
var largest = stats
.filter(function (stat) { return stat.isFile(); })
.reduce(function (prev, next) {
return prev.size > next.size ?
prev :
next;
}, 0);
cb(null, files[stats.indexOf(largest)]);
}
});
});
});
};
Example (iii)
Modular callback approach:
var fs = require('fs');
var path = require('path');
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) {
if (er) return cb(er);
var paths = files.map(function (file) {
return path.join(dir,file);
});
getStats(paths, function (er, stats) {
if (er) return cb(er);
var largestFile = getLargestFile(files, stats);
cb(null, largestFile);
});
});
};
function getStats (paths, cb) {
var counter = paths.length;
var errored = false;
var stats = [];
paths.forEach(function (path, index) {
fs.stat(path, function (er, stat) {
if (errored) return;
if (er) {
errored = true;
return cb(er);
}
stats[index] = stat;
if (--counter == 0) cb(null, stats);
});
});
}
function getLargestFile (files, stats) {
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
}
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
Example (iv)
Async library approach:
var fs = require('fs');
var async = require('async');
var path = require('path');
module.exports = function (dir, cb) {
async.waterfall([
function (next) {
fs.readdir(dir, next)
},
function (files, next) {
var paths = files.map(function (file) { return path.join(dir, file) });
async.map(paths, fs.stat, function (er, stats) {
next(er, files, stats);
});
},
function (files, stats, next) {
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
next(null, files[stats.indexOf(largest)]);
}
], cb);
}
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
Example (v)
Q promises library approach:
var fs = require('fs');
var path = require('path');
var Q = require('q');
var fs_readdir = Q.denodeify(fs.readdir);
var fs_stat = Q.denodeify(fs.stat);
module.exports = function (dir) {
return fs_readdir(dir)
.then(function (files) {
var promises = files.map(function (file) {
return fs_stat(path.join(dir,file));
});
return Q.all(promises).then(function (stats) {
return [files, stats];
});
})
.then(function (data) {
var files = data[0]
var stats = data[1]
var largest = stats.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
});
};
function max(prop, x, y) {
return x[prop] > y [prop] ? x : y;
}
function isFile(stat) {
return stat.isFile();
}
//use
var findLargest = require('./findLargest')
findLargest('./path/to/dir')
.then(function (filename) {
console.log('largest file was:', filename);
})
.catch(console.error);
Example (v)
Co generator library approach:
var co = require('co');
var thunkify = require('thunkify');
var fs = require('fs');
var path = require('path');
var readdir = thunkify(fs.readdir);
var stat = thunkify(fs.stat);
module.exports = co(function* (dir) {
var files = yield readdir(dir);
var stats = yield files.map(function (file) {
return stat(path.join(dir,file))
});
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
});
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
ECMAScript 6 is coming
Test ECMAScript 6 today
$ sudo npm install --global traceur
$ traceur --out build.js --script my_source_file.js
$ node build.js
Arrow Functions
var users = [
{ name: 'Jack', age: 21 },
{ name: 'Ben', age: 23 },
{ name: 'Adam', age: 22 }
];
console.log(users.map(function(user) { return user.age; }));// [21, 23, 22]
var ages = users.map(user => user.age);
var sum = ages.reduce((a, b) => a + b);
console.log(sum); // 66
var agesDoubled = users.map(user => {
var age = user.age;
return age * 2;
});
console.log(agesDoubled); // [42, 46, 44]
Arrow Functions (ii)
//Lexical this binding
var person = {
name: 'Peter',
getName: function() {
return this.name;
}
};
console.log(person.getName()); // "Peter"
var getPeterName = person.getName;
console.log(getPeterName()); // "undefined" [in Node]
var person = {
name: 'Peter',
getName: () => {
return this.name;
}
};
console.log(person.getName()); // "Peter"
var getPeterName = person.getName;
console.log(getPeterName()); // "Peter"
Modules
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
module math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));
// otherApp.js
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
// app.js
module math from "lib/mathplusplus";
import exp from "lib/mathplusplus";
alert("2π = " + exp(math.pi, math.e));
// Dynamic loading – ‘System’ is default loader
System.import('lib/math').then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});
Map + Set
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
Weak Maps + Weaks Sets
var map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original"); //key must be a non primitive, value can be of any type
// later
var value = map.get(element);
console.log(value); // "Original"
// later still - remove reference
element.parentNode.removeChild(element);
element = null;
value = map.get(element);
console.log(value); // undefined <- avoids memory leaks
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
Block-scoped variables
for (var i = 0; i < 10; i++);
console.log(i); // 10
for (let j = 0; j < 10; j++);
console.log(j); // Error: j is not defined
Constants
const x = 4;
x = 9; // Error: x is read-only
Classes
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);
this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
//...
}
update(camera) {
//...
super.update();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}
// Pseudo-code of Array
class Array {
constructor(...args) { /* ... */ }
static [Symbol.create]() {
// Install special [[DefineOwnProperty]]
// to magically update 'length'
}
}
// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(...args); }
}
// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2
Enhanced Object Literals
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};
Template Strings
// Basic literal string creation
`In JavaScript '\n' is a line-feed.`
// Multiline strings
`In JavaScript this is
not legal.`
// Construct a DOM query
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
Destructuring
// list matching
var [a, , b] = [1,2,3];
// object matching
var { op: a, lhs: { op: b }, rhs: c }
= getASTNode()
// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()
// Can be used in parameter position
function g({name: x}) {
console.log(x);
}
g({name: 5})
// Fail-soft destructuring
var [a] = [];
a === undefined;
// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;
Default + Rest + Spread
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3); // 15
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true); // 6
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]); //6
Iterators
//Classical way
function idMaker(){
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
var it = idMaker();
console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...
//New way
function* idMaker(){
var index = 0;
while(true)
yield index++;
}
var gen = idMaker();
console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...
Iterators (ii)
function* fibonacci() { // a generator function
let [prev, curr] = [0, 1];
for (;;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
// truncate the sequence at 1000
if (n > 1000)
break;
print(n);
}
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur }
}
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
print(n);
}
Iterators (iii)
let articleParagraphs = document.querySelectorAll("article > p");
for (let paragraph of articleParagraphs) {
paragraph.classList.add("read");
}
Comprehensions
[for (i of [ 1, 2, 3 ]) i*i ]; // [ 1, 4, 9 ]
var abc = [ "A", "B", "C" ];
[for (letters of abc) letters.toLowerCase()]; // [ "a", "b", "c" ]
var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];
[for (year of years) if (year > 2000) year]; // [ 2006, 2010, 2014 ]
var numbers = [ 1, 2, 3 ];
var letters = [ "a", "b", "c" ];
var cross = [for (i of numbers) for (j of letters) i+j];
// [ "1a", "1b", "1c", "2a", "2b", "2c", "3a", "3b", "3c" ]
Proxies
// Proxying a normal object
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};
var p = new Proxy(target, handler);
p.world === 'Hello, world!';
/ Proxying a function object
var target = function () { return 'I am the target'; };
var handler = {
apply: function (receiver, ...args) {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p() === 'I am the proxy';
Private object properties
var firstName = Symbol("first name property");
var person = {};
person[firstName] = "Nicholas";
console.log(person[firstName]); // "Nicholas"
//You need the symbol to access the property
console.log(Symbol("first name property")); //undefined
Math + Number + String + Object APIs
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
Promises
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}
var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
});
Tail Call recursion
function factorial(n, acc = 1) {
'use strict';
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
// Stack overflow in most implementations today,
// but safe on arbitrary inputs in eS6
factorial(100000);
JavaScript Testing
- Confidence
- Easier refactoring
- Less regression
- Less complexity
- TDD is fun!
Why test?
- First law: You may not write production code until you have written a failing unit test
- Second law: You may not write more of a unit test that is sufficient to fail
- Third law: You may not write more production code than is sufficient to pass the currently failing test
How to test?
- Keep test clean: Test code is just as important as production code
- Clean tests: readability
- One assert per test: Tests come to a single conclusion that is quick and easy to understand
- F.I.R.S.T.
- Fast
- Independent
- Repeatable
- Self-validating
- Timely
How to test (ii)
Better than more theory, we are going to practice TDD with an exercise. You can find the complete solution in here.
TDD by Example
This is the description:
Algebraic Data Types (not to be confused with Abstract Data Types, ADT) are composed types (normally recursively) and for those who have defined a serie of operations.
For example in Haskell, natural numbers can be defined in this way:
data Nat = Zero | Succ Nat
So:
zero = Zero
one = Succ Zero
two = Succ (Succ Zero)
three = Succ (Succ (Succ Zero))
...
The objetive of this kata is to define an algebra for the Nat data type
.
TDD by Example (ii)
In JavaScript
we are going to use the next definition for natural numbers:
function zero(){}
function succ(nat) {
return function() {
return nat;
};
}
zero
and succ()
are constructor functions of the Nat
data type. A nat number could be the zero
function (although you will never really call this function) or a function obtained by calling the function succ()
with the previous natural number. This definition corresponds roughly to the definition of Church numbers
zero nat number corresponds to zero
function
one nat number is obtained by calling succ(zero)
two nat number is obtained by calling succ(succ(zero))
... and so on
TDD by Example (iii)
Or more precisely:
var one = succ(zero);
var two = succ(one) = succ(succ(zero));
...
The algebra are composed by the next operations:
natToInt(nat) -> int // obtains the integer value of the nat number. For example natToInt(succ(succ(zero)) returns 2
intToNat(int) -> nat // converts an integer to a natural value. For example intToNat(2) does the same that succ(succ(zero))
toString(nat) -> string // returns a string representation of the natural value (see examples below)
add(nat1, nat2) -> nat // given two natual numbers, returns the natural sum of them
mul(nat1, nat2) -> nat // multiplies two naturals
compareTo(nat1, nat2) -> int // returns a number less than zero when nat1 < nat2; greater than zero when nat1 > nat2; and 0 when nat1 === nat2
TDD by Example (iv)
Some examples:
natToInt(zero); // 0
natToInt(succ(zero)); // 1
natToInt(succ(succ(zero))); // 2
intToNat(0) === zero; // true
intToNat(1); // is the same than succ(zero)
intToNat(2); // is the same than succ(succ(zero))
natToInt(intToNat(10)); // 10
toString(zero); // "zero"
toString(succ(zero)); // "succ(zero)"
toString(succ(succ(zero))); // "succ(succ(zero))"
add(zero, zero) === zero; // true
add(succ(zero), zero); // is the same that succ(zero)
add(succ(zero), succ(zero)); // is the same that succ(succ(zero))
add(succ(zero), succ(succ(zero))); // is the same that succ(succ(succ(zero)))
natToInt(add(zero, succ(zero))); // 1
natToInt(add(intToNat(10), intToNat(15))); // 25
mul(zero, zero) === zero; // true
mul(suc(zero), zero) === zero; // true
natToInt(mul(intToNat(10), intToNat(15))); // 150
compareTo(zero, zero); // 0
compareTo(zero, succ(zero)) < 0; // true
compareTo(succ(zero), 0) > 0; // true
compareTo(suc(zero), succ(zero)); // 0
natToInt(compareTo(intToNat(10), intToNat(15))) < 0; // true
TDD by Example (v)
Additional notes:
- Function implementations must be pure (no mutable state and no side effects). So iterative implementations are not allowed. Do not use
while
orfor
keywords in your solution. - A simple but inefficient implementation of
add()
operation could be:
function add(nat1, nat2) {
return intToNat(natToInt(nat1) + natToInt(nat2));
}
But you must not use arithmetic operators (+, - , *, /, %, ...) in implementing the functions add()
and mul()
.
TDD by Example (vi)
Let's start with the first function: natToInt()
This could be the very first dummy test:
var assert = require("assert");
var zero = function(){};
function succ(nat) {
return function() {
return nat;
};
}
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0);
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
1) should return 0 for the zero function
0 passing (6ms)
1 failing
1) Nat numbers tests natToInt() tests should return 0 for the zero function:
ReferenceError: natToInt is not defined
at Context.<anonymous> (/home/mint/test.js:14:20)
TDD by Example (vii)
Now, we can write a simple implementation to pass the test:
...
function natToInt(nat) {
return 0;
}
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0, 'natToInt(zero) should return 0');
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1 passing (8ms)
TDD by Example (viii)
This implementation is not very useful since it only applies to the number zero. So we go back to cause a failure by adding a test case.
...
var one = succ(zero);
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0, 'natToInt(zero) should return 0');
});
it('should return 1 for the one nat number', function(){
assert.equal(natToInt(one), 1, 'natToInt(one) should return 1');
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1) should return 1 for the one nat number
1 passing (7ms)
1 failing
1) Nat numbers tests natToInt() tests should return 1 for the one nat number:
AssertionError: natToInt(one) should return 1
at Context.<anonymous> (/home/mint/test.js:24:14)
TDD by Example (ix)
We can fix this by changing the implementation:
...
function natToInt(nat) {
if (nat === zero) return 0;
else return 1;
}
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
✓ should return 1 for the one nat number
2 passing (8ms)
TDD by Example (x)
Easily we can break the implementation by adding a new test:
...
var one = succ(zero);
var two = succ(one);
...
it('should return 2 for the two nat number', function(){
assert.equal(natToInt(two), 2, 'natToInt(two) should return 2');
});
This procedure seems pointless. But it is just the opposite, because it helps us reason about the correct solution.
TDD by Example (xi)
Moreover, the solution is not as simple as it seems:
...
function natToInt(nat) {
if (nat === zero) return 0;
if (nat === one) return 1;
else return 2;
}
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1) should return 1 for the one nat number
✓ should return 2 for the one nat number
2 passing (8ms)
1 failing
1) Nat numbers tests natToInt() tests should return 1 for the one nat number:
AssertionError: natToInt(one) should return 1
TDD by Example (xii)
Why? Very easy, check out the code:
var zero = function(){};
var one = succ(zero);
var two = succ(one);
function succ(nat) {
return function() {
return nat;
};
}
function natToInt(nat) {
if (nat === zero) return 0;
if (nat === one) return 1;
else return 2;
}
succ(zero) === succ(zero); //false
zero === zero; //true
TDD by Example (xiii)
So let's make a first attempt to seriously implement the natToInt() function:
function natToInt(nat) {
var result = 0;
while (nat !== zero) {
nat = nat();
result++;
}
return result;
}
This is not a very FP approach, but it is working.
However, it violates one of the requirements: "iterative implementations are not allowed. Do not use while
or for
keywords in your solution"
So we'll add a test to invalidate the solution:
it('should not use loops', function(){
assert.ok(/for|while/.test(natToInt.toString()) === false, '"for" or "while" keywords are not allowed');
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
✓ should return 1 for the one nat number
✓ should return 2 for the one nat number
1) should ot use loops
3 passing (7ms)
1 failing
1) Nat numbers tests natToInt() tests should ot use loops:
AssertionError: "for" or "while" keywords are not allowed
TDD by Example (xiv)
A recursive implementation for pass the tests would be :
function natToInt(nat) {
return nat === zero ? 0 : 1 + natToInt(nat());
}
It is time that you are going to implement the rest of the functions.
Now we have enough confidence that the natToInt() function behaves correctly.
You can use this function in the implementation or testing the rest of the exercise as we know it will not fail (really you will use it only in tests).
We can also make changes in the implementation safely
Resources guide
- JavaScript: The Good Parts by Douglas Crockford
- Professional JavaScript for Web Developers by Nicholas Zakas
- Speaking JavaScript by Axel Rauschmayer
- JavaScript: The Definitive Guide by David Flanagan
- Secrets of the JavaScript Ninja by John Resig and Bear Bibeault
- Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript by David Herman
- Eloquent JavaScript: A Modern Introduction to Programming by Marijn Haverbeke
- Testable JavaScript by Mark Ethan Trostler
- Functional JavaScript: Introducing Functional Programming with Underscore.js by Michael Fogus
- Learning JavaScript Design Patterns by Addy Osmani
- JavaScript Allongé by raganwald
- Principles of Object-Oriented Programming in JavaScript by Nicholas Zakas
- JavaScript Spessore by raganwald
- Understanding ECMAScript 6 by Nicholas Zakas
- Maintainable JavaScript by Nicholas Zakas
- You Don't Know JS by Kyle Simpson
- If Hemingway Wrote JavaScript by Angus Croll
Books
Videos
Online Help
Blogs
Online Learning
Online Playgrounds
IDEs
- Books
- Videos
- Test Frameworks
- Assertion libraries
- Developer Tools
Testing
Testing (ii)
- Continuous integration
- Cover coverage
- Performance tests
- Code Quality
- Tutorials
- Slides
Testing (iii)
Package Managers
Module Loaders
Task Automation
Scaffolding Tools
Functional Libraries
Reactive Programming
Async JavaScript
JavaScript Compile Languages
- Node(V8)
- SpiderMonkey (Mozilla)
- Chakra (IE)
- SquirrelFish (WebKit)
- Carakan (Opera)
- Rhino
- asm.js
JavaScript Engines
JavaScript Hardware
Resources of resources
JavaScript Course
By Javier Pérez
JavaScript Course
- 2,913