JavaScript Basics (part 2)

Functions

Functions Arguments

In JavaSript, matching functions’ declared argument during a call is not mandatory. No error is raised when parameters are omitted. The hidden object arguments holds the given parameters.

function f(a) {
    for (var i = 0; i < arguments.length; i++) {
        console.log(i + '=>' + arguments[i]);
    }
}
f('ok',false) 
//  0=>ok
//  1=>false

Arguments list order can be tricky to handle. Option objects should be used to identify parameters.

var obj1 = new Object1( w, h, c, o, s);
var obj2 = new Object2({
    width : w,
    height : h,
    color: c,
    opacity: o,
    shadow: s
});

Functions used as methods

When belonging to an object and being invoked by that object, functions are methods. the parameter this refers to the object that owns the method.

 var p1 = {
    x: 10,
    y: 10,
    toString: function() { /* definition of a method */
        return '(' + this.x + ', ' + this.y + ')';
    }
};
// invocation of 'toString' as a method
p1.toString(); // '(10, 10)'

Functions used as functions

If not bounded to an object, functions are bounded to the global scope. In that case, this refers to the global scope and not to the calling context.

var my_name_is = 'This is global!';
function f() {
    var my_name_is = 'This is local to f()';
    function print_name() {
        console.log(this.my_name_is); // don't do that!
    }
    print_name();
}
f(); // 'This is global!'

Functions used as constructors

When used as a constructor, a function is called a Constructor Function. By convention a CF has an upper cased first letter. Constructor Functions are used in conjunction with the new operator. The new operator creates a fresh object and apply the CF to that object. The this pointer refers to that new object. is meant to refer to the new object.

:warning: This function’s prototype property is used to initialize the new object’s prototype link.

If no return statement or if the function doesn’t return an object, then this is automatically returned.

 function Point(x, y) {
    this.x = x || 0;
    this.y = y || 0;
}
Point.prototype.toString = function() {
    return '('+ this.x + ', ' + this.y + ')';
};
var p = new Point();

Functions invoked with a different context

Functions are objects, and objects can have functions (methods). Then Functions have methods.

Their exists a Function function. has it has a capital ‘F’ you can guess it is a Constructor Function. The Function.prototype property is used as a prototype link to any function.

The apply method belongs to Function.prototype. Following the prototype chain, any function can call the apply method.

apply invokes the function with a given context (this) as a parameter.

var weirdo = {
    x: '\u03B1',
    y: '\u03B2'
}

Point.prototype.toString.apply(weirdo); // '(α, β)'
p.toString.apply(weirdo); // '(α, β)'

const f = Point.prototype.toString.bind(weirdo); 
f();// '(α, β)'

IIFE Immediately Invoked Function Expressions

Use immediately invoked function expressions to prevent polluting the global scope.

var a = 1;
(function(){
    var a = 0
    console.log(a); // 0
})();
console.log(a); // 1

:six: Fonctions fléchées (arrow functions)

([param] [, param]) => {
   instructions
}

si une seule instruction alors son résultat est retourné (et pas besoin d’accolades) :

let calcul = x => x*x;
console.log(calcul(2))

:six: Fonctions génératrices (generators)

function* nom([param1[, param2[, ... paramN]]]) {
   instructions
}

Les fonctions génératrices retournes des objets de type Generator qui sont à la fois des itérateurs et des itérables. Les fonctions génératrices permettent d’être quittées en cours d’exécution, puis d’être reprises en conservant leur contexte.

La fonction n’est pas exécutée les de son appel et renvoie un itérateur. Lors de l’appel a la méthode next() de l’itérateur la génératrice est exécuté jusqu’à rencontrer la première expression yield (sorte de return, qui peut apparaître plusieurs fois et défini la valeur a retourner)

function* fib(max){
    let n0 = 1;
    let n1 = 1;
    let tmp;
    do {
        yield n1;
        tmp = n0;
        n0 = n1;
        n1 += tmp
    } while(n1 < max);
}

for(let val of fib(100)){
    console.log(val)
}

Strict Mode

Strict Mode is a restricted variant of Javascript defined in EcmaScript 5. It is activated per-function adding the string 'strict mode'; in the body of the function.

It is advised not to use ‘strict mode’ on the global scope in order to avoid imported non-strict dependencies and other files to be treated as strict mode.

function myFunction(){
    'use strict';
    // instructions...
}
forgotTheVar = 17; // throws a ReferenceError
delete Object.prototype; // throws a TypeError
var o = { p: 1, p: 2 }; // !!! syntax error
function sum(a, a, c){ /* ... */ }// !!! syntax error
var sum = 015; // !!! syntax error
function f() {
    'use strict';
    var o = { x:17 };
    with (o) { console.log(x); } // !!! syntax error
}
var x = 17;
var evalX = eval(''use strict'; var x = 42; x');
x === 17;  // true
evalX === 42; // true

Exceptions

