© Peter Hoddie and Lizzie Prader 2020
P. Hoddie, L. PraderIoT Development for ESP32 and ESP8266 with JavaScripthttps://doi.org/10.1007/978-1-4842-5070-9_2

2. JavaScript for Embedded C and C++ Programmers

Peter Hoddie1  and Lizzie Prader1
(1)
Menlo Park, CA, USA
 

This chapter is a fast-paced, practical introduction to JavaScript for developers who are already familiar with C or C++. It assumes that you already know how to program and, perhaps, have some development experience with embedded systems. The JavaScript language introduced here is exactly the same language that’s used on the web. But since the focus here is on embedded systems rather than web browsers, this chapter addresses some aspects of JavaScript that are seldom used by developers working on the web. For example, consider that it’s almost impossible to write embedded software without manipulating binary data (such as an array of bytes); JavaScript supports binary data with built-in typed array classes, yet most web developers never use that feature because it’s unnecessary when building a web page. So even if you’re familiar with JavaScript on the web, you may want to read this chapter to familiarize yourself with language features more common on embedded systems than on the web.

C and C++ programmers have a big advantage when getting started with JavaScript, because the language looks quite similar to C. That’s no accident: the JavaScript programming language was designed to be similar to the Java programming language; Java was created as an evolution of C++; and C++ was created to bring object-oriented programming to C. The many similarities will help you quickly read and write JavaScript code. Still, the languages are also different in many respects. This chapter uses the similarities as a foundation to introduce you to some of the differences.

JavaScript is now more than 20 years old, and it’s constantly evolving. This chapter introduces modern JavaScript, including the features in the 2019 edition of JavaScript as well as some (like private fields) that are on track for inclusion in a future edition. Only features of JavaScript that are part of the standard language are described here. Because of JavaScript’s long history, certain features are no longer recommended for use; this chapter identifies some of them. In particular, JavaScript 5th Edition, standardized in 2012, introduced strict mode , which eliminates a handful of confusing and inefficient features. Those original behaviors remain available in sloppy mode, which is primarily used for backward compatibility for websites, but this book uses strict mode exclusively.

Fundamental Syntax

This section introduces fundamentals such as how to use JavaScript to make function calls, declare variables, and control the flow of execution with if, switch, for, and while statements. All of these are very similar in C and JavaScript, but you’ll learn about some important differences along the way.

“Hello, world”

The traditional starting point for learning C is the hello, world program from Kernighan and Ritchie’s book The C Programming Language. In JavaScript, it’s just one line:
trace("hello, world ");

Here, the C printf function is replaced by the trace function from the Moddable SDK. (Developers working with JavaScript on the web use console.log instead of trace.) As in C, the argument to the function is passed inside parentheses and the statement is terminated with a semicolon. The string literal passed to the function is identical, too—a string surrounded by double quotes—and uses the familiar backslash () notation as in C to escape special characters, such as the newline character here.

Semicolons

One significant difference between C and C++ is that the semicolon at the end of a statement is optional in JavaScript, thanks to the automatic semicolon insertion (ASI) feature. The following code is allowed in JavaScript but fails in C:
trace("hello, ")
trace("world")
trace(" ")

While this is convenient, since it saves a keystroke and silently fixes the common mistake of leaving out the semicolon, it creates ambiguities in certain obscure cases, which can result in bugs. Therefore, you should always end statements with a semicolon rather than relying on ASI. JavaScript linters, such as ESLint, include a check for missing semicolons.

Declaring Variables and Constants

Variables in JavaScript are declared with the let statement:
let a = 12;
let b = "hello";
let c = false;

Unlike in C, the variable declaration doesn’t include any type information (such as int, bool, or char *). That’s because any variable may hold any type. This dynamic typing, which is further explained later in this chapter, is one feature of JavaScript that takes C programmers a little time to get used to.

Variable names in JavaScript generally follow C conventions: they’re case-sensitive, so i and I are distinct names, and there’s no limit on the length of variable names. JavaScript variable names may also include Unicode characters, as in these examples:
let garçon = "boy";
let 東京都 = "Tokyo";
let $ = "dollar";
let under_score = "_";
You declare constant values with const:
const PRODUCT_NAME = "Light Sensor";
const LUMEN_REGISTER = 2;
const USE_OPTIMIZATIONS = true;

Any attempt to assign a value to a constant generates an error; however, unlike in C, this error is generated at runtime rather than at build time.

As shown in Listing 2-1, declarations made with let and const obey the same scoping rules as declarations in C.
let x = 1;
const y = 2;
let z = 3;
if (true) {
    const x = 2;
    let y = 1;
    trace(x);   // output: 2
    trace(y);   // output: 1
    trace(z);   // output: 3
    y = 4;
    z += y;
}
trace(x);       // output: 1
trace(y);       // output: 2
trace(z);       // output: 7

Listing 2-1.

JavaScript also lets you use var to declare variables and this is still common, since let is a relatively new addition. However, this book recommends using let exclusively, because var doesn’t obey the same scoping rules as declarations in C.

The if Statement

The if statement in JavaScript has the same structure as in C, as illustrated in Listing 2-2.
if (x) {
    trace("x is true ");
}
else {
    trace("x is false ");
}

Listing 2-2.

As in C, when the if or else block is a single statement you can omit the braces that delimit the block:
if (!x)
    trace("x is false ");
else
    trace("x is true ");
The condition in the if statement in Listing 2-2 is simply x. In C, this means that if x is 0, the condition is false; otherwise, it’s true. In JavaScript, this is more complex, because the variable x may be of any type, not just a number (or a pointer, but pointers don’t exist in JavaScript). JavaScript has defined the following rules to evaluate whether a given value is true or false:
  • For a boolean value, this determination is simple: the value is either true or false.

  • For a number, JavaScript follows the rule of C, treating a value of 0 as false and all other values as true.

  • An empty string (a string with length 0) evaluates to false, and all non-empty strings evaluate to true.

In JavaScript, a value that evaluates to true in a condition is called “truthy,” and one that evaluates to false is called “falsy.”

A compact form of an if statement, the conditional (ternary) operator, is available in JavaScript and has the same structure as in C:
let x = y ? 2 : 3;

The switch Statement

As shown in Listing 2-3, the switch statement in JavaScript looks very much as it does in C.
switch (x) {
    case 0:
        trace("zero ");
        break;
    case 1:
        trace("one ");
        break;
    default:
        trace("unexpected! ");
        break;
}

Listing 2-3.

There’s one important difference, however: the value following the case keyword is not limited to integer values. For example, you can use a floating-point number (see Listing 2-4).
switch (x) {
    case 0.25:
        trace("one quarter ");
        break;
    case 0.5:
        trace("one half ");
        break;
}

Listing 2-4.

You can also use strings (Listing 2-5).
switch (x) {
    case "zero":
    case "Zero":
        trace("0 ");
        break;
    case "one":
    case "One":
        trace("1 ");
        break;
    default:
        trace("unexpected! ");
        break;
}

Listing 2-5.

In addition, JavaScript lets you mix the types of values in the case statements, though that’s seldom necessary.

Loops

JavaScript has both for and while loops that look similar to their C language counterparts (see Listing 2-6).
for (i = 0; i < 10; i++)
    trace(i);
let j = 12;
while (j--)
    trace(j);

Listing 2-6.

JavaScript loops support both continue and break (Listing 2-7).
for (i = 0; i < 10; i++) {
    if (i & 1)
        continue;   // Skip odd numbers
    trace(i);
}
let j = 0;
do {
    let jSquared = j * j;
    if (jSquared > 100)
        break;
    trace(jSquared);
    j++;
} while (true);

Listing 2-7.

Types

JavaScript has just a handful of built-in types, from which all other types are created. Many of these types are familiar to C and C++ programmers, such as Boolean, Number, and String, though there are differences in the JavaScript versions of these that you need to understand. Other types, such as undefined, do not have an equivalent in C or C++.

Note that this section doesn’t introduce all the types. It omits RegExp, BigInt, and Symbol, for example, because they’re not commonly used when developing in JavaScript for embedded systems; they are available, however, should your project require them.

undefined

In C and C++, an operation can have a result that’s not defined by the language. For example, if you forget to initialize x in the following code, the value of y is unknown:
int x;
int y = x + 1;      // ??
Also, the result of a function is unknown if you forget to include a return statement:
int add(int a, int b) {
    int result = a + b;
}
int z = add(1, 2);  // ??

Your C or C++ compiler usually detects these kinds of mistakes and issues warnings so you can fix the problem. Still, there are many ways to make mistakes in C and C++ that result in code with results that are unpredictable.

In JavaScript, there’s never a situation in which the result is unpredictable. One part of how this is achieved is with the special value undefined, which indicates that no value has been assigned. In C, 0 is sometimes used as an invalid value for a similar purpose, but it’s ambiguous in situations where 0 is a valid value.

When you define a new local variable, it has the value of undefined until you assign another value to it. If your function exits without a return statement , its return value is undefined. You’ll see other uses of undefined throughout this chapter.

Strictly speaking, JavaScript has an undefined type, which always has the value undefined.

Boolean Values

The boolean values in JavaScript are true and false. These are not the same as 1 and 0 in C; they’re distinct values defined by the language.
let x = 42;
let y = x == 42;    // true
let z = x == "dog"; // false

Numbers

Every number value in JavaScript is defined to be a double-precision (64-bit) IEEE 754 floating-point value. Before you gasp in horror at the performance implications of this on a microcontroller, know that the XS JavaScript engine used in the Moddable SDK internally stores numbers as integers and performs integer math operations on them when possible. This ensures that the implementation is efficient on microcontrollers without an FPU while maintaining full compatibility with standard JavaScript.
let x = 1;
let y = -2.3;
let z = 5E2;    // 500

There are some benefits to having every number defined to be 64-bit floating-point. For one thing, integer overflow is much less likely. If the result of an integer operation overflows a 32-bit integer, it’s automatically promoted to a floating-point value. A 64-bit floating-point value can store integers up to 53 bits before losing precision. If you do happen to perform a math operation that generates a fractional result—for example, dividing an odd integer by 2—JavaScript returns the accurate fractional result as a floating-point value.

Infinity and NaN

JavaScript has some special values for numbers:
  • Dividing by 0 doesn’t generate an error but instead returns Infinity .

  • Attempting to perform a nonsense operation returns NaN , meaning “not a number.” For example, 5 / "a string" and 5 + undefined return NaN because it doesn’t make sense to divide an integer by a string value or add undefined to an integer.

Bases

JavaScript has special notation for hexadecimal and binary constants:
  • A 0x prefix on a number means it’s in hexadecimal notation, just as it does in C.

  • A 0b prefix on a number means it’s in binary notation, as supported in C++14.

These prefixes are useful when working with binary data, as in the following examples:
let hexMask = 0x0F;
let bitMask = 0b00001111;

Unlike C, JavaScript doesn’t support octal numbers with a leading 0, as in 0557; if you try to use one, it generates an error when building. Octal numeric literals are supported in the form 0o557.

Numeric Separators

