Updated on
JavaScript Basics (part 2)
- Functions
- Strict Mode
- Exceptions
- Recursion
- Closure
- Inheritance
- Templating
- Enumerations
- JSON
- Classical data manipulation pattern
Functions
- Functions are objects.
- Functions are linked to
Function.prototype
(which is itself linked toObject.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
. - When executed, functions have access to the list of parameters through
arguments
(a sort of array).
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); // '(α, β)'
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
Fonctions fléchées (arrow functions)
- syntaxe plus courte, utile pour les fonction anonymes en paramètres d’autres fonctions
- ne possède pas de valeur pour
this
,arguments
,super
etnew.target
([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))
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...
}
- 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 by default. No tail optimization for self calling functions.
function fib(n) {
if (n <= 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
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);
}
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:
- 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(); // '(-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:
- 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 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'
];
- [] iterable (
for...of
) - [] NOT enumerable (
for...in
) - [] mutable
Simple Object (map)
const myEnum = {
'THIS': 1,
'THAT': 2,
'OTHER': 3
};
- [] enumerable (
for...in
) - [] NOT iterable (
for...of
) - [] mutable
Constructor function + Object.freeze
const keys = [
'THIS',
'THAT',
'OTHER'
];
const MyEnum = function () { };
for (const key of keys) {
MyEnum[key] = new MyEnum();
}
Object.freeze(MyEnum);
- [] enumerable (
for...in
) - [] immutable
- [] NOT iterable (
for...of
)
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]
- [] enumerable (
for...in
) - [] iterable (
for...of
) - [] immutable
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:
- Import JSON raw data from a distant server into a client.
- Create Models from this raw data. Adaptations may be needed.
- Create Views for these Models.