JavaScript 101 – Invocation pattern – aka this keyword



JavaScript 101 – Invocation pattern – aka this keyword

0 2


slides-js101

A javascript quickstart.

On Github themouette / slides-js101

JavaScript 101

By Julien Muetton / @themouette

OpenStudio 2013 December, 5th

Environments

Server

Shipping applications

Web browser

  • Websites
  • Extensions

What follows is mostly about HTML

HTML

HTML inclusion

  • Always include scripts at the bottom as it stops page execution
  • CSS in the header to avoid multiple rendering
  • Limit the number of API requests as network is slow
  • Concat and compress your assets for all previous reasons

Document Object Modeler

Document fragments

When altering a node, DOM is rendered again.

To avoid performances issues, you need to work on a detached piece of DOM.

A Fragment is an empty DOM node.

Coding standards

CS: Braces

automatic semicolon insertion force to put braces at the EOL

// BAD
return
{
    foo: "foo"
};

vs

// GOOD
return {
    foo: "foo"
}; 

CS: Strings

Use `Array.join()` to declare multiline strings

// BAD
var foo = 'Lorem ipsum dolor sit amet, consetetur '
        + 'sadipscing elitr, sed diam nonumy ';

vs

// GOOD
var foo = [
    'Lorem ipsum dolor sit amet, consetetur ',
    'sadipscing elitr, sed diam nonumy '
    ].join(''); 

CS: name your functions

Lambda functions are shown as (anonymous) in debugger.

// BAD
var foo = function() {
    /* ... */
};

vs

// GOOD
var foo = function foo() {
    /* ... */
};

CS: use JSON

Variable initialization should remain readable

// BAD
var foo = new Object();
foo.name = 'John';
foo.age = 32;

vs

// GOOD
var foo = {
    name: 'john',
    age: 32
};

CS: Semicolons

Semicolons `;` are optional as ASI insert them for us.

a = b + c
foo()
// All right
a = b + c ; foo()
a = b + c
[1].push(a)
// Not what expected
a = b + c[1].push(a)
a = b + c
(opts || {}).foo ? bar() : baz()
// Not what expected
a = b + c(opts || {}).foo ? bar() : baz()

Either you learn ASI rules or you write semicolons

To go further on ASI: DailyJS on ASI

More advices in the Airbnb JavaScript Style Guide

Variables and contexts

Contexts

var foo = "outside";
function test() {
    var foo = "inside";
    console.log('test foo is "%s"', foo);
}

test();
console.log('outside foo is "%s"', foo);Run

Will output

test foo is "inside"
outside foo is "outside"

Each function defines its own context and can access definition context.

Local vs Global

Any variable not declared with var is global.

var foo = "outside";
function test() {
    foo = "inside";
    console.log('test foo is "%s"', foo);
}

test();
console.log('outside foo is "%s"', foo);Run

Will output

test foo is "inside"
outside foo is "inside"

Hoisting: variables

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();
Run
// Is compiled into
var foo = 1;
function bar() {
    var foo;
    if (!foo) {
        foo = 10;
    }
    alert(foo);
}
bar();

Variable declarations are hoisted to the top of context by compiler

Hoisting: functions

var a = 1;
function b() {
    a = 10;
    return;
    function a() {};
}

b();
alert(a);
Run
// Is compiled into
var a = 1;
function b() {
    // declare a symbol locally
    function a() {};
    a = 10;
    return;
}

b();
alert(a);

Function declarations are hoisted too.

To go further on hoisting: adequatelygood

Closures

Definition

A closure is a function alongside a referencing environment.

To be short, you can reference variables that belongs to the context where function was defined.

In the meantime, a function creates its own context.

Example 1: public API

var x = 0;
function incr() {
    x++;
    console.log(x);
}
function decr() {
    x--;
    console.log(x);
}

console.log(x); // log 0
incr();         // log 1
incr();         // log 2
decr();         // log 1Run

Example 2

Create a callback from a variable:

function createAdd(number) {
    function doAdd(value) {
        return value + number;
    }

    return doAdd;
}

var add10 = createAdd(10);
alert(add10(1)); // returns 11Run

Example 3

Closure creates it's own context:

var x = 5;

function foo() {
    var x = 12;
    function myFun() {
        // do something
    }
    return myFun;
}

console.log(foo());     // log function myFun() {...}
console.log(x);         // log 5
console.log(myFun);     // reference errorRun

Invocation pattern

aka this keyword

When calling a method on an object