JavaScript lets you use the underscore character (_) as a numeric separator, to separate digits in a number. The separator doesn’t change the value of the number but can make it easier to read. C++14 also has a numeric separator, but it uses the single quote character (') instead.
let mask = 0b0101101011110000;
let maskWithSeparators = 0b0101_1010_1111_0000;

Bitwise Operators

JavaScript provides bitwise operators for numbers, including the following:
  • ~ – bitwise NOT

  • & – bitwise AND

  • | – bitwise OR

  • ^ – bitwise XOR

It also provides these bitwise operators, for shifting bits:
  • >> – signed shift right

  • >>> – unsigned shift right

  • << – shift left

There’s no unsigned shift left because shifting left by a nonzero value always discards the sign bit. When performing any bitwise operation, JavaScript always first converts the value to a 32-bit integer; any fractional component or additional bits are discarded.

The Math Object

The Math object provides many of the functions C programmers use from the math.h header file. In addition to common constants such as Math.PI, Math.SQRT2, and Math.E, it includes common functions such as those shown in Listing 2-8.
let x = Math.min(1, 2, 3);  // minimum = 1
let y = Math.max(2, 3);     // maximum = 3
let r = Math.random();      // random number between 0 and 1
let z = Math.abs(-3.2);     // absolute value = 3.2
let a = Math.sqrt(100);     // square root = 10
let b = Math.round(3.9);    // rounded value = 4
let c = Math.trunc(3.9);    // truncated value = 3
let z = Math.cos(Math.PI);  // cosine of pi = -1

Listing 2-8.

Consult a JavaScript reference for a complete listing of the constant values and functions provided by the Math object.

Converting Numbers to Strings

In C, a common way to convert a number to a string is to use sprintf to print the number to a string buffer. In JavaScript, you convert a number to a string by calling the number’s toString method (yes, in JavaScript even a number is an object!):
let a = 1;
let b = a.toString();   // "1"
The default base for toString is 10; to convert to a non-decimal value, such as hexadecimal or binary, pass the base as the argument to toString:
let a = 240;
let b = a.toString(16);     // "f0"
let c = a.toString(2);      // "11110000"
To convert to floating-point notation, use toFixed instead of toString and specify the number of digits after the decimal point:
let a = 24.328;
let b = a.toFixed(1);   // "24.3"
let c = a.toFixed(2);   // "24.33"
let d = a.toFixed(4);   // "24.3280"

The functions toExponential and toPrecision provide additional formatting options for converting numbers to strings.

Converting Strings to Numbers

In C, a common way to convert a string to a number is to use sscanf. In JavaScript, use either parseInt or parseFloat depending on whether you want the result as an integer or a floating-point value:
let a = parseInt("12.3");       // 12
let b = parseFloat("12.30");    // 12.3
The default base for parseInt is 10, except when the string begins with 0x, in which case the default is 16. The parseInt function takes an optional second argument indicating the base. The following example parses a hexadecimal value:
let a = parseInt("F0", 16);     // 240

You can also access the functionality of parseInt and parseFloat as Number.parseInt and Number.parseFloat; however, this is less common.

Strings

In C, strings are not a distinct type but just an array of 8-bit characters. Because strings are so common, the C standard library provides many functions for working with them. Still, working with strings isn’t easy in C and can easily lead to security errors, like buffer overflows. C++ addresses some of the issues, though working with strings is still not easy or safe. JavaScript, by contrast, has a built-in String type that was designed to be easy for programmers to use and to use safely; this reflects JavaScript’s origin as the language of the web, where string manipulations are common in building web pages.

In JavaScript, strings differ from common C strings in many ways. A string is a sequence of 16-bit Unicode characters (UTF-16), not an array of 8-bit characters. Using Unicode to represent strings ensures that all applications can work reliably with string values in any language. Although the characters are conceptually 16-bit Unicode, the JavaScript engine may store them internally in any representation. The XS engine stores strings in UTF-8, so there’s no additional memory overhead for characters from the common 7-bit ASCII character set.

Accessing Individual Characters

JavaScript strings are not arrays; however, they do support C’s array syntax for accessing individual characters. Unlike in C, though, the result is not the Unicode (numeric) value of the character but a one-character string containing the character at that index.
let a = "garçon";
let b = a[3];   // "ç"
let c = a[4];   // "o"

In C, accessing an invalid index—for example, past the end of the string—returns an undefined value. For a declared as shown in the preceding code, a[100] will access whatever happens to be in memory 100 bytes after the start of the string. The access might even cause a memory fault by accessing unmapped memory. In JavaScript, attempting to read a character outside the valid range of a string returns the value undefined.

To get the Unicode value of a character at a given index, use the charCodeAt function :
let a = "garçon";
let b = a.charCodeAt(3);    // 231
let c = a.charCodeAt(4);    // 111
let d = a.charCodeAt(100);  // NaN

Modifying Strings

C lets you both read from and write to the characters in a string. JavaScript strings are read-only, also called immutable ; you cannot modify a string “in place.” For example, the assignment to a[0] in the following code does nothing in JavaScript:
let a = "a string";
a[0] = "A";

This restriction can be a little difficult to get used to coming from C, but it becomes familiar with some experience using the many methods provided to operate on a string.

Determining the Length of Strings

To determine the length of a string in C, you use the strlen function , which returns the number of bytes in the string. It determines the length by scanning for a byte with the value 0, because strings in C are defined to end on a 0 byte. In JavaScript, strings are a sequence of Unicode characters, with no terminating null character; the number of characters in the sequence is known to the JavaScript engine and is available through the length property.
let a = "hello";
let b = a.length;   // 5

One problem with strlen is that number of bytes in a string is only equal to the string’s length when the characters are 8-bit ASCII characters. For Unicode characters, strlen doesn’t provide the character count. Of course, there are other functions that do, but it’s a common mistake for C programmers to use strlen incorrectly with strings to get the character count, leading to bugs. The JavaScript length property avoids this problem because it always returns a character count.

The example in Listing 2-9 uses the length property to count the spaces in a string.
let a = "zéro un deux";
let spaces = 0;
for (let i = 0; i < a.length; i++) {
    if (a[i] == " ")
        spaces += 1;
}
trace(spaces);

Listing 2-9.

Embedding Quotes and Control Characters

The string literal values in this chapter up to this point have all used double quote marks (") to define the beginning and end of the string. Strings delimited by double quotes may contain single quotes (').
let a = "Let's eat!";
As in C, such strings cannot contain a double quote. Unlike in C, you can delimit a string with single quotes instead of double quotes, which is convenient for strings that contain double quotes.
let a = '"This is a test," she said.';
Strings delimited by single or double quotes must be entirely contained on a single line. You can include line breaks in the string by using to specify the newline character; the backslash () lets you escape characters just as in C.
let a = 'line 1 line 2 line 3 ';
Another way to delineate a string in JavaScript is to use the backtick character (`) . Strings defined in this way are called template literals and have several useful properties, including that they can span multiple lines (potentially making your strings more readable; compare Listing 2-10 to the preceding example).
let a =
`line 1
line 2
line 3
`;

Listing 2-10.

String Substitution

Template literals provide a substitution mechanism that’s useful for composing a string from several parts. The functionality this provides is very similar to using printf in C with a formatting string. However, whereas C separates the formatting string from the values to be formatted, JavaScript merges them. This may feel unfamiliar at first, but putting the values to be formatted directly into the string is less error-prone.
let a = "one";
let b = "two";
let c = `${a}, ${b}, three`;    // "one, two, three"
Inside a template literal, the characters between ${ and } are evaluated as a JavaScript expression, which enables you to perform calculations and apply formatting to the result:
let a = `2 + 2 = ${2 + 2}`;     // "2 + 2 = 4"
let b = `Pi to three decimals is ${Math.PI.toFixed(3)}`;
        // "Pi to three decimals is 3.142"
A special feature, called tags, enables a function to modify the default behavior of template literals. For example (as Chapter 4 will demonstrate), you can use this feature to convert the string representation of a UUID to binary data. The details of how tagged template literals work are beyond the scope of this chapter, but using them is easy: just put the tag before the template literal.
let a = uuid`1805`;
let b = uuid`9CF53570-DDD9-47F3-BA63-09ACEFC60415`;

Adding Strings

You can combine strings in JavaScript by using the addition operator (+):
let a = "one";
let b = "two";
let c = a + ", " + b + ", three";   // "one, two, three"
JavaScript lets you add strings to non-string values, such as numbers. Its rules for how this works usually give you the expected result, but not always:
let a = "2 + 2 = " + 4;         // "2 + 2 = 4"
let b = 2 + 2 + " = 2 + 2";     // "4 = 2 + 2"
let c = "2 + 2 = " + 2 + 2;     // "2 + 2 = 22"

Because remembering all the rules about type conversion during string addition can be difficult, it’s recommended that you instead use template literals, which are more predictable and often more readable.

Converting String Case

Converting strings to uppercase or lowercase in C is challenging, especially when you’re working with the complete set of Unicode characters. JavaScript has built-in functions for doing these conversions.
let a = "Garçon";
let b = a.toUpperCase();    // "GARÇON"
let c = a.toLowerCase();    // "garçon"

Notice that the toUpperCase and toLowerCase functions do not modify the original string, stored in the variable a in the preceding example, but rather return a new string with the modified value. All JavaScript functions that operate on strings behave this way, because all strings are immutable.

Extracting Parts of Strings

To extract part of a string into another string, use the slice function . Its arguments are the starting and ending indices, where the ending index is the index before which to end extraction. If the ending index is omitted, the string’s length is used.
let a = "hello, world!";
let b = a.slice(0, 5);      // "hello"
let c = a.slice(7, 12);     // "world"
let d = a.slice(7);         // "world!"

JavaScript also has a substr function , which provides similar functionality to slice but with slightly different arguments. However, slice is preferred over substr, which is maintained primarily for legacy code on the web.

Repeating Strings

To create a string that repeats a particular value several times, use the repeat function :
let a = "-";
let b = a.repeat(3);    // "---"
let c = ".-";
let d = c.repeat(2);    // ".-.-"

Trimming Strings

When parsing strings, you often want to remove white space (space character, tab, carriage return, line feed, and so on) at the start or end. The trim functions remove white space in a single step:
let a = " JS ";
let b = a.trim();        // "JS"
let c = a.trimStart();   // "JS "
let d = a.trimEnd();     // " JS"

The trim functions could be implemented entirely in JavaScript (as could most of the string functions), but building them into the language means their implementation is considerably faster and their behavior is consistent across all applications.

Searching Strings

The strstr function in C finds one string inside another. The indexOf function in JavaScript is similar to strstr. As shown in Listing 2-11, the first argument to indexOf is the substring to search for, the optional second argument is the character index at which to begin searching, and the result of the function is the index where the substring is found, or –1 if not found.
let string = "the cat and the dog";
let a = string.indexOf("cat");      // 4
let b = string.indexOf("frog");     // –1
let c = string.indexOf("the");      // 0
let d = string.indexOf("the", 2);   // 12

Listing 2-11.

Sometimes you want to find the last occurrence of a substring. In C, this requires calling strstr several times until no further matches are found. JavaScript provides the lastIndexOf function for this situation.
let string = "the cat and the dog";
let a = string.lastIndexOf("the");        // 12
let b = string.lastIndexOf("the", a - 1); // 0
When evaluating strings, it’s useful to check whether the string begins or ends with a particular string. You use strcmp and strncmp to do this in C. This situation is common enough that JavaScript provides dedicated startsWith and endsWith functions.
if (string.startsWith("And "))
    trace(`Don't start sentence with "and"`);
if (string.endsWith("..."))
    trace(`Don't end sentence with ellipsis`);

Functions

JavaScript has functions, of course, as does C. Some functions are very similar in both languages.
function add(a, b) {
    return a + b;
}

Function Arguments

Because JavaScript variables may hold any type of value, the type of arguments is not given, just their name. Also unlike in C and C++, there are no function declarations; you just write the source code to the function and then any code that can access the function can call it. This ad hoc approach allows for faster coding.

In C and C++, you can pass an argument value by reference, using a pointer, but in JavaScript you must always pass arguments by value. Consequently, a JavaScript function never changes the value of a variable passed to it. For example, the add function in Listing 2-12 doesn’t change the value of x.
function add(a, b) {
    a += b;
    return a;
}
let x = 1;
let y = 2;
let z = add(x, y);

Listing 2-12.

When you pass an object to a function, the function can modify the properties of the object but not the call’s local variable holding the object. This is similar to passing a pointer to a data structure in C. In Listing 2-13, the setName function adds the name property to the object passed to it. The assignment it makes, of a new empty object to its parameter a, doesn’t change the value of b.
function setName(a, name) {
    a.name = name;
    a = {};
}
let b = {};
setName(b, "thermostat");
// b.name is "thermostat"

Listing 2-13.

In C and C++, the implementation of a function can determine the number of arguments passed to it and can access each one using va_start, va_end, and va_arg. These are powerful tools but can be complicated to use. JavaScript also provides tools for working with the arguments to a function. Any arguments not passed by the caller are set to undefined , so (as done for b in Listing 2-14), you can check whether an argument has not been passed.
function add(a, b) {
    if (b == undefined)
        return NaN;
    return a + b;
}
add(1);

Listing 2-14.

Another way to access the parameters passed to the function is by using the special arguments variable, which behaves like an array containing the arguments. This approach is similar to using va_arg with the added benefit of knowing the number of arguments. In Listing 2-15, the add function accepts any number of arguments and returns their sum.
function add() {
    let result = 0;
    for (let i = 0; i < arguments.length; i++)
        result += arguments[i];
    return result;
}
let c = add(1, 2);
let d = add(1, 2, 3);

Listing 2-15.

The use of arguments is common in JavaScript, but it’s not available in all situations. It’s introduced here because you’re likely to see it in code. Modern JavaScript has an additional feature, called rest parameters , which provides similar functionality, is always available, and is more flexible (see Listing 2-16).
function add(...values) {
    let result = 0;
    for (let i = 0; i < values.length; i++)
        result += values[i];
    return result;
}

Listing 2-16.

Here ...values indicates that all remaining arguments (all arguments, in this example) are to be placed in an array named values. The code in Listing 2-17 adds a round parameter to control whether the values should be rounded before being summed.
function addR(round, ...values) {
    let result = 0;
    for (let i = 0; i < values.length; i++)
        result += round ? Math.round(values[i]) : values[i];
    return result;
}
let c = addR(false, 1.1, 2.9, 3.5); // c = 7.5
let d = addR(true, 1.1, 2.9, 3.5);  // d = 8

Listing 2-17.

Just as rest parameters combine several arguments into an array, spread syntax separates the content of an array into individual arguments. Spread syntax uses the same three-dot syntax as rest parameters. The function in Listing 2-18 sums the absolute value of its arguments; it first takes the absolute value of the arguments and then calls the add function using spread syntax to compute the sum.

function addAbs(...values) {
    for (let i = 0; i < values.length; i++)
        values[i] = Math.abs(values[i]);
    return add(...values);
}
let c = addAbs(-1, -2, 3);  // c = 6

Listing 2-18.

There are many other uses of spread syntax—for example, to clone an array:
let a = [1, 2, 3, 4];
let b = [...a];         // b = [1, 2, 3, 4]
You can also use spread syntax to concatenate two arrays:
let a = [1, 2];
let b = [3, 4];
let c = [...a, ...b];   // c = [1, 2, 3, 4]
In some situations, it’s useful to provide a default value for an argument. This isn’t possible in C but it is done in C++, using the same syntax as in JavaScript. In JavaScript, since arguments not passed by the caller are set to undefined , you can provide a default value for any parameter that has that value. The function in Listing 2-19 accepts a temperature value; if the units aren’t specified, a default of Celsius is used.
function setCelsiusTemperature(temperature) {
    trace(`setCelsiusTemperature ${temperature} `);
}
function setTemperature(temperature, units = "Celsius") {
    switch (units) {
        case "Fahrenheit":
            temperature -= 32;
            temperature /= 1.8;
            break;
        case "Kelvin":
            temperature -= 273.15;
            break;
        case "Celsius":
            // no conversion needed
            break;
    }
    setCelsiusTemperature(temperature);
}
setTemperature(14); // units argument defaults to Celsius
setTemperature(14, "Celsius");
setTemperature(57, "Fahrenheit");

Listing 2-19.

Unlike in C, every function in JavaScript has a return value; there’s no way for a function to exit without a well-defined return value. Consider the three functions shown in Listing 2-20.
function a() {
    return undefined;
}
function b() {
    return;
}
function c() {
}

Listing 2-20.

Function a explicitly returns the value undefined. Function b provides no value to the return statement and so returns undefined. Function c has no return statement but returns undefined just like function b because undefined is the default value that all functions return. You’ll find all three of these forms in JavaScript code, depending on the preference of the code’s author. They’re indistinguishable by the caller of the function.

By contrast, the following code is allowed in C. The result of function c is whatever value happens to be in the memory or register reserved for the return value.
int c(void) {
}
int b = c();    // b is unknown

Passing Functions As Arguments

In C, it’s common to pass a function a pointer to another function, enabling you to customize the behavior of the function being passed—for example, to provide a comparison function to use when sorting. Similarly, JavaScript functions may be passed as arguments to another function, as shown in Listing 2-21.
function square(a) {
    return a * a;
}
function circleArea(r) {
    return Math.PI * r * r;
}
function sum(filter, ...values) {
    let result = 0;
    for (let i = 0; i < values.length; i++)
        result += filter(values[i]);
    return result;
}
let a = sum(square, 1, 2, 3);   // 14
let b = sum(circleArea, 1);     // 3.14...

Listing 2-21.

You can also pass built-in functions as arguments. For example, the following code calculates the sum of the square roots of the remaining arguments:
let c = sum(Math.sqrt, 1, 4, 9);    // 6
Often when a function is passed, that function is used in only that one place. In C, the function implementation is often not located near where it’s called, which hurts readability. Unlike C, JavaScript allows anonymous (unnamed) inline functions. The following example calls the sum function defined in Listing 2-21 to calculate the sum of areas of equilateral triangles using an anonymous inline function as the filter:
let a = sum(function(a) {
    return a * (a / 2);
}, 1, 2, 3);    // 7
Anonymous functions are widely used in JavaScript code for various kinds of callbacks. Seeing the source code to a function as the argument to a function call is a little unusual, but you do get used to it. If you prefer to keep function implementations separate from function calls, you can use nested functions instead. In Listing 2-22, the function triangleArea is visible only inside the function main. Using a nested function keeps the implementation of the filter function near the place where it’s used, often improving the maintainability of the code.
function main() {
    function triangleArea(a) {
        return a * (a / 2);
    }
    let a = sum(triangleArea, 1, 2, 3);  // 7
}

Listing 2-22.

Declaring Functions

As noted earlier, there are no function declarations in JavaScript, unlike in C and C++: when you declare a function in JavaScript, you’re actually declaring a variable. The following line of code, using the common syntax for declaring a function, creates a local variable named example:
function example() {}
The following line also creates a local variable named example, assigning an anonymous function to it:
let example = function() {};
These two lines of code are equivalent, and both functions can be called in the same way. But because both forms create a local variable, you can’t have a function and a local variable with the same name. You can, however, change the function that a local variable references, as shown in Listing 2-23.
function log(a) {
    trace(a);
}
log("one");
// Disable logging
let originalLog = log;
log = function(a) {}
log("two");
// Reenable logging
log = originalLog;
log("three");

Listing 2-23.

Closures

One of the most powerful features of JavaScript functions is closures . They’re commonly used for callback functions. A closure binds together a function with a group of variables outside the function. The references to outside variables persist for the lifetime of the closure. Closures don’t exist in C and were only added to in C++ in 2011, as lambda expressions; consequently, many developers working in C and C++ are unfamiliar with them. Despite the obscure name, closures are so easy to use that it’s easy to forget you’re using them.

Listing 2-24 uses a closure to implement a counter. The makeCounter function returns a function. You can have one function return a pointer to another function in C, but there’s a difference here: the anonymous function that’s returned references a variable named value, and that variable is not local to the anonymous function; instead it’s a local variable in the makeCounter function that the anonymous function is contained in.
function makeCounter() {
    let value = 0;
    return function() {
        value += 1;
        return value;
    }
}

Listing 2-24.

Each time the function returned by makeCounter is called, it increments value and returns that value. Here’s how it works: When a function references variables outside its own local scope, it’s said to “close” over those variables, automatically creating a closure. In this example, using the variable value in the anonymous function creates a closure that lets it access the local variable value from makeCounter. JavaScript makes it safe to use that local variable even after makeCounter returns and the stack frame of makeCounter has been deallocated (see Listing 2-25).
let counter = makeCounter();
let a = counter();  // 1
let b = counter();  // 2
let c = counter();  // 3

Listing 2-25.

The example in Listing 2-25 does what you expect: the makeCounter function returns a counter function; each time the counter function is called, it increments the counter and returns the new value. But what happens if you call makeCounter twice? Does the second call return a separate counter or a reference to the first counter? For the answer, see Listing 2-26.
let counterOne = makeCounter();
let counterTwo = makeCounter();
let a = counterOne();   // 1
let b = counterOne();   // 2
let c = counterTwo();   // 1
let d = counterTwo();   // 2
let e = counterOne();   // 3
let f = counterTwo();   // 3

Listing 2-26.

As you can see, each time makeCounter is called, the function it returns has a new closure with a separate copy of value.

If it’s difficult right now to imagine how you might use closures in your own code, don’t worry; many programmers use them without even realizing it. Closures are common in APIs that use callback functions; when the callback function is installed, it often closes over variables that it uses when the callback is invoked.

If you have experience with object-oriented programming, you may recognize closures used this way as being similar to object instances, and in fact they can be used for that. However, JavaScript has better alternatives, using classes (introduced later in this chapter).

Objects

JavaScript is an object-oriented programming language; C is not. There are few practical ways to use JavaScript without using objects. In the earlier sections of this chapter, even common operations on numbers and strings required calling methods of the number and string objects. C++ is an object-oriented language, but C++ and JavaScript take very different approaches to objects. For example, C++ has class templates, operator overloading, and multiple inheritance—none of which are part of JavaScript. If you’re coming from C, you’ll need to learn a bit about objects. If you’re coming from C++, you’ll need to learn about JavaScript’s more compact approach to objects. The good news is that millions of developers have successfully used objects in JavaScript to build web pages, web services, mobile apps, and embedded firmware.

To create objects in JavaScript, you use the new keyword, as in C++. All objects in JavaScript descend from Object , a built-in object. The following lines create an instance of Object:
let a = new Object();
let b = new Object;

Object is a special kind of function called a constructor. When the Object constructor is invoked with new, an instance of Object is created and the constructor function is executed to initialize the instance. If the constructor function is passed no arguments, the parentheses for the argument list are optional. Therefore, the preceding two lines are identical; which form you use is a matter of personal coding style.

There are many other objects that are built into JavaScript. Listing 2-27 shows examples of how the constructor is called for some of them. Details about these and other built-in objects are provided in later sections of this chapter.
let a = new Array(10);           // array of length 10
let b = new Date("September 6, 2019");
let c = new Date;                // current date and time
let d = new ArrayBuffer(128);    // 128-byte buffer
let e = new Error("bad value");

Listing 2-27.

The base object, Object, doesn’t do much by itself. Still, it’s common in JavaScript code because it can be used as an ad hoc record. In C, you use a structure (struct) to hold a set of values; in C++, you use either a structure or a class (struct or class). A JavaScript object, unlike a structure in C or C++, is not a fixed set of fields. What C calls fields are called properties in JavaScript. As illustrated in Listing 2-28, you can add properties to an object whenever you want; they don’t have to be declared in advance.
let a = new Object;
a.one = 1;
a.two = "two";
a.object = new Object;
a.add = function(a, b) {
    return a + b;
};

Listing 2-28.

Because creating these ad hoc objects is so common, JavaScript provides a shortcut: you can use {} in place of new Object. The result is identical, but the code is more compact. You can initialize properties of an object by enumerating the properties within the braces. The following is equivalent to the preceding example:
let a = {one: 1, two: "two", object: {},
         add: function(a, b) {return a + b;}};

JavaScript developers tend to prefer the braces style (and this book uses it almost exclusively) because it’s more compact and more readable.

Object Shorthand

It’s common to store the result of several calculations in local variables and then put those into an object. When the local variables have the same name as the properties of the object, the code looks redundant, as in the example in Listing 2-29.
let one = 1;
let two = "two";
let object = {};
let add = function(a, b) {return a + b;};
let result = {one: one, two: two, object: object, add: add};

Listing 2-29.

Because this situation happens frequently, JavaScript provides a shortcut for it. The code in Listing 2-30 is equivalent to the preceding example.
let one = 1;
let two = "two";
let object = {};
let add = function(a, b) {return a + b;};
let result = {one, two, object, add};

Listing 2-30.

Another shortcut is available for defining properties that have a function as their value. Listing 2-31 shows the straightforward approach.
let object = {
    add: function(a, b) {
        return a + b;
    },
    subtract: function(a, b) {
        return a - b;
    }
};

Listing 2-31.

Listing 2-32 shows the shortcut version, which eliminates the colon (:) and the function keyword.
let object = {
    add(a, b) {
        return a + b;
    },
    subtract(a, b) {
        return a - b;
    }
};

Listing 2-32.

In addition to being more compact and readable, this same syntax is used for defining classes in JavaScript, as you’ll soon see.

Deleting Properties

Not only can you add properties to a JavaScript object at any time, but you can also remove them. Properties are removed using the delete keyword :
delete a.one;
delete a.missing;

Once a property is deleted, getting it from the object gives the value undefined. You may recall that this is the same value that’s returned when you try to access a character of a string beyond the string’s length. It’s not an error to use delete on a property that the object doesn’t have. For example, no error is generated when (given object a with properties one, two, and object) a.missing is deleted as shown previously.

C++ programmers are familiar with delete as the way to destroy an object and so might expect that deleting a property would destroy the object referenced by the property; however, the delete keyword in JavaScript is different, as discussed later in the “Memory Management” section.

Checking for Properties

Because properties can come and go at any time, sometimes you need to check whether a particular property is present on an object. There are two ways to do this. Since any missing property has the value undefined, you could check whether getting a property gives the value undefined.
if (a.missing == undefined)
    trace("a does not have property 'missing'");
But don’t do that! There are several subtle problems that can arise. For example, consider this code:
let a = {missing: undefined};
if (a.missing == undefined)
    trace("a does not have property 'missing'");
Here, the object has a missing property that happens to have a value of undefined. There are other ways this check can fail, but for now this example is enough to demonstrate the need for a better solution. Using the keyword in is a better way to check for the existence of a property. The following example works in all situations:
if (!("missing" in a))
    trace("a does not have property 'missing'");

Adding Properties to Functions

Functions in JavaScript are objects, which means you can add and remove properties of a function just as you would with any other object. Listing 2-33 defines a function named calculate that supports three operations, each corresponding to a property of the function that’s assigned a constant: add is 1, subtract is 2, and multiply is 3. The operations defined here are similar to an enumeration in C or C++. However, instead of being defined separately from the calculate function as an enum in C or C++, the operation values are attached directly to the function that uses them. This way of providing names for constants is used in some parts of the Moddable SDK.
function calculate(operation, a, b) {
    if (calculate.add == operation)
        return a + b;
    if (calculate.subtract == operation)
        return a - b;
    if (calculate.multiply == operation)
        return a * b;
}
calculate.add = 1;
calculate.subtract = 2;
calculate.multiply = 3;
let a = calculate(calculate.add, 1, 2);         // 3
let b = calculate(calculate.subtract, 1, 2);    // -1

Listing 2-33.

Freezing Objects

There are situations in which you want to ensure that the properties of an object cannot be changed. You might be tempted to use const to achieve that:
const a = {
    b: 1
};
However, that doesn’t work. Using const doesn’t make the object on the right side of the = in the constant declaration read-only; in this example, it makes only a read-only. Consider these subsequent assignments:
a = 3;      // generates an error
a.b = 2;    // OK - can change existing property
a.c = 3;    // OK - can add new property
To prevent modifications to the object that is the value of the constant, you can use Object.freeze, a built-in function that makes all existing properties of the object read-only and prevents new properties from being added. As you can see in Listing 2-34, attempts to change the value of a property in the frozen object or add a new property to the object generate errors.
const a = Object.freeze({
    b: 1
});
a = 3;      // generates an error
a.b = 2;    // error - can't change existing property
a.c = 3;    // error - can't add new property

Listing 2-34.

Note that Object.freeze returns the object passed to it, which is convenient in this example because it avoids adding a line of code. Object.freeze is rarely used in JavaScript for the web today, but the Moddable SDK uses it extensively because it enables objects to be stored efficiently in ROM or flash memory on embedded devices, saving limited RAM.

Object.freeze is a shallow operation, which means it doesn’t freeze nested objects. In Listing 2-35, for example, the nested object assigned to the property c is not frozen.
const a = Object.freeze({
    b: 1,
    c: {
        d: 2
    }
});
a.c.d = 3;  // OK
a.c.e = 4;  // OK
a.b = 2;    // error - can't change existing property
a.e = 3;    // error - can't add new property

Listing 2-35.

You could explicitly freeze c, but that begins to get verbose and error-prone, as shown in Listing 2-36.
const a = Object.freeze({
    b: 1,
    c: Object.freeze({
        d: 2
    })
});

Listing 2-36.

Because freezing objects helps optimize memory use on embedded devices, the XS JavaScript engine used in the Moddable SDK extends Object.freeze with an optional second argument which enables a deep freeze—that is, recursively freezing all nested objects (see Listing 2-37).
const a = Object.freeze({
    b: 1,
    c: {
        d: 2
    }
}, true);
a.c.d = 3;  // error - can't change existing property
a.c.e = 4;  // error - can't add new property

Listing 2-37.

Note that this extension to Object.freeze isn’t part of the JavaScript language standard, so it doesn’t work in most environments. It does, however, address a common need in embedded development. Perhaps a future edition of the JavaScript language will support this capability.

If your code needs to know whether an object is frozen, you can use Object.isFrozen. Like Object.freeze, this is a shallow operation, so it doesn’t tell you whether any nested objects are frozen.
if (!Object.isFrozen(a)) {
    a.b = 2;
    a.c = 3;
}

Freezing an object is a one-way operation: there is no Object.unfreeze. This is because Object.freeze is sometimes used as a security measure to prevent untrusted client code from tampering with the object. If the untrusted code could unfreeze the object, it would enable the security measure to be breached.

null

Like C and C++, JavaScript code uses the value null. In C and C++, this is written as NULL to indicate that it’s defined using a macro; in JavaScript, null is a built-in value.

C uses NULL as the value for pointers that don’t currently reference anything. JavaScript has no pointers, so this meaning doesn’t make sense. In JavaScript, null is a value indicating that there’s no reference to an object. The value null is considered as a special null object and consequently has a type of Object.

It’s easy to get null and undefined confused. They’re similar, but not identical: undefined means no value has been given; null explicitly states that there’s no object reference, which implies that the variable or property will hold an object at some point during its execution. As a rule, when a local variable or object property is intended to reference an object, assign the value null when there’s no object.

Comparisons

Comparing two values in C is straightforward, for the most part, because you’re usually comparing two values of the same type. In a few cases, the C language applies type conversion before comparing. This enables you to, for example, compare a uint8_t value to a uint32_t value without having to explicitly convert the type of either value. C++ makes comparisons considerably more powerful by providing operator overloading, enabling programmers to provide their own implementations of the comparison operators for types they define. JavaScript is much more like C than C++ in this regard; it doesn’t support operator overloading, so the behavior of comparisons is fully defined by the JavaScript language.

Like C, JavaScript implicitly converts certain types when performing a comparison with the equality operator (==). Listing 2-38 shows a few examples.
let a = 1 == "1";       // true
let b = 0 == "";        // true
let c = 0 == false;     // true
let d = "0" == false;   // true
let e = 1 == true;      // true
let f = 2 == true;      // false
let g = Infinity == "Infinity";  // true

Listing 2-38.

As you can see, the rules for how types are converted in a comparison aren’t always what you might expect. For this reason, JavaScript programmers often avoid implicit conversions by using the strict equality operator (===) instead, as shown in Listing 2-39. The strict equality operator never performs type conversion; if the two values are of different types, they’re always unequal.
let a = 1 === "1";      // false
let b = 0 === "";       // false
let c = 0 === false;    // false
let d = "0" === false;  // false
let e = 1 === true;     // false
let f = 2 === true;     // false
let g = Infinity === "Infinity";  // false

Listing 2-39.

JavaScript also provides a strict inequality operator (!==), which can be used in place of the inequality operator (!=) to avoid type conversion:
let a = 1 !== "1";      // true
let b = 0 !== "";       // true
let c = 0 !== false;    // true

In many cases, there’s no harm in using == and != instead of the strict versions. However, the edge cases in which the behaviors are different can introduce bugs that are difficult to track down. Therefore, the current best practice in JavaScript programming is to always use the strict versions of the operators.

Some of the examples in this chapter that precede the introduction of the strict comparison operators use == and !=. Now that you know about the strict versions of these operators and why they’re preferred, the examples in the remainder of this book will use only the strict operators.

Comparing Objects

When two objects are compared in JavaScript, they’re equal only if they reference the same instance. This is usually what you expect, though sometimes developers incorrectly expect that if all the properties of two different instances are equal, the result of the equality comparison is true. This kind of deep comparison is not provided directly by JavaScript, though it may be implemented in your application if needed.
let a = {b: 1};
let b = a === {b: 1};   // false
let c = a;
let d = a === c;        // true

In C++, the default behavior for comparing objects is the same as in JavaScript. Using operator overloading, C++ programmers can perform deep comparisons if the class implements support.

Errors and Exceptions

JavaScript includes a built-in Error type which is used to report problems that occur during execution. Errors are almost exclusively used together with JavaScript’s exception mechanism, which is similar in many ways to C++ exceptions. The C language doesn’t include exceptions, though similar functionality is often built using setjmp and longjmp in the C standard library.

To create an error, invoke the Error constructor. To help with debugging, you can provide an optional error message.
let a = new Error;
let b = new Error("invalid value");

There are other kinds of errors, which are used to indicate a specific problem. These include RangeError, TypeError, and ReferenceError. You use them in the same way as Error. It’s most common to simply use Error, but you can use the others if they fit your situation.

Once you have an error, you report it using a throw statement (Listing 2-40).
function setTemperature(value) {
    if (value < 0)
        throw new RangeError("too cold");
    ...
}

Listing 2-40.

You can specify any value following the throw statement, though by convention the value is usually an instance of an error.

When an exception is thrown, the current execution path ends. Execution resumes at the first catch block on the stack. If there are no catch blocks on the stack, the exception is considered an unhandled exception. Unhandled exceptions are ignored, meaning the host doesn’t attempt to handle the exception. To catch an exception, you write try and catch blocks just as in C++; Listing 2-41 follows from the preceding example to illustrate this.
try {
    setTemperature(-1); // throws an exception
    // Execution never reaches here
    displayMessage("Temperature set to -1 ");  
}
catch (e) {
    trace(`setTemperature failed: ${e} `);
}

Listing 2-41.

When setTemperature generates an exception in this example, execution jumps to the catch block, skipping over the call to displayMessage. The argument to the throw statement as shown in Listing 2-40—the RangeError instance created by the setTemperature function—is provided here in the local variable named e that’s specified in parentheses following the catch keyword. If your catch block doesn’t use that value, you can omit the parentheses following catch, as shown in Listing 2-42.
try {
    setTemperature(-1); // throws an exception
    // Execution never reaches here
    displayMessage("Temperature set to -1 "); 
}
catch {
    trace("setTemperature failed ");
}

Listing 2-42.

After catching the error, you have the option to propagate it, as if it hadn’t been caught. This is useful if you want to perform cleanup when the error occurs and the error also needs to be handled by code farther up the call stack. To propagate the exception, use the throw statement inside the catch block (Listing 2-43).
try {
    setTemperature(-1); // throws an exception
    // Execution never reaches here
    displayMessage("Temperature set to -1"); 
}
catch (e) {
    trace(`setTemperature failed: ${e} `);
    throw e;
}

Listing 2-43.

Your exception handling may also include a finally block , as shown in Listing 2-44. (Standard C++ doesn’t provide finally, but it’s part of Microsoft’s dialect of C++.) The finally block is always called, no matter how the exception is handled, or even if it isn’t caught by a catch block.
try {
    setTemperature(-1);
}
catch (e) {
    trace(`setTemperature failed: ${e} `);
    throw e;
}
finally {
    displayMessage(`Temperature set to ${getTemperature()} `); // always executes
}

Listing 2-44.

In Listing 2-44, the call to displayMessage happens regardless of whether setTemperature throws an exception. When using finally, you can omit the catch block (Listing 2-45), in which case the exception will continue to propagate up the stack after the finally block executes.
try {
    setTemperature(-1);
}
finally {
    displayMessage(`Temperature set to ${getTemperature()}`);
}

Listing 2-45.

When an exception is not handled—for example, when setTemperature throws an exception in Listings 2-44 and 2-45—a warning is traced to the debug console. It isn’t necessarily a mistake to leave an exception caught, but it can be an indication of a problem. The warning may include the name of the function that detected the uncaught exception; this is a native function, often part of the Moddable SDK runtime, so the name may be unfamiliar.

While these examples have just a few lines of code in the try blocks, real-world code often has large blocks of code within a single try block. This enables you to keep the code for handling errors small and isolated, rather than having it be part of each function call as can be the case in C.

The combination of try, catch, and finally blocks gives you a great deal of flexibility in how your code responds or doesn’t respond to exceptions. Don’t worry too much about using them as you get started. It’s common to write code without exception handling and then add it later when you address the failure cases.

Classes

Like C++, JavaScript lets you create your own kinds of objects by defining classes. In JavaScript, you use the class keyword to define and implement your classes. Classes in JavaScript are quite a bit simpler than in C++. Even if you don’t expect to create your own classes, you should become familiar with JavaScript classes so that you’ll be able to understand code written by others.

Earlier versions of JavaScript didn’t have the class keyword, making it more difficult to create classes. The keyword was introduced in the 6th Edition of the language standard (often referred to as “ES6”) in 2015. Before that, JavaScript developers created classes using lower-level approaches, including Object.create, or directly manipulated the object’s prototype property. While these techniques still work and are common in legacy code in the web, this section focuses on modern JavaScript, where class enables code to be more readable and has no impact on runtime performance.

Class Constructor and Methods

Listing 2-46 shows a simple class, Bulb, representing a light bulb that can be either on or off.
class Bulb {
    constructor(name) {
        this.name = name;
        this.on = false;
    }
    turnOn() {
        this.on = true;
    }
    turnOff() {
        this.on = false;
    }
    toString() {
        return `"${this.name}" is ${this.on ? "on" : "off"}`;
    }
}

Listing 2-46.

Unlike in C++, there’s no declaration of the class; there’s only an implementation. The syntax used to define the functions in the class is the same as you’ve already seen for functions outside a class (in the “Object Shorthand” section). However, unlike when functions are defined as properties in an ordinary object, in a class there are no commas between the functions.

As you can see in Listing 2-46, the Bulb class is a collection of functions. The function named constructor in a class is special; it’s called automatically when the object is created. The constructor performs any necessary initialization before the new instance is returned to the creator. The following code creates an instance of Bulb:
let wallLight = new Bulb("wall light");
wallLight.turnOn();
Another special function in JavaScript classes is toString. This function is called automatically in situations where JavaScript wants the string representation of the object. The toString method of Bulb provides a summary of the current state, which is useful for debugging.
let wallLight = new Bulb("wall light");
wallLight.turnOn();
trace(wallLight);
// output: "wall light" is on

Because the trace function outputs strings, it converts its argument to a string, which invokes the toString method. You can also call toString directly, as in wallLight.toString().

The toString method is a special case in JavaScript; there are no other conversion functions, such as toNumber.

Note

The invocation of a class constructor must occur after the class is defined. This means that you can only call new Bulb after the definition of the class in Listing 2-46, not before. Invoking it before that throws a runtime exception with the message get Bulb: not initialized yet!.

Static Methods

As in C++, a JavaScript class may include static methods, meaning functions that are accessed through the class rather than the instance. A simple example of a static method is one that returns the version of the implementation (Listing 2-47).
class Bulb {
    ...     // as earlier
    static getVersion() {
        return 1.2;
    }
}

Listing 2-47.

Static methods are attached to the class and therefore can be called even before an instance is created.
if (Bulb.getVersion() < 1.5)
    throw new Error("incompatible version");

Subclasses

Much of the power of classes comes from the ability to create subclasses. In JavaScript, you use the extends keyword to create a subclass. The code in Listing 2-48 implements DimmableBulb as a subclass of the Bulb class defined in Listing 2-46.
class DimmableBulb extends Bulb {
    constructor(name) {
        super(name);
        this.dimming = 100;
    }
    setDimming(value) {
        if ((value < 0) || (value > 100))
            throw new RangeError("bad dimming value");
        this.dimming = value;
    }
}

Listing 2-48.

As you’d expect from a subclass, the DimmableBulb class inherits the turnOff and turnOn methods from Bulb. The constructor function requires some explanation. It immediately calls super with the same argument as was passed to it. In a JavaScript class, super is a reference to the constructor of the superclass—here, the constructor of Bulb. Therefore, the first task performed by the DimmableBulb constructor is to construct its superclass, Bulb.

While the constructor of a subclass may perform calculations before calling the constructor of its superclass, it must eventually call it. Until it does, this is undefined, so any attempt to get or set properties on the instance will fail. For example, modifying the DimmableBulb constructor as shown in Listing 2-49 generates an exception when it attempts to set the dimming property, because this is not yet available.
class DimmableBulb extends Bulb {
    constructor(name) {
        this.dimming = 100; // throws an exception
        super(name);
    }
    ...
}

Listing 2-49.

The DimmableBulb implementation also inherits the toString method from Bulb. The implementation of toString for Bulb doesn’t print the dimming level; the implementation of toString for DimmableBulb (Listing 2-50) adds the dimming level, by first calling the toString method in Bulb (as specified by super) and then appending the dimming level to that result.
class DimmableBulb extends Bulb {
    ...
    toString() {
        return super.toString() +
               ` with dimming ${this.dimming}`;
    }
}

Listing 2-50.

The built-in Object class is the ultimate superclass of all JavaScript classes. The Bulb class inherits directly from Object. This is implied by the absence of an extends clause in its implementation, but it can also be stated explicitly, as shown in Listing 2-51.
class Bulb extends Object {
    constructor(name) {
        super();
        this.name = name;
        this.on = false;
    }
    ...
}

Listing 2-51.

Note that because Bulb now explicitly extends Object, the Bulb constructor must invoke the constructor of the class it extends by calling super. If the call to super is omitted, accessing this throws an exception with the message Bulb: this is not initialized yet!.

Classes that descend directly from Object aren’t usually written this way, to keep the source code concise. But this example does hint at another feature of JavaScript classes: the ability to subclass built-in objects. The example in Listing 2-52 subclasses the built-in Array class (which you’ll learn more about soon) to add methods for finding the total and average of the values in the array.
class MyArray extends Array {
    sum() {
        let total = 0;
        for (let i = 0; i < this.length; i++)
            total += this[i];
        return total;
    }
    average() {
        return this.sum() / this.length;
    }
}
let a = new MyArray;
a[0] = 1;
a[1] = 2;
let b = a.sum();        // 3
let c = a.average();    // 1.5

Listing 2-52.

When building a product, you may have more than a single instance of Bulb. For example, you might be making a light switch that controls several light bulbs, and you might keep that list of lights in an array. You could create a subclass of Array for this purpose, with the subclass (Bulbs in the example in Listing 2-53) providing batch operations on the bulbs.
class Bulbs extends Array {
    allOn() {
        for (let i = 0; i < this.length; i++)
            this[i].turnOn();
    }
    allOff() {
        for (let i = 0; i < this.length; i++)
            this[i].turnOff();
    }
}
let bulbs = new Bulbs;
bulbs[0] = new Bulb("hall light");
bulbs[1] = new DimmableBulb("wall light");
bulbs[2] = new DimmableBulb("floor light");
bulbs.allOn();

Listing 2-53.

It would be nice to have a dimAll method in Bulbs, but that would only work for instances of DimmableBulb; calling setDimming on an instance of Bulb throws an exception because the method doesn’t exist. The JavaScript instanceof operator helps here, by enabling you to determine whether an instance corresponds to a particular class (Listing 2-54).
let a = new Bulb("hall light");
let b = new DimmableBulb("wall light");
let c = a instanceof Bulb;      // true
let d = b instanceof Bulb;      // true
let e = a instanceof DimmableBulb;  // false
let f = b instanceof DimmableBulb;  // true

Listing 2-54.

As you can see, instanceof checks the specified class, including its superclasses. In the example in Listing 2-54, this means that b is an instance of both DimmableBulb and Bulb, since Bulb is the superclass of DimmableBulb. With this knowledge, the implementation of dimAll is now possible (Listing 2-55).
class Bulbs extends Array {
    ...
    dimAll(value) {
        for (let i = 0; i < this.length; i++) {
            if (this[i] instanceof DimmableBulb)
                this[i].setDimming(value);
        }
    }
}

Listing 2-55.

The properties of the Bulb instance are ordinary JavaScript properties, making them available both to the class implementation and to code using the class:
let wallLight = new Bulb("wall light");
wallLight.turnOn();
trace(`Light on: ${wallLight.on} `);
That’s useful, but sometimes you want to use a different representation for the value inside the implementation than what you use in the API. For example, the setDimming method accepts values from 0 to 100, because percentages are a natural way to describe a dimming level; however, the implementation may prefer to store a value from 0 to 1.0 because that’s more efficient for its internal calculations. JavaScript classes support getters and setters that are useful for these kinds of transformations. The implementation in Listing 2-56 replaces the setDimming method with a getter and setter for the dimming property.
class DimmableBulb extends Bulb {
    constructor(name) {
        super(name);
        this._dimming = 1.0;
    }
    set dimming(value) {
        if ((value < 0) || (value > 100))
            throw new RangeError("bad dimming value");
        this._dimming = value / 100;
    }
    get dimming() {
        return this._dimming * 100;
    }
}
let a = new DimmableBulb("hall light");
a.dimming = 50;
a.dimming = a.dimming / 2;

Listing 2-56.

Users of the class access the dimming property as an ordinary JavaScript property. However, when the property is set, the set dimming setter method of the class is invoked, and when the property is read, the get dimming getter method is invoked.

Private Fields

The getter and setter in Listing 2-56 store the value in a property named _dimming. JavaScript code has long used an underscore (_) at the start of property names to indicate that they’re only for internal use. Unlike C++, JavaScript has not provided private fields in classes. Work on adding private fields to the JavaScript standard is nearly complete; this section introduces private fields as they’re expected to be in the JavaScript standard. Private fields are supported by the XS JavaScript engine for use in your embedded development.

Private fields in JavaScript are indicated by prefixing the field name with a hash character (#). The private field must be declared in the class body. Listing 2-57 shows the version of DimmableBulb in Listing 2-56 rewritten to use a private field named #dimming in place of _dimming.
class DimmableBulb extends Bulb {
    #dimming = 1.0;
    set dimming(value) {
        if ((value < 0) || (value > 100))
            throw new RangeError("bad dimming value");
        this.#dimming = value / 100;
    }
    get dimming() {
        return this.#dimming * 100;
    }
}
let a = new DimmableBulb("hall light");
a.dimming = 50;
a.dimming = a.dimming / 2;
a.#dimming = 100;   // error

Listing 2-57.

Notice that the private field #dimming is initialized to 1.0 in its declaration in the class body. This is optional; it can instead be initialized in the constructor. Until it’s initialized, it has a value of undefined.

Notice also that the example in Listing 2-57 eliminates the constructor entirely. That’s possible here because #dimming is already initialized. Since DimmableBulb inherits from Bulb, when there’s no constructor on DimmableBulb the constructor of Bulb is automatically called when an instance is created. As you’d expect from C++, the code outside the class has no access to private fields; consequently, the final line of the example, which attempts to assign a value to #dimming, generates an error.

JavaScript doesn’t support the C++ friends or protected class features. The private properties of a class are only directly accessible to code inside the class body. Private fields are truly private, remaining invisible even to subclasses and superclasses.

Private Methods

Together with private fields, the JavaScript language standard is adding private methods—functions that can only be called from within a class’s implementation. For example, the DimmableBulb class in Listing 2-58 has a private #log method .
class DimmableBulb extends Bulb {
    #dimming = 1.0;
    set dimming(value) {
        if ((value < 0) || (value > 100))
            throw new RangeError("bad dimming value");
        this.#dimming = value / 100;
        this.#log(`set dimming ${this.#dimming}`);
    }
    get dimming() {
        this.#log("get dimming");
        return this.#dimming * 100;
    }
    #log(msg) {
        trace(msg);
    }
}
let a = new DimmableBulb("hall light");
a.#log("test");     // error

Listing 2-58.

Using Callback Functions in Classes

There are times when a class implementation passes a function to an API as a callback . A common example is when an API uses a timer to delay an action into the future. JavaScript web developers commonly use setTimeout for this purpose; in embedded JavaScript, the equivalent is Timer.set . The example in Listing 2-59 adds a method to the Bulb class to turn the light on or off after a specified time interval elapses.
class Bulb {
    ...
    setOnAfter(value, delayInMS) {
        let bulb = this;
        Timer.set(function() {
            if (value)
                bulb.turnOn();
            else
                bulb.turnOff();
        }, delayInMS);
    }
}

Listing 2-59.

The setOnAfter method calls Timer.set with two arguments: an anonymous function to execute after the timer expires and the time to wait in milliseconds. The callback function uses a closure to access bulb; this is necessary because the value of this in the callback is not the instance of bulb that setOnAfter was called with, but rather is the global object (that is, globalThis). This code works, but JavaScript has better tools to implement this functionality.

Like modern C++, modern JavaScript has lambda functions—commonly called arrow functions because of the => syntax used to declare them. Like closures, arrow functions are a little difficult to understand but easy to use. When an arrow function is called, its this value is the same as the this value of the function in which the arrow function is defined. This feature of arrow functions is referred to as lexical this because the value of this inside the arrow function is taken from the enclosing function.

Arrow functions are popular because they maintain the value of this and they’re more concise in source code. The examples in Listing 2-60 show the same functions using the function keyword and arrow function syntax.
function randomTo100() {
    return Math.random() * 100;
}
let randomTo100 = () => Math.random() * 100;
function cube(a) {
    return a * a * a;
}
let cube = a => a * a * a;
function add(a, b) {
    return a + b;
}
let add = (a, b) => a + b;
function upperFirst(str) {
    let first = str[0].toUpperCase();
    return first + str.slice(1);
}
let upperFirst = str => {
    let first = str[0].toUpperCase();
    return first + str.slice(1);
};

Listing 2-60.

All of the pairs of examples in Listing 2-60 are functionally equivalent, apart from the value of this in the functions; however, the examples don’t use this. The code in Listing 2-61 uses an arrow function to improve the implementation of setOnAfter (in Listing 2-59) by taking advantage of the lexical this to eliminate the bulb local variable. Using this approach, the code of the callback is able to use this in the same way that class methods can.
class Bulb {
    ...
    setOnAfter(value, delayInMS) {
        Timer.set(
            () => value ? this.turnOn() : this.turnOff(),
            delayInMS
        );
    }
}

Listing 2-61.

It’s important to be familiar with arrow functions because they’re very common in JavaScript. You’ll encounter them in some of the examples in this book. Keep in mind that arrow functions aren’t just an alternative way of writing the source code of functions; they also change the value of this inside the function.

Modules

Modules are the mechanism in JavaScript for packaging a library of code. There are some similarities between JavaScript modules and shared or dynamic libraries in C and C++: both specify exports to share a limited number of classes, functions, and values; and both can import classes, functions, and values from other libraries. Like dynamic libraries in C, JavaScript modules are loaded at runtime. There are also many differences, including that there’s no JavaScript equivalent to a statically linked C library.

Importing from Modules

To use the capabilities provided by a module, you must first import the corresponding classes, functions, or values. There are many different ways to import from a module, with a flexibility that gives you control over what you import and how you name those imports.

Examples in the preceding section used the Timer class without showing where it came from. The Timer class is contained in the timer module . To import from a module, you use the import statement .
import Timer from "timer";
Timer.set(() => trace("done"), 1000);

The import statement is special in JavaScript in that it’s executed before all other code. It’s customary to put the import statement at the top of the source code, like include statements in C, but even if they’re not first, they still execute first.

The preceding form of the import statement consists of two parts:
  • The name of the variable in which to store the import. Here it’s Timer, but you can use any name you like. The ability to select the name can help avoid name conflicts, especially when you’re working with many modules.

  • After the from keyword, the module specifier. Here it’s "timer".

A module specifier that, like "timer", is not a path is called a bare module specifier . For embedded JavaScript, these are more common; in fact, this book uses only bare module specifiers. One reason for this is that there’s often no file system in an embedded device to resolve the path. By contrast, JavaScript on the web currently only uses paths for module specifiers, so there you’ll see import statements with a from clause, like from "./modules/timer.js".

The form of the import statement illustrated previously imports the default export of the timer module. Every module has a default export. Some modules have additional exports; for example, the http module used in Chapter 3 exports both a Request class and a Server class. Listing 2-62 shows different ways to import these non-default exports from http.
import {Server} from "http";    // server only
new Server;
import {Request} from "http";   // client only
new Request;
import {Server, Request} from "http";
new Server;
new Request;

Listing 2-62.

You can use the as keyword to rename non-default exports that you import. Listing 2-63 renames Server to HTTPServer and Request to HTTPClient.
import {Server as HTTPServer, Request as HTTPClient} from "http";
new HTTPServer;
new HTTPClient;
new Request;    // fails, Request is undefined
new Server;     // fails, Server is undefined

Listing 2-63.

If you prefer for readability, you may use the same module specifier in multiple import statements:
import {Server as HTTPServer} from "http";
import {Request as HTTPClient} from "http";
You can also import all the exports from a module. When you do this, you assign the imports to an object. By avoiding name conflicts, this feature of JavaScript serves a similar purpose to namespaces in C++.
import * as HTTP from "http";
new HTTP.Server;
new HTTP.Request;
Once you import a class from a module, you can use it like a class declared in the same source file or a JavaScript built-in class. As you’ve seen, you can instantiate the class with the new operator. You can also create subclasses of an imported class:
import {Request} from "http";
class MyRequest extends Request {
    ...
}

Exporting from Modules

When you start writing your own classes, you’ll want to package them into your own modules; those modules need to export their classes so that they can be used by other code (and functions and values may likewise be exported). The following line uses the export statement to provide the Bulb class as the module’s default export:
export default Bulb;
You can optionally put the export statement before the declaration of a class, which here means you could combine export default with the definition of the Bulb class, as follows:
export default class Bulb {
    ...
}

This approach is valid but less common. Current JavaScript best practices recommend putting all your import statements together at the start of the source file and all export statements together at the end, making your code easier to read and maintain.

The following example shows two ways to provide non-default exports of Bulb and DimmableBulb:
export {Bulb};
export {DimmableBulb};
export {Bulb, DimmableBulb};
Like the import statement, an export statement can perform renaming using as. This is useful when you want to export a different name from that used in your implementation.
export {Bulb as BULB, DimmableBulb as DIMMABLEBULB};

The only way a module can access the contents of another module is through its exports. Classes, functions, and values that are not exported cannot be accessed directly; they’re equivalent to classes, functions, and values defined using the static keyword in C and C++, but with this important difference: by default in C and C++, everything is exported unless declared static, whereas in JavaScript nothing is exported except as indicated by an export statement. The JavaScript approach—a whitelist instead of a blacklist of exports as in C—helps with security and maintainability by avoiding unintended exports.

ECMAScript Modules vs. CommonJS Modules

The modules used in this book are part of the JavaScript language specification. They’re sometimes referred to as ECMAScript modules, or ESMs . Before modules were added to the official specification, a module system named CommonJS was used in some environments, particularly in Node.js. Because of that history, you may still see CommonJS documentation and modules. However, they don’t work in the hosts used in this book, and most environments (including Node.js) are migrating to standard JavaScript modules.

Globals

Like C and C++, JavaScript has global variables. You’ve used some of them already, such as Object, Array, and ArrayBuffer. These built-in classes are assigned to global variables that have the same name as the class. You access these globals simply by using their name. If that name isn’t in the current scope, the global variable is used. If no global variable is available with that name, an error is generated. This is similar to the link error generated when you access a nonexistent global in C.
function example() {
    let a = Date;       // OK, Date is built in
    let b = DateTime;   // error, DateTime is not defined
}
In C and C++, you create a global variable by declaring a variable at the top-level scope of a source code file. Unless it’s marked as static, the variable is visible to all code statically linked to that file. In JavaScript, you must be explicit about creating a global variable. To define a new global variable in JavaScript, you add it to an object named globalThis. The following line creates a global variable named AppName and sets its initial value:
globalThis.AppName = "light bulb";
Once the global variable is defined, you can access it either implicitly by stating only its name or explicitly by reading the property from globalThis:
AppName = "Light Bulb";
globalThis.AppName += " App";
If you want to know whether a particular global variable is already defined, use the in keyword introduced in the “Objects” section of this chapter.
if ("AppName" in globalThis)
    trace(`AppName is ${AppName} `);
else
    trace("AppName not available");
In the same way that you remove properties from an object with the delete operator, you can remove global variables:
delete globalThis.AppName;

Note that the original name of the globalThis object was global, which is easier to remember and type; it was changed for compatibility reasons. Some environments support global as an alias for globalThis.

When you work with modules, they can seem to have global variables. Consider the module example in Listing 2-64.
let counter = 0;
function count() {
    return ++counter;
}
export default count;

Listing 2-64.

The way the counter variable is declared at the top-level scope, it appears to be like a global variable declaration in C or C++, but it’s not. The counter variable is private to the module, since it isn’t explicitly exported. Such variables are local to the module. In C or C++, the equivalent result is achieved by preceding the variable declaration with static to limit its visibility to the current source code file.

Arrays

In C and C++, any pointer to a type (such as char *) or to a structure (such as struct DataRecord *) may access a single element (as in *ptr) or an array (as in ptr[0], ptr[1]). These uses of a pointer can lead to errors—for example, writing to an index beyond the end of the memory reserved for an array. To help avoid some of the dangers of working with arrays, C++ provides an std:array class template, which also provides iterators and other common helper functions. JavaScript’s built-in Array object is more like std:array in C++ in that it’s designed to be safe and it provides many helper functions to perform common operations.

Unlike C and C++, JavaScript doesn’t permanently set the number of elements when an array is instantiated; arrays may contain a variable number of elements. You can optionally indicate the number of elements when calling the constructor.
let a = new Array;      // empty array
let b = new Array(10);  // 10-element array

As you might have guessed, all array entries are initialized to undefined. Notice that when an array is created, there’s no indication of the type of data it will hold. That’s because each array element may contain any value; the values don’t need to be of the same type.

Array Shorthand

Because creating arrays is so common, JavaScript provides a shortcut:
let a = []; // empty array
Using this shortcut syntax, you can provide the initial values of the array:
let a = [0, 1, 2];
let b = [undefined, 1, "two", {three: 3}, [4]];

Accessing Elements of an Array

Accessing elements of an array uses the same syntax as in C and C++. Elements are numbered starting at 0.
let a = [0, 1, 2];
a[0] += 1;
trace(a[0]);
a[1] = a[2];
Reading the value of an array beyond the end of the array returns undefined. Writing a value beyond the end of an array creates the value, extending the length of the array.
let sparse = [0, 1, 2];
sparse[3] = 3;
sparse[1_000_000] = "big array";

You might expect that this assignment to the millionth element of the array will fail on a microcontroller with limited memory: the ESP8266 has only about 64 KB of RAM, so how can it hold an array with a million elements? Yet the assignment succeeds, and accessing sparse[1_000_000] returns "big array". How does that work?

An array in JavaScript may be sparse, meaning that not all elements must be present. Any elements that aren’t present have a value of undefined. In the case here of the array sparse, there are only five elements, which just happen to be at indices 0, 1, 2, 3, and 1,000,000.

Arrays have a length property, which indicates the number of elements in the array. The length is used, for example, to iterate over the elements in the array. For a sparse array, length is not the number of elements with an assigned value, but 1 more than the highest index that has an assigned value. In the case here of the array sparse, length is 1,000,001 even though there are only five elements with assigned values.

Setting the length property changes the array. Setting it to a smaller value truncates the array. The following truncates the preceding sparse array to four elements:
sparse.length = 4;  // [0, 1, 2, 3]

Setting an array’s length property to a larger value doesn’t change the array’s contents.

Iterating over Arrays

As shown in Listing 2-65, you can use the length property to iterate over the elements in an array using a for loop, as in C and C++.
let a = [0, 1, 2, 3, 4, 5];
let total = 0;
for (let i = 0; i < a.length; i++)
    total += a[i];

Listing 2-65.

Instead of using the C-style for loop, you can use the JavaScript for-of loop .
for (let value of a)
    total += value;
The for-of loop approach is more compact, eliminating the code to manage the value i and the lookup of the value in the array a[i]. Both the C-style for loop and the for-in loop iterate through all the values from index 0 to the length of the array, even for sparse arrays where there are unassigned values. Because the unassigned values have a value of undefined, total has a value of NaN at the end of the code in Listing 2-66.
let a = [0, 1, 2, 3, 4, 5];
a[1_000_000] = 6;
let total = 0;
for (let i in a)
    total += a[i];

Listing 2-66.

You can modify this code to ignore array elements with the value undefined, as follows:
for (let i in a)
    total += (undefined === a[i]) ? 0 : a[i];
An alternative solution is to iterate over the values in the array using a for-of loop that includes only array elements that have an assigned value, as shown in Listing 2-67; the value of total at the end of this code is 21 rather than NaN as in Listing 2-66.
let a = [0, 1, 2, 3, 4, 5];
a[1_000_000] = 6;
let total = 0;
for (let value of a)
    total += value;

Listing 2-67.

The Array object also has methods for iterating over an array in many different ways, each of which uses a callback function. The forEach method is similar to the for-in loop (see Listing 2-68). Like the for-of loop, this method skips array elements that don’t have a value assigned.
let a = [0, 1, 2, 3, 4, 5];
let total = 0;
a.forEach(function(value) {
    total += value;
});

Listing 2-68.

Using arrow functions reduces the iteration code to a single line:
let a = [0, 1, 2, 3, 4, 5];
let total = 0;
a.forEach(value => total += value);

As you might guess, not all of the many ways to iterate over an array in JavaScript are equally efficient. The forEach approach, for example, is the most compact code but requires a function call on every element, which can add overhead. For small arrays, use whichever approach is most convenient; for large arrays, it can be worth measuring the performance of different approaches to find the fastest one.

The map method is helpful when you need to perform an operation on each element of an array. It invokes a callback on each element and returns a new array containing the results. The following example creates an array containing the square of the values in the original array. The arrow function invoked for each element uses the exponentiation operator (**) to calculate the square.
let a = [-2, -1, 0, 1, 2];
let b = a.map(value => value ** 2); // [4, 1, 0, 1, 4]

Adding and Removing Elements of an Array

Because JavaScript arrays aren’t a fixed length, they have uses beyond a simple ordered list. The push and pop functions enable you to use an array as a stack (last in, first out), as shown in Listing 2-69.
let stack = [];
stack.push("a");
stack.push("b");
stack.push("c");
let c = stack.pop();    // "c"
stack.push("d");
let d = stack.pop();    // "d"
let b = stack.pop();    // "b"

Listing 2-69.

With the unshift and pop functions , you can use an array as a queue (first in, first out). The unshift function adds values to the start of an array; see Listing 2-70. (There’s also shift, which removes the first item from an array.)
let queue = [];
queue.unshift("first");
queue.unshift("second");
let a = queue.pop();    // "first"
queue.unshift("third");
let b = queue.pop();    // "second"

Listing 2-70.

Using unshift and pop to add and remove elements of a queue is useful but not entirely intuitive. These functions would be easier to use if they had names that make more sense for a queue; you can do this by creating a subclass of Array, as shown in Listing 2-71.
class Queue extends Array {
    add(element) {
        this.unshift(element);
    }
    remove(element) {
        return this.pop();
    }
}
let queue = new Queue;
queue.add("first");
queue.add("second");
let a = queue.remove(); // "first"
queue.add("third");
let b = queue.remove(); // "second"

Listing 2-71.

To extract part of an array into another array, use the slice function . As when you use slice to extract parts of strings, it takes two arguments: the starting and ending indices (where the ending index is the index before which to end extraction). If the ending index is omitted, the string’s length is used. The slice function never changes the content of the array it’s operating on.
let a = [0, 1, 2, 3, 4, 5];
let b = a.slice(0, 2);  // [0, 1]
let c = a.slice(2, 4);  // [2, 3]
To remove part of an array, use splice. The name splice is very similar to slice, and the two operate similarly: they take the same arguments, and both functions return an array containing the section of the array identified by the arguments. However, splice also deletes the elements from the original array.
let a = [0, 1, 2, 3, 4, 5];
let b = a.splice(0, 2); // [0, 1]
let c = a.splice(0, 2); // [2, 3]
// a = [4, 5] here

Searching Arrays

Searching for a particular value within an array is common, and there are several functions to help with that. As shown in Listing 2-72, you can use indexOf to search from the start of an array or lastIndexOf to search from the end. The first parameter is the value to search for; an optional second parameter indicates the index at which to begin searching in the array. If the value isn’t found, both functions return –1.
let a = [0, 1, 2, 3, 2, 1, 0];
let b = a.indexOf(1);           // 1
let c = a.lastIndexOf(1);       // 5
let d = a.indexOf(1, 3);        // 5
let e = a.lastIndexOf(1, 3);    // 1
let f = a.indexOf("one");       // –1

Listing 2-72.

The indexOf and lastIndexOf functions use the strict equality operator to test whether a match is found. If you want to apply a different test, use the findIndex function, which invokes a callback function to test for a match. The following example performs a case-insensitive match:
let a = ["Zero", "One", "Two"];
let search = "one";
let b = a.findIndex(value =>
                     value.toLowerCase() === search); // 1

Sorting Arrays

Sorting is another common operation on arrays. The sort function on arrays is similar to the qsort function in C and C++, though it may be implemented using a different sorting algorithm. Like qsort, JavaScript’s sort operates in place, so no new array is created. The built-in sort function’s default behavior is to compare the array values as strings.
let a = ["Zero", "One", "Two"];
a.sort();
// ["One", "Two", "Zero"]
To implement other behaviors, you provide a callback function to perform the comparison. The comparison is similar to that of the callback function in the C and C++ qsort function, receiving two values to compare and returning a negative number, 0, or a positive number depending on the result of the comparison. For example, the following code sorts an array of numbers:
let a = [0, 1, 2, 3, 2, 1, 0];
a.sort((x, y) => x - y);
// [0, 0, 1, 1, 2, 2, 3]
The example in Listing 2-73 uses a more complex comparison function to perform a case-insensitive sort of strings.
let a = ["Zero", "zero", "two", "Two"];
a.sort();
// ["Two", "Zero", "two", "zero"]
a.sort((x, y) => {
    x = x.toLowerCase();
    y = y.toLowerCase();
    if (x > y)
        return +1;
    if (x < y)
        return -1;
    return 0;
});
// ["Two", "two", "Zero", "zero"]

Listing 2-73.

Binary Data

JavaScript didn’t always support binary data, unlike C, which has supported memory buffers containing native integer types from the start. In C, one of the first things you learn is how to allocate memory with malloc and how to fill that memory with arrays and other data structures. The ability to operate on memory buffers directly is essential for many kinds of embedded development—for example, when working with binary messages in various network and hardware protocols. JavaScript supports the same kinds of operations as you’re accustomed to when coding in C and C++, though the way you perform those operations is quite different.

Another benefit to using binary data in JavaScript is that it can reduce your project’s memory use. One of the fundamental characteristics of JavaScript is that any value may hold any type, but this powerful feature comes with a cost: additional memory is required on each value to store the value’s type. While a boolean value in C is just one byte (or a bit, using bit fields), a boolean value in JavaScript can be much more—for example, 8 or 16 bytes. Using binary data in JavaScript, you can store a boolean value in a byte (or even a bit) with just a little work. If your project maintains a large amount of data in memory, consider using JavaScript’s binary data features, as the memory savings can be significant. Creating a 1,000-element array of JavaScript booleans using a standard Array object may require 16 KB of RAM, more than may be free on an ESP8266, but creating it using a Uint8Array object requires just 1 KB of RAM—exactly the same as in C.

ArrayBuffer

The JavaScript equivalent of calloc is the ArrayBuffer class. An ArrayBuffer is a block of memory of a fixed number of bytes. The memory is initially set to 0, to avoid any surprises with uninitialized memory.
let a = new ArrayBuffer(10);    // 10 bytes

If the buffer can’t be allocated because not enough free memory is available, the ArrayBuffer constructor throws an exception.

To retrieve the number of bytes in an ArrayBuffer , get the byteLength property. The number of bytes contained in an ArrayBuffer instance is fixed at the time it’s created. There’s no equivalent to realloc; you cannot set the byteLength property of an ArrayBuffer.
let a = new ArrayBuffer(16);
let b = a.byteLength;   // 16
a.byteLength = 20;      // exception thrown
As with an array, you use the slice method to extract a section of the buffer into a new ArrayBuffer instance:
let a = new ArrayBuffer(16);
let b = a.slice(0, 8);      // copy first half
let c = a.slice(8, 16);     // copy second half
let d = a.slice(0);         // clone entire buffer

You might expect to be able to access the content of an ArrayBuffer using array syntax (for example, a[0]), but this is not the case. An ArrayBuffer is only a buffer of bytes. Because there’s no type associated with the data, JavaScript doesn’t know how to interpret the bytes—for example, whether the byte values are signed or unsigned. To access the data in an ArrayBuffer, you wrap it in a view. The following sections introduce two kinds of view: typed array and data view.

Typed Arrays

JavaScript typed arrays are a collection of classes that let you work with arrays of integers and floating-point values stored in an ArrayBuffer. You don’t work with the TypedArray class directly but with its subclasses for specific types, such as Int8Array, Uint16Array, and Float32Array. Using a typed array is similar to creating a memory buffer in C with calloc and assigning the result to a pointer to an integer or floating-point type.

You can create a typed array that wraps an existing ArrayBuffer. The following example wraps an ArrayBuffer into a Uint8Array:
let a = new ArrayBuffer(16);
let b = new Uint8Array(a);
Now that you have a view on the buffer, you can access the content using array bracket syntax as you’d expect:
b[0] = 12;
b[1] += b[0];
Typed arrays, such as the Uint8Array in the earlier example, have a byteLength property, as does ArrayBuffer, but they also have a length property indicating the number of elements in the array. When the elements are bytes, these two values are equal, but for larger types, they differ (see Listing 2-74).
let a = new ArrayBuffer(24);
let b = new Uint8Array(a);
let c = new Uint16Array(a);
let d = new Uint32Array(a);
let e = b.length;   // 24
let f = c.length;   // 12
let g = d.length;   // 6

Listing 2-74.

Here a single ArrayBuffer is wrapped by several views; this is allowed. In C, it’s called “aliasing,” and it’s dangerous because it interferes with certain compiler optimizations. In JavaScript, it’s safe, though you should use it with care to avoid unexpected surprises when reading and writing to overlapping views.

You can create a typed array view that references a subset of a buffer, by including an offset in bytes to the start of the view and the number of elements in the view. This is like assigning an integer pointer a value in the middle of a memory buffer. In JavaScript, however, there’s no unpredictable result when you read past the end of the buffer; that always returns undefined (see Listing 2-75).
let a = new ArrayBuffer(18)
let b = new Int16Array(a);
b[0] = 0;
b[1] = 1;
b[2] = 2;
b[3] = 3;
let c = new Int16Array(a, 6, 1);
        // c begins 6 bytes into a and has one element
let d = c[0];   // 3
let e = c[1];   // undefined (read past end of view)

Listing 2-75.

The Int16Array view created in Listing 2-75 for variable c begins at offset 6, but it could begin at any offset, including an odd-numbered one. Accessing the 16-bit values in that array requires misaligned reads. Not all microcontrollers support misaligned reads and writes; the ESP8266 is one microcontroller that doesn’t support misaligned memory access. When C code performs a misaligned read or write, a hardware exception is generated, causing the microcontroller to reset. JavaScript code doesn’t have this problem because the language guarantees that misaligned operations give the same result as aligned operations—another way JavaScript makes coding on embedded products a little easier.

Typed Array Shorthand

It’s common to create small integer arrays. In C and C++, you can easily declare static arrays on the stack.
static uint16_t values[] = {0, 1, 2, 3};
In JavaScript, you can achieve the same result using the static of method on typed arrays:
let a = Uint16Array.of(0, 1, 2, 3);
let b = a.byteLength;   // 8
let c = a.length;       // 4
The of function automatically creates an ArrayBuffer of the size needed to store the values. You can access the ArrayBuffer created by of by getting the buffer property of the typed array. This buffer may be used with other views, such as data views.
let a = Uint16Array.of(0, 1, 2, 3);
let b = a.buffer;
let c = b.byteLength;   // 8

Copying Typed Arrays

In C and C++, you use memcpy and memmove to copy data values within a single buffer or between two buffers. You’ve already seen how to use slice on an ArrayBuffer in JavaScript to copy part or all of the buffer to a new buffer; you can use copyWithin to copy values within a single buffer and set to copy values from one buffer to another. In C, you need to take special care when copying within a single buffer when the source and destination overlap, whereas JavaScript’s copyWithin method guarantees the results are predictable and correct. The first argument to copyWithin is the destination index, and the second and third arguments are the starting and ending source indices to copy (where the ending index is the index before which to end).
let a = Uint16Array.of(0, 1, 2, 3, 4, 5, 6);
a.copyWithin(4, 1, 3);
// [0, 1, 2, 3, 1, 2, 6]
The set method writes one typed array into another. The first argument is the source data to write, and the second argument is the index at which to begin writing the data.
let a = Int16Array.of(0, 1, 2, 3, 4, 5, 6);
let b = Int16Array.of(-2, -3);
a.set(b, 2);
// [0, 1, -2, -3, 4, 5, 6]
To write only a subset of the source data, you need to create another view. The subarray method is convenient for that, as shown in Listing 2-76. Given the starting and ending indices of a typed array, subarray returns a new typed array that references only those indices. Note that subarray doesn’t allocate a new ArrayBuffer; it just references the same ArrayBuffer.
let a = Int16Array.of(0, 1, 2, 3, 4, 5, 6);
let b = Int16Array.of(0, -1, -2, -3, -4, -5, -6);
let c = b.subarray(2, 4);
a.set(c, 2);
// [0, 1, -2, -3, 4, 5, 6]

Listing 2-76.

You could use slice in place of subarray to copy the subset in a new Int16Array, but that temporarily uses additional memory, so subarray is preferred in this case.

The TypedArray classes are not subclasses of Array; they’re entirely independent classes, but they’re designed to share common APIs. For example, the copyWithin method you learned about for typed arrays is available with Array. Similarly, many of the Array methods, including map, forEach, indexOf, lastIndexOf, findIndex, and sort, are also available for typed arrays.

Filling Typed Arrays

Another useful method available for both Array and TypedArray is fill, which is similar to memset in C and C++. But while memset operates only on byte values, fill operates on values of the type of the typed array. As shown in Listing 2-77, the first argument to fill is the value to assign, and the optional second and third arguments are the beginning and ending indices to fill (where the ending index is the index before which to end the fill). If the optional arguments are not provided, the entire array is filled.
let a = new Uint16Array(4);
a.fill(0x1234);
// [0x1234, 0x1234, 0x1234, 0x1234]
a.fill(0, 1, 3);
// [0x1234, 0, 0, 0x1234]
let b = new Uint32Array(2);
b.fill(0x12345678);
// [0x12345678, 0x12345678]

Listing 2-77.

Writing Typed Array Values

Writing values into a typed array usually behaves as in C. For example, if you write a 16-bit value into an 8-bit typed array, the least significant 8 bits are used (see Listing 2-78).
let a = new Uint32Array(1);
a[0] = 0x12345678;  // 0x12345678
let b = new Uint16Array(1);
b[0] = 0x12345678;  // 0x5678
let c = new Uint8Array(1);
c[0] = 0x12345678;  // 0x78

Listing 2-78.

JavaScript also has a Uint8ClampedArray, which implements a different behavior: rather than taking the least significant bits, it pins the input value to a value between 0 and the maximum value that the typed array instance can store.
let a = new Uint8ClampedArray(1);
a[0] = 5;   // 5
a[0] = 256; // 255
a[0] = -1;  // 0

Floating-Point Typed Arrays

There are two floating-point typed arrays: Float32Array and Float64Array. Since number values in JavaScript are 64-bit IEEE 754 floating-point, Float64Array is able to store these values without any loss of precision. Float32Array reduces the precision and range of the values that may be stored but is sufficient for some situations.

Note

The typed array classes make no guarantee about the order of bytes when storing values (that is, whether big-endian or little-endian). The JavaScript engine implementation is free to store values in any way it chooses, as long as the accuracy of the value is preserved. It usually stores them in the same order as the host microcontroller, for maximum efficiency. To control the byte order of values, use a data view (discussed next).

Data Views

The DataView class provides another kind of view onto an ArrayBuffer. Unlike typed arrays, in which all the values are of the same type, data views are used to read and write different-sized integers and floating-point values into a buffer. You can use DataView to access binary data that corresponds to a C or C++ struct containing values of different types.

You instantiate a data view by passing the DataView constructor an ArrayBuffer for the view to wrap, just as you can pass an ArrayBuffer to a typed array constructor:
let a = new ArrayBuffer(16);
let b = new DataView(a);
Also as with typed arrays, you can pass an offset and size to the DataView constructor to restrict the view to a subset of the total buffer. This capability is useful for accessing data structures embedded in a larger memory buffer.
let a = new ArrayBuffer(16);
let b = new DataView(a, 4, 12);
// b may only access bytes 4 through 12 of a

Accessing Values of a Data View

A DataView instance is able to get and set all the same types as a typed array, as shown in Listing 2-79. The getter and setter methods all have the offset into the view as their first argument. The second argument of the setter methods specifies the value to set.
let a = new DataView(new ArrayBuffer(8));
a.setUint8(0, 0);
a.setUint8(1, 1);
a.setUint16(2, 0x1234);
a.setUint32(4, 0x01020304);

Listing 2-79.

Because the DataView methods write multi-byte values in big-endian byte order by default, the buffer a contains the following hexadecimal bytes after the example in Listing 2-79 executes:
00 01 12 34 01 02 03 04
You read the values back using the corresponding getter methods. The following example assumes the DataView instance a shown previously:
let b = a.getUint8(0);      // 0
let c = a.getUint8(1);      // 1
let d = a.getUint16(2);     // 0x1234
let e = a.getUint32(4);     // 0x01020304
The DataView methods have an optional final parameter to control the byte order. If the parameter is omitted or false, the byte order is big-endian; if true, it’s little-endian (see Listing 2-80).
let a = new DataView(new ArrayBuffer(8));
a.setUint8(0, 0);
a.setUint8(1, 1);
a.setUint16(2, 0x1234, true);
a.setUint32(4, 0x01020304, true);

Listing 2-80.

Because setUint8 writes a single-byte value, there’s no byte order, so the third parameter is unnecessary. The calls to setUint16 and setUint32 in Listing 2-80 set the byte order parameter to true, so the output is little-endian.
00 01 34 12 04 03 02 01
To read values stored in little-endian order, pass true as the final parameter to the getter methods:
let b = a.getUint16(2, true);   // 0x1234 (little-endian get)
let c = a.getUint16(2);         // 0x3412 (big-endian get)

The DataView class includes getter and setter methods that correspond to all the types available in TypedArray: Int8, Int16, Int32, Uint8, Uint16, Uint32, Float32, and Float64.

The DataView class is a very flexible way to manipulate binary data structures, but the code isn’t particularly readable. Instead of writing a.value to access a field as you would in C, you have to write something like a.getUint16(6, true). One way to improve the readability and reduce the possibility of errors is to create a subclass of DataView for the data structure. Imagine that you have the C data structure shown in Listing 2-81 for a network packet header that you want to use from JavaScript. For simplicity, assume there’s no padding between the fields.
typedef struct Header {
    uint8_t     kind;
    uint8_t     priority;
    uint16_t    sequenceNumber;
    uint32_t    value;
}

Listing 2-81.

The JavaScript Header class in Listing 2-82 subclasses DataView to implement easy access to the C Header structure. Because network packets typically use big-endian byte ordering, the multi-byte values are written in big-endian order.
class Header extends DataView {
    constructor(buffer = new ArrayBuffer(8)) {
        super(buffer);
    }
    get kind() {return this.getUint8(0);}
    set kind(value) {this.setUint8(0, value);}
    get priority() {return this.getUint8(1);}
    set priority(value) {this.setUint8(1, value);}
    get sequenceNumber() {return this.getUint16(2);}
    set sequenceNumber(value) {this.setUint16(2, value);}
    get value() {return this.getUint32(4);}
    set value(value) {this.setUint32(4, value);}
}

Listing 2-82.

Because the class uses getters and setters, the resulting code for users of the class is similar to C. The example in Listing 2-83 uses the Header class to read values from a packet received in the variable p.
let a = new Header(p);
let b = a.kind;
let c = a.priority;
let d = a.sequenceNumber;
let e = a.value;

Listing 2-83.

Listing 2-84 creates a new packet, initializes the values, and calls a send function to transmit the ArrayBuffer a.buffer used by the Header instance for storage.
let a = new Header;
a.kind = 1;
a.priority = 2;
a.sequenceNumber = 3;
a.value = 4;
send(a.buffer);

Listing 2-84.

As you can see, defining a class representing the binary data structure makes the code that works with that data structure much clearer. Working with binary data is one area where C has an advantage in the compactness of the code; still, it’s possible to achieve the same result with readable code in JavaScript. JavaScript has benefits here, too: Consider that code that reads from data received over the network is often fragile. In this example, if the packet received has only four bytes instead of the needed eight bytes, the read of the value field has an undefined result, which could leak private data or even cause a crash. If that situation occurs in JavaScript, the attempt to read value using getUint32 fails with an exception because the read is out of range.

Memory Management

Memory management is one place where JavaScript differs significantly from C and C++. In C and C++, you explicitly allocate memory with malloc, calloc, and realloc and deallocate it with free. These memory allocation and deallocation functions are not in the language itself but in the standard library. In C++, you also allocate memory when you instantiate a class using new, and you deallocate that memory when you use delete to invoke the class’s destructor.

JavaScript builds memory management into the language. When you create an object, string, ArrayBuffer, or any other built-in object that requires memory, that memory is transparently allocated by the JavaScript engine. As you’d expect, the language also deallocates the memory; however, rather than requiring your code to make a call like free or use the C++ delete operator, JavaScript automatically frees the memory when it determines it’s safe to do so. This approach to memory management is implemented using a garbage collector. At certain points in time, the JavaScript engine runs the garbage collector, which scans all memory allocated by the engine, identifies any allocations that are no longer referenced, and deallocates any unreferenced memory blocks.

Consider this code:
let a = "this is a test";
a = {};
a = new ArrayBuffer(16);
This example does the following:
  1. 1.

    The first line allocates a string and assigns it to a. Because the string is referenced by a, it cannot be garbage-collected.

     
  2. 2.

    The second line assigns an empty object to a, deleting the reference to the string. Since no other variable or properties refer to the string, it’s eligible to be garbage collected.

     
  3. 3.

    After the ArrayBuffer assignment on the third line, the empty object becomes eligible for garbage collection.

     

The JavaScript language doesn’t define when the garbage collector runs. The garbage collector in the XS engine used in the Moddable SDK runs whenever it’s out of memory; that may be never, once an hour, or many times a second, depending on the code running.

The garbage collector works well for managing memory. It reduces the amount of code you need to write, because both allocations and deallocations happen automatically. It eliminates the bug of forgetting to deallocate memory, which causes memory leaks; this is a major concern in embedded systems, many of which must run for months or years at a time, because a small memory leak that occurs periodically eventually leads to a system failure. The garbage collector also eliminates the bug of reading memory that has been deallocated, since memory isn’t deallocated if code is still able to reference.

For all its benefits, the garbage collector is not a general-purpose solution for resource management. Consider Listing 2-85, which opens a file twice, first in write mode and then in read-only mode.
let f = new File("/foo.txt", 1);    // 1 for write
f.write("this is a test");
f = undefined;
...
let g = new File("/foo.txt");       // read-only

Listing 2-85.

At the time undefined is assigned to f in this example, the instance of the File class corresponding to the file opened for write access is eligible for garbage collection. In most file systems, when a file is opened for write access the access is exclusive, meaning the file cannot be opened a second time. Because the garbage collector may run at any time, the call to open the file in read-only mode may or may not succeed, depending on whether the write-access file object has been collected yet. For this reason, objects used to represent non-memory resources, such as an open file, usually provide a way to explicitly release the resource. In the Moddable SDK, the close method is used to release the resources, similar to using the delete operator in C++.
let f = new File("/foo.txt", 1);    // 1 for write
f.write("this is a test");
f.close();

The call to close closes the file immediately. Any further attempt to write to the instance in f will fail. The file may now be opened again, in read or write mode.

The Date Class

The C standard library provides the gettimeofday and localtime functions to determine the current date, time, time zone, and daylight saving time offset. The strftime function in the same library converts the date and time to text format using format strings. JavaScript provides equivalent functionality in the built-in Date class.

The following code creates an instance of the Date class. The instance contains a time value, which is initialized to the current time when the Date constructor is called with no arguments.
let now = new Date;
trace(now.toString());
// Tue Sep 24 2019 11:18:26 GMT-0700 (PDT)
The Date constructor accepts arguments to initialize the value to something other than the current time. You can initialize it from a string, though this is not recommended because of the ease of making mistakes in the string format.
let d = new Date("Tue Sep 24 2019 11:18:26 GMT-0700 (PDT)");
Instead, you can pass the components of the time (hours, minutes, year, and so on) as arguments to the constructor:
let d = new Date(2019, 8, 24);          
        // September 24 2019 midnight
let e = new Date(2019, 8, 24, 11, 18, 26);  
        // September 24 2019 11:18:26
Note here that the value for the month of September is 8, rather than 9 as you might expect. That’s because month numbers in the JavaScript Date API start from 0 instead of 1; this was decided early in the development of JavaScript to match the Java language’s java.util.Date object. Also note that the time specified in the second declaration is local time, not UTC (Coordinated Universal Time). To specify UTC time, use the Date.UTC function together with the Date constructor.
let d = new Date(Date.UTC(2019, 8, 24));    
        // September 24 2019 midnight UTC
A Date instance stores a time value in milliseconds and always in UTC time. To retrieve that value, call the getTime method.
let now = new Date;
let utcTimeInMS = now.getTime();
If your code needs to retrieve the time frequently, the preceding example is inefficient, as it creates a new instance of Date every time the current time is needed. For such situations, the static method now returns the current UTC time in milliseconds as a number.
let utcTimeInMS = Date.now();
The Date class provides access to all the parts that make up a date and time (see Listing 2-86).
let now = new Date;
let ms = now.getMilliseconds();     // 0 to 999
let seconds = now.getSeconds();     // 0 to 59
let minutes = now.getMinutes();     // 0 to 59
let hours = now.getHours();     // 0 to 23
let day = now.getDay();         // 0 (Sunday) to 6 (Saturday)
let date = now.getDate();       // 1 to 31
let month = now.getMonth();     // 0 (January) to 11 (December)
let year = now.getFullYear();

Listing 2-86.

The values returned in Listing 2-86 are local time, with the time zone and daylight saving time offsets applied. Versions of the same functions for the UTC values are also available; they begin with getUTC, as in getUTCMilliseconds, getUTCSeconds, and so on.

There are also setter methods corresponding to all the getter methods. Listing 2-87 creates a date object and modifies it to be midnight of the following New Year’s Day.
let d = new Date;
d.setMilliseconds(0);
d.setSeconds(0);
d.setMinutes(0);
d.setHours(0);
d.setDate(1);
d.setMonth(0);
d.setFullYear(d.getFullYear() + 1);

Listing 2-87.

The setHours and setFullYear methods support additional parameters, enabling the example in Listing 2-87 to be written more compactly:
let d = new Date;
d.setHours(0, 0, 0, 0);
d.setFullYear(d.getFullYear() + 1, 0, 1);
To retrieve the current time zone offset from UTC time, call the getTimezoneOffset method . The value returned is in minutes and has the current daylight saving time offset applied.
let timeZoneOffset = d.getTimezoneOffset();
// timeZoneOffset = 420 (offset in minutes from UTC)
As shown earlier in this section, the toString method of the Date object provides a string representing the local time with the time zone and daylight saving time offsets applied. For some situations—for example, networking—it’s helpful to have a text representation of the string in UTC time. Use the toUTCString method to create a string representing the UTC time.
let d = new Date;
trace(d.toUTCString());
// "Tue, 24 Sep 2019 18:18:26 GMT"
Another time and date format used by many standards is ISO 8601. The toISOString method provides an ISO 8601–compatible version of the date as a string.
let d = new Date;
trace(d.toISOString());
// "2019-09-24T18:18:26.000Z"

While toUTCString and toISOString are convenient, you can use your knowledge of JavaScript dates and strings to generate strings in any format your project needs.

Event-Driven Programming

Embedded programs, particularly those running on less powerful devices, are often organized around a single loop that executes continuously. Listing 2-88 shows a trivial example.
while (true) {
    if (readButton())
        lightOn();
    else
        lightOff();
}

Listing 2-88.

This style of programming works for very simple embedded devices. However, it doesn’t work well for larger systems with many different inputs and outputs; for such systems, event-driven programming is preferred. Event-driven programs wait for events to occur, such as a button press. When the event occurs, a callback is invoked to respond to it. JavaScript is designed for use with event-driven programs because that’s how web browsers work.

Listing 2-89 is an event-driven version of the infinite loop in the preceding example. Here, the onRead callback is invoked when the button changes so that the code doesn’t need to continuously poll the button state.
let button = new Button;
button.onRead = function(value) {
    if (value)
        lightOn();
    else
        lightOff();
}

Listing 2-89.

As a rule, callbacks that deliver events are invoked only when the microcontroller is idle. When JavaScript code is executing, callbacks are deferred until the code completes. In Listing 2-88, since the loop is infinite, no callbacks can be invoked. Therefore, it’s generally impossible to use a single loop as the basis for your JavaScript application; you must adopt the event-driven programming style.

If you haven’t done much event-driven programming before, don’t worry. The examples in this book are all written to show you how to use embedded JavaScript APIs in an event-driven programming style. With a little practice, it should become second nature.

Conclusion

With this introduction to JavaScript under your belt, you’re ready to move on in this book. The remaining chapters are about how to use JavaScript on embedded systems to create IoT products using features provided by the Moddable SDK.

The JavaScript language specification is huge—over 750 pages. This book can’t possibly explain every feature and nuance of the language, but many excellent resources are available to help you learn more. Mozilla’s MDN Web Docs (developer.mozilla.org) is the de facto reference for the JavaScript language. It’s up to date with the latest standard, provides plenty of examples, and is extremely detailed. It’s a great resource for embedded developers because many of the examples it presents can be understood even if you’re not a web developer.

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

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