7
From Signs to Patterns

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.

7.1. Reserved words

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

Table 7.1. Uses of reserved words

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
class, extends, static, super
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

7.2. The pronoun “this”

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.

7.2.1. The many ways to link the pronoun “this”

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”.

7.2.1.1. First branch: global scope versus (local) function scope

  • – In the global scope, 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
  • – In the function scope, the question to answer is: how is the function invoked?

7.2.1.2. Function invoked with the operator (.)

const f = function () {return this;};
f();                    // returns global by default [object Window]

7.2.1.3. Function invoked as an object method

const obj = {};
obj.myMethod = function () {return this;};
obj.myMethod();        // returns [object obj]

7.2.1.4. Function invoked as a “callback” (DOM events, AJAX requests, etc.)

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

7.2.1.5. Function coded in the “arrow syntax”

Within an “arrow function”, this retains the value of the enclosing lexical context this.

7.2.1.6. Function invoked as “constructor” by the operator new

See section 7.3: “Operator new”.

7.2.1.7. Multiple invocations: Inner function redefining “this”

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);

7.2.2. How to explicitly bind the pronoun?

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) {
  return this.x + this.y + z;
}
sum(3);
NaN
As an object property const context = {x: 1, y: 2};
context.sum = sum;
context.sum(3);
6
Using call sum.call( context, 3 ); 6
Using apply sum.apply( context, [3] ); 6
Prior binding using bind const s = sum.bind(context);
s(3) ;
6

7.3. Operator: new

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

7.4. Punctuation signs

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)
 

7.5. JavaScript usual design patterns

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:

  • – “creational patterns”: the prototypal approach, in Chapter 4, is a creational pattern;
  • – “structural patterns”: beyond creation, the “namespacing” and the “decorator” modules are structural patterns;
  • – “behavioral patterns”: the “observer” and its “publish/subscribe” variant are behavioral patterns.

7.5.1. Programming idioms

7.5.1.1. Default initializations using a logical shortcut

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)

7.5.1.2. Encapsulation using a closure

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

7.5.1.3. Displaying the properties of an object: “Map/Entries Combo”

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"]

7.5.2. Creational pattern: “Assign/Create Combo”

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.

7.5.2.1. Read a JSON object and give it a prototype

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)
 );
image

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.

7.5.2.1.1. Complete pattern: “Assign/Create Combo”
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:

  • – cloning the initial object (from JSON);
  • – customizing the clone by additions/deletions;
  • – inheriting by delegation from a given prototype;
  • – identification through a “constructor” name.

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”).

7.5.3. Structural pattern: singleton or namespace pattern

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;
 })();

7.5.3.1. Augmenting the previous namespace

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

7.5.4. Another structural pattern: the Decorator pattern

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).

7.5.4.1. Code

        // 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);},""));

7.5.4.2. Logs

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

7.5.4.3. Comments

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.

7.5.5. Behavioral pattern: the observer or publish/subscribe pattern

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.

7.5.5.1. Code: The “push” approach: the code is on the observable side

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.

7.6. Metaprogramming with ES6

7.6.1. “Reflection” by “Symbols”

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'.

7.6.2. New tool for measuring code performance

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.239.234