var john = {
    name: "John"
};

john.sayHello = function () {
    alert("Hello " + this.name);
};

john.sayHello();Run

this is the object.

When Calling A Function

var name = "Garry";

function sayHello() {
    alert("Hello " + this.name);
}

sayHello();Run

this is the current context:

Common Gotcha

var name = "Garry";
var joe = {
    name: "Joe"
};

joe.sayHello = function sayHello() {
    alert("Hello " + this.name);
}

var fct = joe.sayHello;

fct();Run

Force execution context

var joe = {
    name: "Joe"
};
function sayHello(greeting, to) {
    greeting || (greeting = 'Hello');
    to || (to = 'annonymous');

    alert([greeting, to, 'I\'m', this.name].join(' '));
}

sayHello.call(joe, 'Good morning', 'Garry');
sayHello.apply(joe, ['Good morning', 'Garry']);Run

will both result into

Good morning Garry, I'm Joe.

Context binding

function bind(context, method) {

    return function () {
        method.apply(context, arguments);
    }
}

This way, you can make sure the context is what you expect.

In jQuery it is called $.proxy.

Read more on partial application on Cowboy's blog and Invocation patterns in depth

Object Model

Constructor

A constructor is a simple function

var User = function User(properties) {
    // init properties if falsy
    properties || (properties = {});

    this.firstname = properties['firstname'];
    this.lastname  = properties['lastname'];
};

Instantiation is as expected:

var john = new User({
    firstname: "John",
    lastname: "Doe"
});

Adding functions

User.prototype.sayHello = function sayHello() {
    alert("Hello, I'm " + this.firstname);
};

To be more efficient, use extend function:

$.extend(User.prototype, {
    sayHello: function sayHello() {
        alert("Hello, I'm " + this.firstname);
    },
    goodBye: function goodBye() {
        alert("Bye !");
    }
});

Prototype is great

  • Multiple inheritance;
  • Add a method to all instances;
  • Pattern strategy.

Prototype cons

  • No visibility;
  • Not natural at first glance;
  • You can mess up existing code.

Never override native objects prototypes.

Modules

Simple modules

Use IIFE to isolate context

var myModule = (function (window, undefined) {
    var privateVariable;

    function foo () {
    }
    function bar () {
    }

    return {
        foo: foo,
        bar: bar
    };
})(window);

More about IIFE on Cowboy's blog

AMD

Asynchronous Module Definition relies on define.

define([
    'jquery',
    'app/model/user'
], function ($, User) {
    var user = new User();

    /* ... */

    return something;
});

curljs and requirejs are amd loader.

amdclean removes extra define

CommonJS

aka exports and require

browserify allow to use nodejs dependency model.

module.exports = (function () {

    var User = require(__dirname + '/model/user');

    var u = new User();

    /* ... */

    return something;
})();

Promises / deferred

Promise?

  • Promises are CommonJS spec (/A, /B, /C, /D) ;
  • Functional approach for asynchronous ;
  • A single API for every asynchronous processes ;
  • Avoid pyramid of doom.

Quick jQuery example

jQuery is none of CommonJS Promize compliant but is consistent.

$.ajax( "/example" )

    // when request is successful
    .done(function(response) { alert("success"); })

    // Error happened
    .fail(function(xhr, statusText, error) {
        alert("error"); })

    // executed in both cases
    .always(function() { alert("complete"); });

Combine

Note there is a single callback to handle all returned data.

$.when(
    $.ajax("/example"),
    $.ajax("/sample")
)

    // called when both succeed
    .done(function (example, sample) {
        alert ('both returned');
    })

    // called when any request fails
    .fail(function () { /* ... */ });

Pipe

$.ajax( "/example" )
    .fail(function () {
        alert('example failed') })
    .pipe(
        function success(example) {
            return $.ajax('/sample');
        },
        function error() {
            alert('example failed !')
        })

    // From here, this is about second request
    .done(function (sample) {
        alert ('sample returned'); })
    .fail(function () {
        alert('sample failed') });

Filter data

Easy way to tweak inconsistant apis.

$.ajax( "/example" )
    .then(
        function success(example) {
            // filter data
            return {response: example};
        },
        function error() { /*...*/ },
        function progress() { /*...*/ }
    )

    // `data` has been altered by `then` callback
    .done(function (data) {
        console.log(data.response);
    });

Tools

Linters

Unit testing

Functional testing

Organize and build

Libraries

Resources

Starter guides

Videos

Articles

Thank you !

Questions ?