Skip to content

Latest commit

 

History

History
565 lines (434 loc) · 15.5 KB

js-basics-2.md

File metadata and controls

565 lines (434 loc) · 15.5 KB

JavaSript Basics (part 2)

  1. JavaSript Basics (part 2)
    1. Functions
      1. Functions Arguments
      2. Functions used as methods
      3. Functions used as functions
      4. Functions used as constructors
      5. Functions invoked with a different context
      6. IIFE Immediately Invoked Function Expressions
    2. Strict Mode
    3. Exceptions
    4. Recursion
    5. Closure
      1. Closure to Hide Singletons
      2. Closure to Make Safe Objects
      3. Closure to Make Modules
    6. Inheritance
      1. Inheritance with the Classical Pattern
      2. Inheritance with the ECMASript 5 Pattern
      3. Inheritance with the Differential Pattern
      4. Inheritance with the Functional Pattern
    7. JSON
    8. Classical data manipulation pattern

Functions

  • Functions are objects.
  • Functions are linked to Function.prototype (which is itself linked to Object.prototype).
  • Functions have a prototype property, different from the prototype link.
  • Used by Constructor Functions to define newly created objects' prototype.
  • Nothing differentiates CF from non-CF so all functions have it.
  • Functions have a link to the object which invokes them: this.

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.

⚠️ 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); // '(α, β)'

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

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 adviced 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...
}
  • Some silent errors become throw errors

    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
  • Some dangerous or slow structures are forbidden

    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
  • Use of restricted words raises errors (class, enum, export, extends, import, super)

Exceptions

Exceptions are meant to interrupt abnormally behaving programs.

  • Thrown with the throw statement.
  • Caught with the try / catch statements.
 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. No tail optimization for self calling functions.

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

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 closure 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:

  • It's the classical way to do. Easy to read and understand. Any JS developer knows it. Cons:
  • Manipulating Constructor Functions and the new operator is risky.
  • All members are public.
  • Parameters and inheritance are problematic.
 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(); // '(-1, -2, -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 ordrer to specify (specialise) another one.

Pros:

  • can hide Constructor Functions and new operators.

Cons:

  • objects are linked (if base object changes, then inherited objects change too).
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:

  • using a create function into another,
  • sharing the attributes to initialize to new object.
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 functionnal pattern.

JSON

JSON : Javascript Object Notation

  • Concise and text-based, made for data exchange through different systems.
  • Based on JS object literals syntax.
  • 6 data types: objects, arrays, strings (double-quoted), number, boolean, null.
  • Can directly be interpreted as JS objects.
{
    'type': 'line',
    'points': [
        {
            'x': -1, 'y': -1
        },
        {
            'x': 10, 'y': 100
        }
    ]
}

⚠️ 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.

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.