JavaScript extensions in Squish 6.6

Last edited on

Squish 6.6 ships with several extensions to the JavaScript support. The main ones are described below.

Let Declarations

Early JavaScript versions offered the var keyword for variable declarations:

function factorial(n)
{
    var result;
    if (n > 1) {
        var rest = factorial(n - 1);
        result = rest * n;
    } else {
        result = 1;
    }
    return result;
}

The sometimes non-intuitive side-effect of var declarations is their scoping: they are accessible within the entire function's scope. Or globally when found outside of a function. And - even more surprising - access to the variable is possible before the declaration statement. Even though the value will be undefined in that case.

The let keyword does away with those potential problems:

x; // ReferenceError!
let x = 1;
if (x) {
    let x = 2;
    x;   // <-- 2
}
x; // <-- 1

To further avoid variable declaration problems the "use strict" directive described below is worthwhile using. It will catch cases of accidental variable usage without a var or let declaration.

Const Declarations

The const keyword was available before but had a behavior that pre-dates the standardization in ECMAScript. As of Squish 6.6.0 const will have

a) block scope like let and b) lead to an error on attempts to change its value

c; // ReferenceError!
const c = 1;
c = 2;  // TypeError!

A constant declaration needs to include an initializer value, i.e. const c; by itself is not a valid statement.

Arrow Functions

A classic function declaration

function multiply(a, b)
{
    return a * b;
}

print(multiply(3, 7));  // <-- 21

can be expressed in a short-hand form using the => operator:

let multiply = (a, b) => a * b;

print(multiply(3, 7));   // <-- 21

This notation is particularly convenient for concise expression of callback functions:

let arr = [ 3, 5 ];

arr.forEach(v => { test.log(v); });  // logs 3 and 5

Template Strings

Tired of assembling strings using the + operator? A new type of string literal marked by backticks provides more flexibility than the classic strings surrounded by single or double quotes.

Instead, of assembling a constant like

const multiLine = "First line\n" +
    "Second line\n" +
    "Third line`;

multi-line strings can be expressed as a single literal for example:

const multiLine = `First line
Second line
Third line`;

And instead of mixing constant and variable parts like this

test.log("Found " + itemCount + " menu items");

the ${...} placeholder can embed expressions that will be evaluated on-the-fly:

test.log(`Found ${itemCount} menu items`);

Classes

Object-oriented programming with JavaScript is based on the concept of function and prototype objects. A concise declaration of object prototypes, functions and inheritance is now available via the class keyword.

Note that the script interpreter has to see the declaration of a class prior to its first usage. That's different to functions that can be referenced even if the declaration follows later in the script.

Constructors

A class declaration can contain a special constructor function. It will be invoked upon creation of an object via the new operator. That way an object's initial state can be set safely and conveniently.

class Animal
{
    constructor(name) { this.name = name; };
}

var dog = new Animal("Rantanplan");
dog.name;  // "Rantanplan"

Prototype Methods

Function properties of class objects (methods) can be defined with a shorthand notation that does not include the typical function keyword:

class Animal
{
    constructor(name) { this.name = name; };
    jump() { return this.name + " jumps"; }
}

var dog = new Dog("Rantanplan");
dog.jump();  // "Rantanplan jumps"

Static Methods

Methods can be declared for the class itself via the static keyword. They do not require instances of the class and not operate on them.

Typically, class functions contain utility or factory functions.

class Animal
{
    constructor(name) { this.name = name; };
    static Dog(name)
    {
        let dog = new Animal(name);
        dog.legs = 4;
        return dog;
    }
}

var dog = Animal.Dog("Rantanplan");
dog.legs;  // 4

Extensions

Once a class is declared it can act as a base for other classes that will inherit all its properties.

class Bird extends Animal
{
    chirp() { return this.name + " chirps" }
};

Super

The constructor function of a complex class may want to invoke the constructor of the base class. This is possible through the super() function.

class Car {
    constructor(brand) {
        this.brand = brand;
    }
}

class Model extends Car {
    constructor(brand, mod) {
        super(brand);
        this.model = mod;
    }
}

Similarly, the super property references methods of the extended class:

class B { greet() { return "hel"; } }
class C extends B { greet() { return super.greet() + "lo"; } }

let c = new C();
c.greet(); // "hello"

Class Expressions

Classes can also be declared as part of an expression and thus treated like values:

let Animal = class { .... };
let animal = new Animal();

Strict Mode

A strict parsing mode will be enabled if the magic string "use strict" or 'use strict'is found at the top of a script file or function. The strict mode helps to detect common programming errors like access to undeclared variables.

Example:

"use strict";

function main() {
    verboseMode = true;  // <-- error: access to undeclared variable
}

Currently, the following issues will be detected:

Both module code as well as class functions will always be interpreted in strict mode.

The magic "use strict" string merely needs to be the first statement within a file or function by the way. Both whitespace and comments can come first.

Note about Script-based Object Maps

Since JavaScript modules will be evaluated in strict mode script-based object maps may need fixing in case they rely on behavior which is not permitted in strict mode.

Exponentiation Operator

A short form of the Math.pow(base, exp) exponentiation function is available via the new ** operator. It can also be part of an assignment.

var n = 2 ** 3;  // <--- 8
n **= 3; // <--- 512

Parameter-less Catch Clause

The classic way of catching a script exception in a try/catch statement includes the identification of the thrown exception - e in the example below:

try {
    f();
} catch (e) {
    test.log("Function f() threw an exception");
}
try {
    f();
} catch {
    test.log("Function f() threw an exception");
}

Miscellaneous