Advanced Javascript

EDGE Academy 2013

Johan Bjurling / Fredrik Wärnsberg

Fredrik Wärnsberg

Johan Bjurling

netlight.js

reveal.js

var h = document.getElementsByTagName("head")[0];
var l = document.createElement("link");
l.href = "css/theme/netlight.css";
l.rel = "stylesheet";

Overview

  1. First Class Functions
  2. Closures
  3. This
  4. Asynchronous programming
  5. Prototype based inheritance
  6. Tools, frameworks, projects

First Class Functions

First Class Functions

In Javascript, functions are objects and can thus be treated just like objects

function myFunction() {
  alert("myFunction");
}

alert(myFunction instanceof Object);

They can be stored in variables

var f = function myFunction() {
  alert("myFunction");
}
f();

They can be sent as arguments to other functions

function myFunction(f) {
  f();
}

They can be returned from functions

function myFunction() {
  return function() {
    alert("johan");							
  };
}

They can have properties

function myFunction() {
  alert("alert!");							
}
myFunction.myProperty = "something";

They can be properties

var myObject = {
  myFunctionAsProperty: function() { alert("yeah"); }
};

They can be extended

Function.prototype.curry = function() {
  var fn = this, slice = [].slice, args = slice.call(arguments);
  return function() {
    return fn.apply(this, args.concat(
	  slice.call(arguments)));
  };
};

Closures

A simple example

function makeClosure() {
  var name = "Johan";
  return function() {
    alert(name);
  };
}

A somewhat more advanced example

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

A practical example

Emulate privacy (Module Pattern)

The Module Pattern

var myModule = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };
})();
					

A pitfall

Creating closures inside loops

Closures in loops

var arrayOfFunctions = [];
for (var i = 0; i < 3; i++) {
  arrayOfFunctions[i] = function() { alert(i); };
}
						

How 'this' works in Javascript

You always have 'this' set to something

 

alert(this); // [object Window]

 

window.name = "A name";
var test = function(){
  alert(this);
};
test();

 

$('a').on('click', function(){
  alert(this); // HTMLElement
});

 

var object = {
  name: 'A Name',
  greet: function(greeting){
    alert(greeting + this.name);
  }
};
object.greet('Hello, ');
window.name = "window's name";
var object = {
  name: 'An object',
  init: function(){
    var self = this;
    var test = function(){
      alert(this.name);
      alert(self.name);
    }
    test();
  }
};

object.init();
 var object = {
  name: "A name",
  test: function(greeting){ alert(greeting + this.name); }
}

var stolen = object.test;
stolen("Hello, ");

Controlling this

 

Function.prototype.call(context, arg1, arg2, ...argN);
Function.prototype.apply(context, [args]); 
stolen.call(obj, "Bonjour, ");

stolen.apply(obj, ["Guten tag, "]);

Remembering this

 

Function.prototype.bind = function(context){
  var __func = this,
      slice  = [].slice,
      args   = slice.call(arguments, 1);
  return function(){
    args = args.concat(slice.call(arguments, 0));
    __func.apply(context, args);
  };
}; 
 stolen.bind(obj).greet('Hello, ');

Asynchronous programming

User me = TwitterUserSerivce.getUser("warnsberg");
List<User> followers = TwitterUserService.getFollowers(me);
List<Tweets> tweets = new ArrayList<Tweets>();
tweets.addAll(TwitterService.loadRecentTweets(followers));
System.out.println(Joiner.on(", ").join(tweets));

What would happen in Javascript

Javascript is single threaded

Event loop

When it needs to do something that won't return immediately, it is done asynchronously.

The basic idea

 

twitter.getUser(callbackFunction);
twitter.getUser({
  success: function(user){ console.log(user); }
});

Async error handling

 

twitter.getUser(function(error, user){ /* check if there was an error */ });
twitter.getUser({
  success: function(user){ /* ... */ },
  error: function(error){ /* ... */ }
});

Rembember our old example?

 

User me = TwitterUserSerivce.getUser("warnsberg");
List<User> followers = TwitterUserService.getFollowers(me);
List<Tweets> tweets = new ArrayList<Tweets>();
tweets.addAll(TwitterService.loadRecentTweets(followers));
System.out.println(Joiner.on(", ").join(tweets));

Hang tight, because this we're going to over lots of things quickly.