Exceptions are meant to interrupt abnormally behaving programs.

 function Point(x, y) {
    if ((typeof x !== 'undefined' && typeof x !== 'number')
    || (typeof y !== 'undefined'  && typeof y !== 'number')) {
        throw { name: 'TypeError', message: ''Point' needs numbers' };
    }
    // ...
}
(function() {
    try {
        var weird = new Point('\u03B1', 0);
    } catch (e) {
        console.log(e.name + ': ' + e.message);
    }
})(); // TypeError: 'Point' needs numbers

Recursion

Possible but slow by default. No tail optimization for self calling functions.

 function fib(n) {
    if (n <= 2) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}

:six: Tail call optimisation.

function fib(n){
  function rec(n, n_1, n_2){
    return n < 2 ? n_1 : rec(n-1, n_2, n_1+n_2)
  }
  return rec(n, 1, 1);
}

Recursion demo on CodePen.

Closure

Functions have access to the variables and parameters of the function that defined them. They have access to the scope that was available during their creation.

A function can be exported to another context (another scope) and still have access to its original scope.

An inner function may live longer than its outer function.

function f_outer() {
    function f_inner() {
        return 'Inner function';
    }
    return f_inner;
}
var f = f_outer(); // f_inner
f(); // 'Inner function'

Inner functions encapsulate states of outer functions for a latter invocation.

function increment() {
    var counter = 0;
    return function() {
        counter = counter + 1;
        return counter;
    };
}
var my_increment = increment();
my_increment(); // 1
my_increment(); // 2
my_increment(); // 3
my_increment(); // 4
counter; // undefined

Closure to Hide Singletons

function html_to_md() {
    var tokens = {
        'p': '\n',
        '/p': '\n',
        'h1': '# ',
        '/h1': '\n',
        // ...
    };
    return function(html) {
        return html.replace(/<([^<>]+)>/g, function(a, b) {
            var t = tokens[b];
            return typeof t === 'string' ? t : a;
        });
    };
}
var parse = html_to_md();
parse('<h1>My Title</h1><h2>Subtitle<...'); // # My Title\n ## Subtitle\n...

An extended example of hidden singletons and utilities.

Closure to Make Safe Objects

By default, objects properties are always visible and writable. Inner Functions and closures can create objects with ‘private’ members.

 function createPoint() {
    var x = 0;
    var y = 0;
    // ...
    return {
        getX: function() {
            return x;
        },
        setX: function(new_x) {
            check_number(new_x);
            x = new_x;
        },
        // ...
    };
}
var p = createPoint(); // { getX: [Function], setX: [Function], ... }

p is a new object created without the ‘new’ operator.

p.x; // undefined

x is not part of p’s inheritance chain but is accessible from p’s functions scope.

p.getX(); // 0
p.setY(-12);
p.toString(); // '(0, -12)'

A complete example of safe objects create with closure.

Closure to Make Modules

var prop;
(function(global) {
    global.MY_MODULE = global.MY_MODULE || {};
    // private API
    function hidden_function() {
        console.log('You called a hidden function.');
    }
    // public API
    global.MY_MODULE.publicFunction = function() {
        console.log('Public function calling a hidden one...');
        hidden_function();
    };
})(this);

for (prop in MY_MODULE) {
    console.log(prop);
}
MY_MODULE.publicFunction(); // Public function calling a hidden one...
// "You called an hidden function."

Inheritance

Inheritance with the Classical Pattern

Pros:

Cons:

 var p,
    Point3D = function() {
        this.z = 0;
    };
Point3D.prototype = new Point();
Point3D.prototype.toString = function() {
    return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
};
p = new Point3D();
p.x = p.y = p.z = -3;
p.toString(); // '(-3, -3, -3)';

A complete example of Objects Inheritance with the Classical Pattern.

Inheritance with the ECMASript 5 Pattern

The ES5 Object.create function can easily handle inheritance.

Object.create(proto[, propertiesObject])

Example:

function createPoint() {
  'use strict';
  return Object.create(Object.prototype, {
    x: {value:0, writable:true},
    y: {value:0, writable:true},
    toString: {value: function(){return "("+this._x+","+this._y+")";}}
  });
}
function createPoint3D() {
  'use strict';
  return Object.create(createPoint(), {
    z: {value:0, writable:true},
    toString: {value: function(){return '('+this.x+','+this.y+','+this.z+')';}}
  });
}
var p = createPoint3D();
p.x = -1;
p.toString(); // '(-1,0,0)'

Inheritance with the Differential Pattern

Create new objects from existing ones.

Specify differences from a base object in order to specify (specialize) another one.

Pros:

Cons:

function createObject(base) {      // Utility function to
    function F() {};               // create objects with
    F.prototype = base;            // 'base' as a prototype.
    return new F();
}
var point = { x: 0, y: 0 },        // A 'base' object.
p3d = createObject(point);         // New object with 'point' as its prototype.
p3d.z = 0;                         // Differences from 'point'.
p3d.toString = function() {
    return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
};
p3d.toString(); // '(0, 0, 0)'

An example of Object Inheritance with the differential pattern.

Inheritance with the Functional Pattern

Some classical function returns new objects with public methods in it.

Public methods access hidden attributes thanks to closure.

