- JavaSript Basics (part 2)
- 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
.
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
});
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)'
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!'
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.
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 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); // '(α, β)'
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 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 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
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);
}
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
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.
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.
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.
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)'
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.
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 : 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
}
]
}
From ECMAScript 3.1, JSON.parse()
is used to parse JSON data.
Never use the dangerous eval()
function.
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.