twitter.getUser("warnsberg", {
  success: function(user){
  },
  error: function(error){
    console.log('Couldn\'t load tweets :(');
  }
});
 twitter.getUser("warnsberg", {
  success: function(user){
    twitter.getFollowers(user, {
      success: function(followers){
      },
      error: function(error){
        console.log('Couldn\'t load tweets :(');
      }
    });
  },
  error: function(error){
    console.log('Couldn\'t load tweets :(');
  }
}); 
twitter.getUser("warnsberg", {
  success: function(user){
    twitter.getFollowers(user, {
      success: function(followers){
        twitter.loadRecentTweets(followers, {
            success: function(tweets){
              console.log(tweets.join(', '));
            },
            error: function(error){
              console.log('Couldn\'t load tweets :(');
            }
        });
      },
      error: function(error){
        console.log('Couldn\'t load tweets :(');
      }
    });
  },
  error: function(error){
    console.log('Couldn\'t load tweets :(');
  }
}); 

...but that's horrible!

Taming the async

Named functions saves the day!


var twitterError = function(error){
  console.log('Couldn\t load tweets :(');
};

var loadUser = function(){
  twitter.getUser('warnsberg', {
    success: loadFollowers,
    error: twitterError
  });
}

var loadFollowers = function(user){
  twitter.getFollowers(user, {
    success: loadTweets,
    error: twitterError
  });
}

var loadTweets = function(followers){
  twitter.loadRecentTweets(followers, {
    success: printTweets,
    error: twitterError
  });
};

var printTweets = function(tweets){
  console.log(tweets.join(', '));
}

Flow control

Composing small, re-usable parts.

Remember this?

 

asyncFunction(callback(err, result){});
 var twitterError = function(error){
  console.log('Couldn\t load tweets :(');
};

var loadUser = function(next){
  if(err) return next(err);
  twitter.getUser('warnsberg', next);
}

var loadFollowers = function(err, user, next){
  if(err) return next(err);
  twitter.getFollowers(user, next);
}

var loadTweets = function(err, followers, next){
  if(err) return next(err);
  twitter.loadRecentTweets(followers, next);
}); 

...and our code turns into

 

async.waterfall([loadUser, loadFollowers, loadTweets],
  function(err, tweets){
    if(err) return console.log('Twitter error :(');
    console.log(tweets.join(', '));
  }
);

Promises

Giving async operations an object representation.

Basic API

 

var promise = new Promise(); promise.resolve(value); promise.reject(error); promise.then(callback);

Promises are chainable

 

var promise = new Promise();

promise.then(function(value){
  /* ... */
}).then(function(value){
  /* ... */
}, function(error){
  console.log('Error: ', error);
});

And our example turns into this

twitter.getUser('warnsberg').then(function(user){
  return twitter.getFollowers(user);
}).then(function(followers){
  return twitter.loadRecentTweets(followers);
}).then(function(tweets){
  console.log(tweets.join(', '));
}, function(error){
  console.log('Couldn\'t load tweets :(');
});

...and in ES.Next

spawn(function*() {
  try {
    let user = yield twitter.getUser('warnsberg');
    let followers = yield twitter.getFollowers(user);
    let tweets = yield twitter.loadRecentTweets(followers);
    console.log(tweets.join(', '));
  } catch(e) {
    console.log('Couldn\'t load tweets :(');
  }
});

Prototype based inheritance

Classical inheritance

Prototype inheritance

You inherit from an actual object, not from a class (blueprint). The inheritance is reflected in the "Prototype Chain".

This means prototype based inheritance is in fact much more similar to real life.

How is an object's prototype determined?

function Sheep() {
}

How can this be used for inheritance?

Let's make a Pig Sheep

Let's make a Pig Sheep

function Sheep() {
}
							
Sheep.prototype.eyeColor = "blue";
							
function PigSheep() {
}
							
						

Tools & Frameworks

Useful tools

Test frameworks

Some final goodies

Summary

In Javascript...

  1. ...functions are First Class Citizens.
  2. ...functions remember their context in "Closures".
  3. ..."this" is a VERY special keyword.
  4. ...you need to structure your callbacks.
  5. ...inheritance is based on objects (prototypes) not classes.
  6. ...there are lots of great tools, frameworks and projects for you to discover.

THE END

 

johanbjurling.github.com/advancedjs