This chapter gathers several notions about the signs and words used in JavaScript, how they interact with the syntax (the traps of polymorphism), how to use programming constraints to style a more readable and robust code and a few hints about meta-programming.
In any language, punctuation plays a role in the interpretation. It is reported in 1327 that Queen Isabella signed the following order about her husband, Edward II:
Edwardum occidere nolite |
// "do not execute Edward" |
timere bonum est. |
// "to fear is a good thing" |
and it is reported that a comma was added (by who?), after ‘timere’:
Edwardum occidere nolite |
// in a single line, read: |
timere, bonum est. |
//"do not fear to execute Edward, it’s good" |
In any language there are reserved words, sacred or forbidden, and you must replace them: e.g. American “my gosh”, instead of a forbidden word.
In any language there are pronouns, a grammatical form referring to another form, and whenever referencing, ambiguities may occur:
“because of the rain, Paul arrived late at the dentist. He was unhappy”
Who was unhappy? The dentist or Paul?
Ambiguity does not exist in computers, but “errare humanum est”. A human mistake can lead to misinterpretation. Keywords, punctuation and a misused pronoun are often error prone in JavaScript. This chapter is meant to guard you against such mistakes, and also meant to teach some programming best practices: design patterns.
The table below contains 38 words to which 10 more are reserved for future use:
debugger, enum, export, implements, import, interface, package, private, protected, public
Keyword | Use |
const, let, var, typeof |
Declarations. Type operator |
break, case, continue, default, do, else, false, for, if, switch, true, while |
Syntax of conditional instruction, branch or loop. The use of ‘continue’ is discouraged in loops, and the use of ‘break’ is encouraged in switch |
delete, in, instanceof, new, null, this |
Operators linked to objects Syntax of ‘class’ in ES6: ignored in this book. |
arguments, function, return, this, yield |
Syntax of function, or generator functions (yield) |
try, catch, finally, throw |
Exception handling |
eval, with |
Strongly discouraged use |
void |
Evaluates the expression to undefined |
yield |
Used with generator functions |
await |
Ignored in this book |
These words are “reserved” and cannot be used for variables, functions and parameters, but can be property keys, though the dot notation is forbidden for them:
let const = 3; // illegal
const object = {const: 3}; // illegal
const object = {"const": 3}; // ok
const object["const"] = 4; // ok
Besides reserved, it is wise to avoid using built-in objects names, methods or property names of the global object:
Object, Function, Array, Boolean, Number, String, Symbol, RegExp, Date, Math, JSON, parseInt, parseFloat, isNaN, …
Also, 16 words were “reserved” before ES5, and it is wise to avoid them as well:
abstract, boolean, byte, char, double, final, float, goto, int, long, native, short, synchronized, throws, transient, volatile
In object-oriented programming, any instruction is in the context of one object: the global object or a particular object. The role of the pronoun “this” is to tell which of these objects the instruction is dealing with, within its global/function scope. At the origin of JavaScript, the pronoun “this” has been introduced to determine which HTML DOM object is currently handled, and by default, it points to window, the HTML global object.
With the multiplication of the uses of JavaScript, this initial choice is a kind of “handicap”. It is important to be trained on this
in order to avoid mistakes.
To keep it simple, we do not mention the "use strict"
mode, where, roughly speaking, if this is not defined in some way, it is “undefined”.
this
refers the global object,
// in the browser environment:
console.log(this === window); // true
// in Node.js, each module has its own global object:
console.log(this === global); // true
const f = function () {return this;};
f(); // returns global by default [object Window]
const obj = {};
obj.myMethod = function () {return this;};
obj.myMethod(); // returns [object obj]
const nav = document.querySelector('nav'); // <nav> tag
const toggNav = function (){ console.log(this);};
nav.addEventListener('click',toggNav); // this = <nav> element
const xhr = new XMLHttpRequest();
xhr.addEventListener("load",jsonCallback); // this = "xhr" element
Within an “arrow function”, this
retains the value of the enclosing lexical context this
.
See section 7.3: “Operator new”.
WARNING.– An inner function may change the value of this
depending on the way that function has been invoked.
Here is a frequent issue: the inner function is a method of window
object:
const nav = document.querySelector('.nav'); // <nav class="nav">
const toggNav = function () {
console.log(this); // <nav> element
setTimeout(function () { // callback
console.log(this); // [object Window] !Beware!
}, 1000);
};
nav.addEventListener('click', toggNav, false);
One solution for passing the context value: copy this
in that
(usual name):
let that = this;
setTimeout(function () { console.log(that); // <nav> ok!
}, 1000);
A simpler (ES6) solution is to use the arrow syntax:
setTimeout( () => { console.log(this); // <nav> ok!
}, 1000);
There are several approaches. We have seen two in the previous example: an intermediate variable (that) or the arrow syntax, but we cannot use these in every situation. Here are some more generic approaches.
Table 7.2. Several ways to binding the pronoun in a function
“Binding mode” | Instructions | Result |
No binding | function sum(z) { |
NaN |
As an object property | const context = {x: 1, y: 2}; |
6 |
Using call |
sum.call( context, 3 ); |
6 |
Using apply |
sum.apply( context, [3] ); |
6 |
Prior binding using bind |
const s = sum.bind(context); |
6 |
As already stated, we recommend to retrain the use of new
but, in some cases, it must be used with built-in objects such as RegExp
(Chapter 3), or XMLHttpRequest
(Chapter 10). Therefore, we mention in this section, the impact of new
on the binding of this
.
Invoked with new
, a function creates an object and modifies the value of this
to the new object. In general, the returned value is precisely the value of that this
, unless you specify an explicit different object (no reason to do so voluntarily). But this constructor function can be called as a regular function, using the operator ()
: then, we can also bind the function to give its this
a different value. Remember the example in Chapter 4:
const c1 = new Candidate(); // object Candidate, no initialisation
Person.call(c1, last, first); // constructor Person initializes c1
Most punctuation signs play a role in JavaScript: some are polymorph, which means having different roles depending on the context.
Table 7.3. Punctuation signs used in JavaScript (col. P= polymorphism)
Sign | Uses | P |
" " ' ' ` ` |
Three kinds of quotes to delimit strings: double and simple (that you may mix if quotes are inside the string), and back-tick, for template string | |
; |
Ends an instruction, or empty instruction (if alone) | |
, |
List separator: object literal, array literal, or; Repetition operator in declaration instructions: let a, b, c; |
P |
. |
Syntax to access an object property (dot notation) | |
: |
Syntax for ternary operator after ?, or; Syntax for key:value in object literal, or; Syntax for a switch case, or for a label just before a block {code} |
P |
? |
Syntax of the ternary operator | |
! |
Unary boolean operator for negation. Used twice, it makes a boolean context: (for instance if x=4, !!x === true) | |
!= !== == === |
Comparison operators: equality or inequality, make a boolean context | |
> < >= <= |
Relational operators. Implicit call to Object.prototype.valueOf() when comparing primitive values | |
+ |
Binary arithmetic operator of addition, or the following String concatenation operator, or unary (see); | P |
- * / % ** |
Binary arithmetic operators of subtraction, multiplication, division, rest (module) and exponentiation (left to right evaluation) | |
+ - |
Unary operators to convert into a positive/negative number, if impossible: yields NaN | |
++ -- |
Unary operators of incrementation, decrementation: can be used in prefix or postfix: ++x or x++ | |
|| && |
Binary boolean operators of disjunction and conjunction: make a boolean context (left to right evaluation) | |
>> >>> << ^ | & ~ |
Binary bitwise operators: right-shift, unsigned right-shift, left-shift, Exclusive-or (XOR), logical or, and (OR/AND) Unary operator to invert bits |
|
= |
Assignment operator. Can be combined with: - an arithmetic operator, prefix: += -= *= /= **= %= - a bitwise operator, prefix: >>= >>>= <<= &= |= ^= |
|
{ } |
Syntax of object literal objet, or: Block of instruction for a function, a branch or a loop, or just plain: { let x; } creates a block |
P |
[ ] |
Syntax to access an object property, including an array element | |
( ) |
Grouping operator, with a list of parameters or arguments, or; Precedence operator: forces the immediate evaluation of the innermost pair of parentheses, or; Syntax for condition in branch or loop: if() for() while() |
P |
… |
Syntax (ES6) for objects or arrays: “rest syntax”, “spread syntax” | |
=> |
Syntax (ES6) for “arrow functions” | |
// /**/ |
Comments: end of line only Comments: possibly over several lines (warning: comments cannot be nested) |
In software development, a “design pattern” is a code pattern recognized as best practice to answer a certain conception issue and reusable for several situations.
We have already come across such patterns: to save free variables within a closure, to build a prototypal object with given prototype and properties, etc. By contrast, a pattern that is not easily reusable depending on a free context (global variables, etc.) is called an “anti-pattern”, even if it is working in its particular context.
The simpler forms of patterns are sometimes called “idioms”, and more complex ones are categorized in the literature as:
Evaluating expressions in boolean context, e.g. conditional instructions, forces the variables to be cast as booleans instead of their own type. This leads to defining some default initialization idioms:
const localObjet = objet || {"nom":"default object"};
let localNum = num !== 0? (num || numDefaut) : 0;
let localStr = str !== ""? (str || strDefaut) : "";
let localProp = "prop" in objet? prop : propDefaut;
depending on the polymorphism of false
, with respect to various values or types:
0, NaN, "", null, undefined → false (boolean)
A frequent situation: we need an external control variable within the code block of a function, example:
let once = true; // variable de corntrÔle exteme
function printOnce(str){
if (once) {once = false; return str;}
// implicit else = return undefined
}
Solution: build a closure around both the variable and the code block, then return the inner code as a function expression:
function printOnce() {
let once = true;
return function(str){
if (once) {once = false; return str;}
};
}
const printOneTime = printOnce();
console.log(printOneTime("Hello")); // -> Hello
console.log(printOneTime("Hello")); // -> undefined
Using the built-in object Map
(ES6), an “Iterable” with a size
property, and set
and get
methods, in combination with Object.entries
:
const can = { nom: 'Good', prenom: 'Morning' };
const canMapped = new Map(Object.entries(can)); // idiom
canMapped.get('nom'); // -> 'Good'
canMapped.size; // -> 2
Using Map
in combination with Array.from
for a time series data array:
const timeData = [['d1', 'v1'], ['d2', 'v2']];
const timeMap = new Map(timeData);
timeMap.get('date1'); // returns "v1"
timeMap.forEach(function(v,d){console.log('date ${d}, v= ${}');});
const dates = Array.from(timeMap.keys())); // ["d1", "d2"]
const valus = Array.from(timeMap.values())); // ["v1", "v2"]
We have learned that we can combine one inheritance by delegation (shared) and several inheritances by concatenation (duplicated).
const obj = Object.assign(
target [=Object.create(proto)], // provides the prototype
source1, // one source of properties
source2 // anonther source
);
The object obj
receives the properties of the sources and has proto
as prototype. This pattern can be adapted to answer several needs.
Data are often exchanged over the Internet in text files using JSON format, and this is an advantage for JavaScript: the assign/create combination can easily fit that use: Object.assign
“mixes” a clone of the JSON parsed object, with a prototype independently provided by Object.create
.
const base = {
// JSON data
};
const proto = {
// prototype methods
};
const nu = Object.assign(
Object.create(proto),
base)
);
The object nu
inherits by delegation of the methods of proto
, while copying the properties of base
. We have learnt that proto
can be built from a function, thus providing a constructor.name
:
function Nu(){} // "prototype support" + "constructor.name"
Nu.prototype.method1 = function(){/*…*/};
Nu.prototype.method2 = function(){/*…*/};
const proto = Nu.prototype;
const nu = Object.assign(Object.create(proto), base));
// nu is a "Nu" object (constructor.name === "Nu")
For an array of objects from the JSON file, and a given Nu.prototype
, a single function call can build the entire nu
’s array, even choosing the properties to keep, or adding some. Let us wrap up everything here.
function Nu(){}
Nu.prototype.email = function(){console.log("Dear "+this.nom);};
const dataset = [{"nom":"Dan"},{"nom":"Bob"}]; // JSON array
const nus = dataset.map(function(d,i){
let nuPlus = {"n":(20+i)}; // if any is to be added
delete d.uselessprop; // if any is not to be kept
return Object.assign(Object.create(Nu.prototype), d, nuPlus);
});
checking it up:
nus.forEach( x=>{ x.email(); }); // "Dear Dan" / "Dear Bob"
console.log( nus[0].constructor.name); // "Nu"
console.log( nus[0] instanceof Nu); // true
console.log( Nu.prototype.isPrototypeOf(nus[0])); // true
This pattern allows us to cumulate all the advantages:
Though the operator nu instanceof Nu
works here, it is safer to use the test Nu.prototype.isPrototypeOf(nu)
(see Eric Elliott quote1: “Don’t listen to instanceof
, and it will never lie to you”).
The goal is to embed in a single object (singleton) all the data and tools relative to a certain apparatus, thus providing a unique global access: everything inside, which can be publicly visible, will be accessed through a single name. Therefore, it is called a “namespace”. For instance, a web page can be represented by a singleton: title, different sections, navigation tools, are all related: webpage.title
, webpage.section[i]
, webpage.load()
, etc.
If the initialization of the singleton requires external information (e.g. a JSON file), the pattern is not merely “creational”; it must be “structural” in that it combines the public data and methods, with a larger range of private variables and functions. The entire structure must be protected from the outside, and must have no side effect as well.
Let us develop this and tour its capabilities:
const webpage = (function() { // first protection with const
// defined within the local scope
const privateM1 = function() { /* … */ }
const privateM2 = function() { /* … */ }
let privateP = 'foobar';
const obj = { // the "revealing pattern"
publicMethod1: privateM1,
properties:{publicProp: privateP}, // nested levels
utils:{publicMethod2: privateM2}
};
Object.seal(obj); // protection of structural changes, or
Object.freeze(obj); // total protection against changes
return obj;
})();
If it exists already, we use it as argument for the IIFE, otherwise we use an empty object. The “augmentation” is concentrated in a single instruction, the IIFE, which modifies the object window.webpage
(or a copy, if webpage
is frozen).
// webpage (the namespace name) can be modified locally and isn't
// overwritten outside of our function context
(function(webpage) {
// new public methods and properties
webpage.foobar = "foobar";
webpage.sayHello = function() {alert("hello world");};
// check whether 'webpage' exists in the global scope
// if not, assign window.namespace an object literal
})(window.webpage = window.webpage || {});
// if "frozen" or "sealed", first copy webpagePlus = webpage
Somehow similar to augmentation, the “decorator” brings new methods to an object: wrapping them with the old ones into a new prototype. Hence, the decorator pattern is also called the “wrapper pattern”.
Indeed, what the decorator does is to mix inheritance by delegation (the first prototype is the generic decorator) and inheritance by concatenation (the second prototype is the actual decorator, and you can build several).
// helpers: protoChainOf(), methodCalls()
function list(q,i){
return '${protoChainOf(q, '=', 0)} methods:
${methcdCails(q)}';
}
function User(){return 'User only';}
function Age(){return 'Age only';}
function UserAge(){return "Mixin";}
User.prototype.full = function(){return this.first+"
"+this.last;};
Age.prototype.year = function(){return 2017 - this.age;};
let z1= Object.assign(Object.create(User.prototype),
Age.prototype),
z2= Object.assign(Object.create(zu1),
{constructor:UserAge});
const last= 'Bel', first= 'Jo', age= 28, qqs= [];
qqs.push( Object.assign(Object.create(z1), {first,age}) );
qqs.push( Object.assign(Object.create(z2), {first},{age}) );
// dynamic modification of methods
Age.prototype.year = function(){return 2018 - this.age;};
User.prototype.full = function(){return "M. "+this.last;};
console.log( qqs.reduce(function(s,q,i){return
s+list(q,i);},""));
User only, prototype chain= |
{last:Bel, first:Jo, age:28} (1)-> User [year] (2)-> User [full] (3)-> Object [] |
methods: year:1989, full:M. Bel |
Mixin, prototype chain= |
{last:Bel, first:Jo, age:28} (1)-> UserAge [] (2)-> User [year] (3)-> User [full] (4) -> |
Object [] |
methods: year:1989, full:M. Bel |
The prototype chain is correctly incremented and methods are delegated along. But, in case of subsequent changes, only those of User.prototype
are passed on (see: the “M.”), not those of Age.prototype
: function year
is a copy and not a reference to the method year
of Age.prototype
.
The “decoration” works, but this form of multiple inheritance remains static: there is no automatic update. The next pattern may be a solution to this issue.
This pattern is able to establish a one-way communication with modules that play the role of “observers”. When notified, an observer reacts according to the signal received from the “observable”. Each observable maintains its list of observers. This is the “push” approach, and it is called the “subscriber/publisher” pattern. In the “pull” approach, it is the observer that maintains a list of observables, and it has the role of asking every observable in order to know about any change.
const Observable = function() { this.observers = []; }
Observable.prototype = {
subscribe(callback) { // adds to the list
let yet = this.observers.some(c => c === callback);
if(!yet) this.observers.push(callback);
},
unsubscribe(callback) { // remove from the list
let i = this.observers.indexOf(callback);
if(i>-1) this.observers.splice(i, 1);
},
publish(str) { // notifies the list
this.observers.forEach(c => {c(this.title+" "+str);});
}
};
const Observer = function(n) { // closure: creating observers
const name = n;
return function(str){console.log(str+" for "+name);}
};
// let's create a newsletter and 2 subscribers
const subscriber1 = Observer("Bob");
const subscriber2 = Observer("Nora");
const blog = Object.assign(Object.create(Observable.prototype),
{"title":"myBlog","observers":[]});
blog.subscribe(subscriber1); // register an observer
blog.subscribe(subscriber2); // register an observer
blog.publish('Jan.2018 issue'); // notifies a change
// logs: ----------------------
//> "myBlog Jan.2018 issue for Bob"
//> "myBlog Jan.2018 issue for Nora"
Each observable owns its list of observers and communication methods. The observers, in this example, are closures: just a name and a callback.
The handling of DOM events (chapter 8) uses this pattern and many animation modules as well.
A new API has been introduced in ES6, which compounds three objects: Symbol
, Reflect
and Proxy
. Symbol
is concerned with the “Reflection within implementation”: to modify the behavior of some generic methods of the objects owning that Symbol
. Thus, a Symbol
is mid-way between an object descriptive property and a comment of a code block.
There exist several predefined Symbol
; here we present two examples:
Symbol.toStringTag
: specifies the return value of Object#toString
instead of the unexplicit [object Object]
const can = { // example with an object literal
get [Symbol.toStringTag]() { return 'Candidat'; }
}
Object.prototype.toString.call(can); // -> '[object Candidat]'
Symbol.replace
specifies the return value 'String#replace'.
The object Performance
has been introduced recently, providing direct access to the inner clock, plus some information about browsing.
The method window.performance.now
provides the time, in milliseconds, which we can subtract between two successive calls and get a true duration (ms).
3.143.239.234