That function is called as a regular function (no new operator).

var createPoint = function(attributes) {
    attributes = attributes || {};
    attributes.x = attributes.x || 0; // Hidden attributes, given as parameters.
    attributes.y = attributes.y || 0;
    // ...
    var point = {}; // The new object to be returned.

    point.toString = function() { // public methods accessing hidden parameters.
        return '(' + attributes.x + ', ' + attributes.y + ')';
    };
    // ...
    return point; // return the newly created object.
};
var p1 = createPoint({ x: -1, y: -4 });

Inheritance is simply done by:

var createPoint3D = function(attributes) {
    attributes = attributes || {};
    attributes.z = attributes.z || 0;
    var point3D = createPoint(attributes); // create a new object from upper hierarchy

    point3D.toString = function() {
        return '(' + attributes.x + ', ' + attributes.y + ', ' + attributes.z + ')';
    };
    // ...
    return point3D;
};
var p3d = createPoint3D({ x: -3, y: -3, z: -4 });

Code reuse or access to super members can be done with the apply invocation pattern.

var createAugmentedPoint3D = function(attributes) {
    attributes = attributes || {};
    attributes.width = attributes.width || '1px';
    attributes.color = attributes.color || '#00FF00';
    var augmentedPoint3D = createPoint3D(attributes);
    var superToString = augmentedPoint3D.toString;  /* Store the super toString
                                                       method locally.*/

    augmentedPoint3D.toString = function() {        /* Shadow super toString
                                                       and call it with 'apply' */
    return '{' + superToString.apply(augmentedPoint3D) + ', width:' +
        attributes.width + ', color:' + attributes.color + '}';
    };
    return augmentedPoint3D;
};
var ap3d = createAugmentedPoint3D({ color: '#b3e4a2' });
ap3d.toString(); // '{(0, 0, 0), width:1px, color:#b3e4a2}'

An example of Object Inheritance with the functional pattern.

Inheritance with the ECMASript 6 (class) Pattern

class Point {
  constructor(x, y) {
    this._x = x;
    this._y = y;
  }
  get x() {
    return this._x || 0;
  }
  set x(val) {
    this._x = val;
  }
  set y(val) {
    this._y = val;
  }
  get y() {
    return this._y || 0;
  }
}
class Point3D extends Point {
  constructor(x, y, z) {
    super(x, y);
    this._z = z;
  }
  get z() {
    return this._z || 0;
  }
  set z(val) {
    this._z = val;
  }
  toString() {
    return (`(${this.x}, ${this.y}, ${this.z})`);
  }
}

const d3 = new Point3D(1, 2, 3);
console.log(d3.toString()); // '(1, 2, 3)'

Templating

String substitution allowing the creation of strings based on value of variables (spritf() style) with the ${} operator.

var name = "Bob";
console.log(`Hello, ${name}!`); // Hello, Bob!

Multi-line strings:

console.log("l1 \
l2 \
l3"); // nope

console.log(`l1
l2
l3`);

Enumerations

There are no specific methods to create enumerations. An enum should contain an enumerable (for...in) and iterable (for...of) set of keys. Ones defined enums should be immutable.

Simple array

const myEnum = [
  'THIS',
  'THAT',
  'OTHER'
];

Simple Object (map)

const myEnum = {
  'THIS': 1,
  'THAT': 2,
  'OTHER': 3
};

Constructor function + Object.freeze

const keys = [
  'THIS',
  'THAT',
  'OTHER'
];
const MyEnum = function () { };
for (const key of keys) {
  MyEnum[key] = new MyEnum();
}
Object.freeze(MyEnum);

Implement Iterable with a Generator

const myEnum = {
  [Symbol.iterator]: function* () {
    for (key of keys) {
      yield key;
    }
  }
};

Iterable with Enumerable Properties

const Enumeration = function (keys) {
  const enumeration = Object.create(null);
  for (const key of keys) {
    enumeration[key] = key;
  }
  enumeration[Symbol.iterator] = function* () {
    for (key of keys) {
      yield enumeration[key];
    }
  }
  Object.freeze(enumeration);
  return enumeration;
}
// test
var myEnum = new Enumeration(['POMME', 'ORANGE', 'CITRON', 'KIWI'])
for (var i of en) { console.log("-", i, ": ", en[i]) }
for (var i in en) { console.log("-", i, ": ", en[i]) }
[...en]

JSON

JSON : Javascript Object Notation

{
    "type": "line",
    "points": [
        {
            "x": -1, "y": -1
        },
        {
            "x": 10, "y": 100
        }
    ]
}

:warning: Since with JSON text is directly interpreted as JS objects, it is very dangerous. Malicious (or mis-constructed) data could be sent by the server.

From ECMAScript 3.1, JSON.parse() is used to parse JSON data.

:warning: Never use the dangerous eval() function.

Classical data manipulation pattern

JSON is used as a way to import raw data from a server and to create models from it. Classical steps are:

  1. Import JSON raw data from a distant server into a client.
  2. Create Models from this raw data. Adaptations may be needed.
  3. Create Views for these Models.