Ref: https://javascript.info

JavaScript Fundamentals

Quotes

In JavaScript, there are 3 types of quotes.

  1. Double quotes: “Hello”.
  2. Single quotes: ‘Hello’.
  3. Backticks: `Hello`.

Double and single quotes are “simple” quotes. There’s practically no difference between them in JavaScript.

Backticks are “extended functionality” quotes. They allow us to embed variables and expressions into a string by wrapping them in ${…}, they allow a string to span multiple lines, for example:

    let name = "John";
    
    // embed a variable
    alert( `Hello, ${name}!` ); // Hello, John!
    
    // embed an expression
    alert( `the result is ${1 + 2}` ); // the result is 3
    
    let guestList = `Guests:
     * John
     * Pete
     * Mary
    `;
    
    alert(guestList); // a list of guests, multiple lines

Basic types

There are 8 basic data types in JavaScript.

  • number for numbers of any kind: integer or floating-point, integers are limited by ±(2^253-1).
  • bigint is for integer numbers of arbitrary length.
  • string for strings. A string may have zero or more characters, there’s no separate single-character type.
  • boolean for true/false.
  • null for unknown values – a standalone type that has a single value null.
  • undefined for unassigned values – a standalone type that has a single value undefined.
  • object for more complex data structures.
  • symbol for unique identifiers.

The typeof operator allows us to see which type is stored in a variable.

Two forms: typeof x or typeof(x). Returns a string with the name of the type, like “string”. For null returns “object” – this is an error in the language, it’s not actually an object.

Exponentiation **

The exponentiation operator a ** b multiplies a by itself b times.

For instance:

    alert( 2 ** 2 ); // 4  (2 multiplied by itself 2 times)
    alert( 2 ** 3 ); // 8  (2 * 2 * 2, 3 times)
    alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 times)
    alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root)
    alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)

Labels for break/continue

A label is an identifier with a colon before a loop:

    labelName: for (...) {
      ...
    }

The break <labelName> statement in the loop below breaks out to the label:

    outer: for (let i = 0; i < 3; i++) {
    
      for (let j = 0; j < 3; j++) {
    
        let input = prompt(`Value at coords (${i},${j})`, '');
    
        // if an empty string or canceled, then break out of both loops
        if (!input) break outer; // (*)
    
        // do something with the value...
      }
    }
    alert('Done!');

In the code above, break outer looks upwards for the label named outer and breaks out of that loop.

So the control goes straight from (*) to alert(‘Done!').

Callback functions

The idea is that we pass a function and expect it to be “called back” later if necessary. In our case, showOk becomes the callback for “yes” answer, and showCancel for “no” answer.

    function ask(question, yes, no) {
      if (confirm(question)) yes()
      else no();
    }
    
    function showOk() {
      alert( "You agreed." );
    }
    
    function showCancel() {
      alert( "You canceled the execution." );
    }
    
    // usage: functions showOk, showCancel are passed as arguments to ask
    ask("Do you agree?", showOk, showCancel);

Function Expression vs Function Declaration

Let’s formulate the key differences between Function Declarations and Expressions.

First, the syntax: how to differentiate between them in the code.

  • Function Declaration: a function, declared as a separate statement, in the main code flow.

      // Function Declaration
      function sum(a, b) {
        return a + b;
      }
    
  • Function Expression: a function, created inside an expression or inside another syntax construct. Here, the function is created at the right side of the “assignment expression” =:

      // Function Expression
      let sum = function(a, b) {
        return a + b;
      };
    

The more subtle difference is when a function is created by the JavaScript engine.

A Function Expression is created when the execution reaches it and is usable only from that moment.

Once the execution flow passes to the right side of the assignment let sum = function… – here we go, the function is created and can be used (assigned, called, etc. ) from now on.

Function Declarations are different.

A Function Declaration can be called earlier than it is defined.

For example, a global Function Declaration is visible in the whole script, no matter where it is. That’s due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an “initialization stage”. And after all Function Declarations are processed, the code is executed. So it has access to these functions.

For example, this works:

    sayHi("John"); // Hello, John
    
    function sayHi(name) {
      alert( `Hello, ${name}` );
    }

this doesn’t work:

    sayHi("John"); // error!
    
    let sayHi = function(name) {  // (*) no magic any more
      alert( `Hello, ${name}` );
    };

In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable.

But if a Function Declaration does not suit us for some reason, or we need a conditional declaration (we’ve just seen an example), then Function Expression should be used.

Arrow functions, the basics

It’s called “arrow functions”, because it looks like this:

    let func = (arg1, arg2, ...argN) => expression

If we have only one argument, then parentheses around parameters can be omitted, making that even shorter.

For example:

    let double = n => n * 2;
    // roughly the same as: let double = function(n) { return n * 2 }
    
    alert( double(3) ); // 6

If there are no arguments, parentheses will be empty (but they should be present):

    let sayHi = () => alert("Hello!");
    
    sayHi();

Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal return within them.

    let sum = (a, b) => {  // the curly brace opens a multiline function
      let result = a + b;
      return result; // if we use curly braces, then we need an explicit "return"
    };
    
    alert( sum(1, 2) ); // 3

Objects: the basics

Literals and properties

An empty object (“empty cabinet”) can be created using one of two syntaxes:

    let user = new Object(); // "object constructor" syntax
    let user = {};  // "object literal" syntax

usually, the figure brackets {…} are used. That declaration is called an object literal. We can immediately put some properties into {…} as “key: value” pairs:

    let user = {     // an object
      name: "John",  // by key "name" store value "John"
      age: 30        // by key "age" store value 30
    };

To remove a property, we can use delete operator:

    delete user.age;

Computed properties

We can use square brackets in an object literal, when creating an object. That’s called computed properties.

For instance:

    let fruit = prompt("Which fruit to buy?", "apple");
    
    let bag = {
      [fruit]: 5, // the name of the property is taken from the variable fruit
    };
    
    alert( bag.apple ); // 5 if fruit="apple"

The meaning of a computed property is simple: [fruit] means that the property name should be taken from fruit.

So, if a visitor enters "apple", bag will become {apple: 5}.

Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write. So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.

Cloning and merging, Object.assign

The syntax is:

    Object.assign(dest, [src1, src2, src3...])
  • The first argument dest is a target object.
  • Further arguments src1, ..., srcN (can be as many as needed) are source objects.
  • It copies the properties of all source objects src1, ..., srcN into the target dest. In other words, properties of all arguments starting from the second are copied into the first object.
  • The call returns dest.

For instance, we can use it to merge several objects into one:

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);

// now user = { name: "John", canView: true, canEdit: true }

Garbage Collection

Ref:https://javascript.info/garbage-collection#reachability

The main things to know:

  • Garbage collection is performed automatically. We cannot force or prevent it.
  • Objects are retained in memory while they are reachable.
  • Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.

Constructor, operator “new”

The regular {...} syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.

That can be done using constructor functions and the “new” operator.

Constructor functions technically are regular functions. There are two conventions though:

  1. They are named with capital letter first.

  2. They should be executed only with “new” operator.

     function User(name) {
       this.name = name;
       this.isAdmin = false;
     }
        
     let user = new User("Jack");
        
     alert(user.name); // Jack
     alert(user.isAdmin); // false
    

When a function is executed with new, it does the following steps:

  1. A new empty object is created and assigned to this.
  2. The function body executes. Usually it modifies this, adds new properties to it.
  3. The value of this is returned.

In other words, new User(...) does something like:

    function User(name) {
      // this = {};  (implicitly)
    
      // add properties to this
      this.name = name;
      this.isAdmin = false;
    
      // return this;  (implicitly)
    }

Optional chaining

The optional chaining ?. stops the evaluation and returns undefined if the part before ?. is undefined or null.

let user = {}; // user has no address

alert( user?.address?.street ); // undefined (no error)

The ?. syntax has three forms:

obj?.prop – returns obj.prop if obj exists, otherwise undefined.

obj?.[prop] – returns obj[prop] if obj exists, otherwise undefined.

obj?.method() – calls obj.method() if obj exists, otherwise returns undefined.

Symbol types

Ref: https://javascript.info/symbol

Symbol is a primitive type for unique identifiers.

Symbols are created with Symbol() call with an optional description (name).

Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: Symbol.for(key) returns (creates if needed) a global symbol with key as the name. Multiple calls of Symbol.for with the same key return exactly the same symbol.

Symbols have two main use cases:

“Hidden” object properties. If we want to add a property into an object that “belongs” to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in for..in, so it won’t be accidentally processed together with other properties. Also it won’t be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.

So we can “covertly” hide something into objects that we need, but others should not see, using symbolic properties.

There are many system symbols used by JavaScript which are accessible as Symbol.*. We can use them to alter some built-in behaviors. For instance, later in the tutorial we’ll use Symbol.iterator for iterables, Symbol.toPrimitive to setup object-to-primitive conversion and so on.

Data Types

Methods of primitives

JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we’ll see how it works because, of course, primitives are not objects (and here we will make it even clearer).

  1. Primitives are still primitive. A single value, as desired.
  2. The language allows access to methods and properties of strings, numbers, booleans and symbols.
  3. In order for that to work, a special “object wrapper” that provides the extra functionality is created, and then is destroyed.

Here’s what actually happens in str.toUpperCase():

let str = "Hello";

alert( str.toUpperCase() ); // HELLO
  1. The string str is a primitive. So in the moment of accessing its property, a special object is created that knows the value of the string, and has useful methods, like toUpperCase().
  2. That method runs and returns a new string (shown by alert).
  3. The special object is destroyed, leaving the primitive str alone.

So primitives can provide methods, but they still remain lightweight.

The JavaScript engine highly optimizes this process. It may even skip the creation of the extra object at all. But it must still adhere to the specification and behave as if it creates one.

Numbers

To write numbers with many zeroes:

Append “e” with the zeroes count to the number. Like: 123e6 is the same as 123 with 6 zeroes 123000000. A negative number after “e” causes the number to be divided by 1 with given zeroes. E.g. 123e-6 means 0.000123 (123 millionths).

For different numeral systems:

Can write numbers directly in hex (0x), octal (0o) and binary (0b) systems. parseInt(str, base) parses the string str into an integer in numeral system with given base, 2 ≤ base ≤ 36. num.toString(base) converts a number to a string in the numeral system with the given base.

Rounding:

Math.floor Rounds down: 3.1 becomes 3, and -1.1 becomes -2.

Math.ceil Rounds up: 3.1 becomes 4, and -1.1 becomes -1.

Math.round Rounds to the nearest integer: 3.1 becomes 3, 3.6 becomes 4 and -1.1 becomes -1.

Math.trunc (not supported by Internet Explorer) Removes anything after the decimal point without rounding: 3.1 becomes 3, -1.1 becomes -1.

For converting values like 12pt and 100px to a number:

Use parseInt/parseFloat for the “soft” conversion, which reads a number from a string and then returns the value they could read before the error.

isFinite(value) converts its argument to a number and returns true if it’s a regular number, not NaN/Infinity/-Infinity:

alert( isFinite("15") ); // true
alert( isFinite("str") ); // false, because a special value: NaN
alert( isFinite(Infinity) ); // false, because a special value: Infinity

Avoid equality checks when working with decimal fractions.

Strings

  • There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions ${…}.
  • Strings in JavaScript are encoded using UTF-16.
  • We can use special characters like \n and insert letters by their unicode using \u….
  • To get a character, use: [].
  • To get a substring, use: slice or substring.
  • To lowercase/uppercase a string, use: toLowerCase/toUpperCase.
  • To look for a substring, use: indexOf, or includes/startsWith/endsWith for simple checks.
  • To compare strings according to the language, use: localeCompare, otherwise they are compared by character codes.

There are several other helpful methods in strings:

  • str.trim() – removes (“trims”) spaces from the beginning and end of the string.
  • str.repeat(n) – repeats the string n times.

Arrays

  • If we shorten length manually, the array is truncated.

We can use an array as a deque with the following operations:

  • push(…items) adds items to the end.
  • pop() removes the element from the end and returns it.
  • shift() removes the element from the beginning and returns it.
  • unshift(…items) adds items to the beginning.
  • splice(pos, deleteCount, …items) – at index pos delete deleteCount elements and insert items.
  • slice(start, end) – creates a new array, copies elements from index start till end (not inclusive) into it.
  • concat(…items) – returns a new array: copies all members of the current one and adds items to it. If any of items is an array, then its elements are taken.

To search among elements:

  • indexOf/lastIndexOf(item, pos) – look for item starting from position pos, return the index or -1 if not found.
  • includes(value) – returns true if the array has value, otherwise false.
  • find/filter(func) – filter elements through the function, return first/all values that make it return true.
  • findIndex is like find, but returns the index instead of a value.

To iterate over elements:

  • forEach(func) – calls func for every element, does not return anything.

To transform the array:

  • map(func) – creates a new array from results of calling func for every element.
  • sort(func) – sorts the array in-place, then returns it.
  • reverse() – reverses the array in-place, then returns it.
  • split/join – convert a string to array and back.
  • reduce(func, initial) – calculate a single value over the array by calling func for each element and passing an intermediate result between the calls.

Additionally:

  • Array.isArray(arr) checks arr for being an array.

Iterable

Ref: https://javascript.info/iterable

Objects that can be used in for..of are called iterable.

  • Technically, iterables must implement the method named Symbol.iterator.
    • The result of obj[Symbol.iterator]() is called an iterator. It handles the further iteration process.
    • An iterator must have the method named next() that returns an object {done: Boolean, value: any}, here done:true denotes the end of the iteration process, otherwise the value is the next value.
  • The Symbol.iterator method is called automatically by for..of, but we also can do it directly.
  • Built-in iterables like strings or arrays, also implement Symbol.iterator.
  • String iterator knows about surrogate pairs.

Objects that have indexed properties and length are called array-like. Such objects may also have other properties and methods, but lack the built-in methods of arrays.

If we look inside the specification – we’ll see that most built-in methods assume that they work with iterables or array-likes instead of “real” arrays, because that’s more abstract.

Array.from(obj[, mapFn, thisArg]) makes a real Array from an iterable or array-like obj, and we can then use array methods on it. The optional arguments mapFn and thisArg allow us to apply a function to each item.

Set and Map

Ref: https://javascript.info/map-set

Map – is a collection of keyed values.

Methods and properties:

  • new Map([iterable]) – creates the map, with optional iterable (e.g. array) of [key,value] pairs for initialization.
  • map.set(key, value) – stores the value by the key.
  • map.get(key) – returns the value by the key, undefined if key doesn’t exist in map.
  • map.has(key) – returns true if the key exists, false otherwise.
  • map.delete(key) – removes the value by the key.
  • map.clear() – removes everything from the map.
  • map.size – returns the current element count.

The differences from a regular Object:

  • Any keys, objects can be keys.
  • Additional convenient methods, the size property.

Set – is a collection of unique values.

Methods and properties:

  • new Set([iterable]) – creates the set, with optional iterable (e.g. array) of values for initialization.
  • set.add(value) – adds a value (does nothing if value exists), returns the set itself.
  • set.delete(value) – removes the value, returns true if value existed at the moment of the call, otherwise false.
  • set.has(value) – returns true if the value exists in the set, otherwise false.
  • set.clear() – removes everything from the set.
  • set.size – is the elements count.

Iteration over Map and Set is always in the insertion order, so we can’t say that these collections are unordered, but we can’t reorder elements or directly get an element by its number.

WeakMap and WeakSet

Ref: https://javascript.info/weakmap-weakset

WeakMap is Map-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means.

WeakSet is Set-like collection that stores only objects and removes them once they become inaccessible by other means.

Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed.

WeakMap and WeakSet are used as “secondary” data structures in addition to the “main” object storage. Once the object is removed from the main storage, if it is only found as the key of WeakMap or in a WeakSet, it will be cleaned up automatically.

Object.keys, values, entries

Ref: https://javascript.info/keys-values-entries

For plain objects, the following methods are available:

  • Object.keys(obj) – returns an array of keys.
  • Object.values(obj) – returns an array of values.
  • Object.entries(obj) – returns an array of [key, value] pairs.

Destructing assignments

Ref: https://javascript.info/destructuring-assignment

  • Destructuring assignment allows for instantly mapping an object or array onto many variables.

  • The full object syntax:

      let {prop : varName = default, ...rest} = object
    

    This means that property prop should go into the variable varName and, if no such property exists, then the default value should be used.

    Object properties that have no mapping are copied to the rest object.

  • The full array syntax:

      let [item1 = default, item2, ...rest] = array
    

    The first item goes to item1; the second goes into item2, all the rest makes the array rest.

  • It’s possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one.

Date and time

Ref: https://javascript.info/date

  • Date and time in JavaScript are represented with the Date object. We can’t create “only date” or “only time”: Date objects always carry both.
  • Months are counted from zero (yes, January is a zero month).
  • Days of week in getDay() are also counted from zero (that’s Sunday).
  • Date auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours.
  • Dates can be subtracted, giving their difference in milliseconds. That’s because a Date becomes the timestamp when converted to a number.
  • Use Date.now() to get the current timestamp fast.

Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds.

Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has performance.now() that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point):

 alert(`Loading started ${performance.now()}ms ago`);
// Something like: "Loading started 34731.26000000001ms ago"
// .26 is microseconds (260 microseconds)
// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct

Node.js has microtime module and other ways. Technically, almost any device and environment allows to get more precision, it’s just not in Date.

JSON.stringify

The JSON (JavaScript Object Notation) is a general format to represent values and objects. It is described as in RFC 4627 standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it’s easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.

JavaScript provides methods:

  • JSON.stringify to convert objects into JSON.
  • JSON.parse to convert JSON back into an object.

For instance, here we JSON.stringify a student:

    let student = {
      name: 'John',
      age: 30,
      isAdmin: false,
      courses: ['html', 'css', 'js'],
      wife: null
    };
    
    let json = JSON.stringify(student);
    
    alert(typeof json); // we've got a string!
    
    alert(json);
    /* JSON-encoded object:
    {
      "name": "John",
      "age": 30,
      "isAdmin": false,
      "courses": ["html", "css", "js"],
      "wife": null
    }
    */

The resulting json string is called a JSON-encoded or serialized or stringified or marshalled object. We are ready to send it over the wire or put into a plain data store.

Please note that a JSON-encoded object has several important differences from the object literal:

  • Strings use double quotes. No single quotes or backticks in JSON. So ‘John’ becomes “John”.
  • Object property names are double-quoted also. That’s obligatory. So age:30 becomes “age”:30.

JSON.stringify can be applied to primitives as well.

JSON supports following data types:

  • Objects { ... }
  • Arrays [ ... ]
  • Primitives:
    • strings,
    • numbers,
    • boolean values true/false,
    • null.

JSON is data-only language-independent specification, so some JavaScript-specific object properties are skipped by JSON.stringify.

Namely:

  • Function properties (methods).

  • Symbolic properties.

  • Properties that store undefined.

      let user = {
        sayHi() { // ignored
          alert("Hello");
        },
        [Symbol("id")]: 123, // ignored
        something: undefined // ignored
      };
        
      alert( JSON.stringify(user) ); // {} (empty object)
    

Excluding and transforming: replacer

The full syntax of JSON.stringify is:

    let json = JSON.stringify(value[, replacer, space])

value A value to encode.

replacer Array of properties to encode or a mapping function function(key, value).

space Amount of space to use for formatting

Most of the time, JSON.stringify is used with the first argument only. But if we need to fine-tune the replacement process, like to filter out circular references, we can use the second argument of JSON.stringify.

If we pass an array of properties to it, only these properties will be encoded.

For instance:

    let room = {
      number: 23
    };
    
    let meetup = {
      title: "Conference",
      participants: [{name: "John"}, {name: "Alice"}],
      place: room // meetup references room
    };
    
    room.occupiedBy = meetup; // room references meetup
    
    alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
    /*
    {
      "title":"Conference",
      "participants":[{"name":"John"},{"name":"Alice"}],
      "place":{"number":23}
    }
    */

Now everything except occupiedBy is serialized. But the list of properties is quite long.

Fortunately, we can use a function instead of an array as the replacer.

The function will be called for every (key, value) pair and should return the “replaced” value, which will be used instead of the original one. Or undefined if the value is to be skipped.

In our case, we can return value “as is” for everything except occupiedBy. To ignore occupiedBy, the code below returns undefined:

    let room = {
      number: 23
    };
    
    let meetup = {
      title: "Conference",
      participants: [{name: "John"}, {name: "Alice"}],
      place: room // meetup references room
    };
    
    room.occupiedBy = meetup; // room references meetup
    
    alert( JSON.stringify(meetup, function replacer(key, value) {
      alert(`${key}: ${value}`);
      return (key == 'occupiedBy') ? undefined : value;
    }));
    
    /* key:value pairs that come to replacer:
    :             [object Object]
    title:        Conference
    participants: [object Object],[object Object]
    0:            [object Object]
    name:         John
    1:            [object Object]
    name:         Alice
    place:        [object Object]
    number:       23
    */

Please note that replacer function gets every key/value pair including nested objects and array items. It is applied recursively. The value of this inside replacer is the object that contains the current property.

The first call is special. It is made using a special “wrapper object”: {"": meetup}. In other words, the first (key, value) pair has an empty key, and the value is the target object as a whole. That’s why the first line is “:[object Object]” in the example above.

The idea is to provide as much power for replacer as possible: it has a chance to analyze and replace/skip even the whole object if necessary.

JSON.parse

To decode a JSON-string, we need another method named JSON.parse.

The syntax:

    let value = JSON.parse(str, [reviver]);

str JSON-string to parse.

reviver Optional function(key,value) that will be called for each (key, value) pair and can transform the value.

    let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
    
    let user = JSON.parse(userData);
    
    alert( user.friends[1] ); // 1

Let’s pass to JSON.parse the reviving function as the second argument, that returns all values “as is”, but date will become a Date:

    let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
    
    let meetup = JSON.parse(str, function(key, value) {
      if (key == 'date') return new Date(value);
      return value;
    });
    
    alert( meetup.date.getDate() ); // now works!

Advanced working with functions

Rest parameters …

There will be no error because of “excessive” arguments. But of course in the result only the first two will be counted.

The rest of the parameters can be included in the function definition by using three dots … followed by the name of the array that will contain them. The dots literally mean “gather the remaining parameters into an array”.

For instance, to gather all arguments into array args:

    function sumAll(...args) { // args is the name for the array
      let sum = 0;
    
      for (let arg of args) sum += arg;
    
      return sum;
    }
    
    alert( sumAll(1) ); // 1
    alert( sumAll(1, 2) ); // 3
    alert( sumAll(1, 2, 3) ); // 6

The rest parameters must be at the end. The rest parameters gather all remaining arguments, so the following does not make sense and causes an error:

    function f(arg1, ...rest, arg2) { // arg2 after ...rest ?!
      // error
    }

Spread syntax

When …arr is used in the function call, it “expands” an iterable object arr into the list of arguments.

For Math.max:

    let arr = [3, 5, 1];
    
    alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments)

We also can pass multiple iterables this way:

    let arr1 = [1, -2, 3, 4];
    let arr2 = [8, 3, -8, 1];
    
    alert( Math.max(...arr1, ...arr2) ); // 8

Also, the spread syntax can be used to merge arrays:

    let arr = [3, 5, 1];
    let arr2 = [8, 9, 15];
    
    let merged = [0, ...arr, 2, ...arr2];
    
    alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)

The spread syntax internally uses iterators to gather elements, the same way as for..of does.

So, for a string, for..of returns characters and …str becomes “H”,“e”,“l”,“l”,“o”. The list of characters is passed to array initializer [...str].

We can copy array or object by using ... like:

    let arr = [1, 2, 3];
    let arrCopy = [...arr]; // spread the array into a list of parameters
                            // then put the result into a new array
                            
    let obj = { a: 1, b: 2, c: 3 };
    let objCopy = { ...obj }; // spread the object into a list of parameters
                              // then return the result in a new object

This way of copying an object is much shorter than let objCopy = Object.assign({}, obj); or for an array let arrCopy = Object.assign([], arr); so we prefer to use it whenever we can.

When we see “…” in the code, it is either rest parameters or the spread syntax.

There’s an easy way to distinguish between them:

When … is at the end of function parameters, it’s “rest parameters” and gathers the rest of the list of arguments into an array. When … occurs in a function call or alike, it’s called a “spread syntax” and expands an array into a list. Use patterns:

Rest parameters are used to create functions that accept any number of arguments. The spread syntax is used to pass an array to functions that normally require a list of many arguments.

Closure and Lexical Environment

Ref: https://javascript.info/closure

“var”

There are two main differences of var compared to let/const:

var variables have no block scope; their visibility is scoped to current function, or global, if declared outside function.

var declarations are processed at function start (script start for globals).

Global Object

Ref: https://javascript.info/global-object

The global object holds variables that should be available everywhere.

That includes JavaScript built-ins, such as Array and environment-specific values, such as window.innerHeight – the window height in the browser.

The global object has a universal name globalThis.

…But more often is referred by “old-school” environment-specific names, such as window (browser) and global (Node.js).

We should store values in the global object only if they’re truly global for our project. And keep their number at minimum.

In-browser, unless we’re using modules, global functions and variables declared with var become a property of the global object.

To make our code future-proof and easier to understand, we should access properties of the global object directly, as window.x.

Function object, Named Function Expression

Ref: https://javascript.info/function-object

Functions are objects.

Here we covered their properties:

name – the function name. Usually taken from the function definition, but if there’s none, JavaScript tries to guess it from the context (e.g. an assignment).

length – the number of arguments in the function definition. Rest parameters are not counted.

If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called a Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.

Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.

They create a “main” function and attach many other “helper” functions to it. For instance, the jQuery library creates a function named $. The lodash library creates a function _, and then adds _.clone, _.keyBy and other properties to it (see the docs when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.

So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.

The “new Function” syntax

Ref: https://javascript.info/new-function

The syntax:

let func = new Function ([arg1, arg2, ...argN], functionBody);

For historical reasons, arguments can also be given as a comma-separated list.

These three declarations mean the same:

new Function('a', 'b', 'return a + b'); // basic syntax
new Function('a,b', 'return a + b'); // comma-separated
new Function('a , b', 'return a + b'); // comma-separated with spaces

Functions created with new Function, have [[Environment]] referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that’s actually good, because it insures us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.

Scheduling: setTimeout and setInterval

Ref: https://javascript.info/settimeout-setinterval

  • Methods setTimeout(func, delay, …args) and setInterval(func, delay, …args) allow us to run the func once/regularly after delay milliseconds.
  • To cancel the execution, we should call clearTimeout/clearInterval with the value returned by setTimeout/setInterval.
  • Nested setTimeout calls are a more flexible alternative to setInterval, allowing us to set the time between executions more precisely.
  • Zero delay scheduling with setTimeout(func, 0) (the same as setTimeout(func)) is used to schedule the call “as soon as possible, but after the current script is complete”.
  • The browser limits the minimal delay for five or more nested call of setTimeout or for setInterval (after 5th call) to 4ms. That’s for historical reasons.
  • Any setTimeout will run only after the current code has finished.

call/apply

Ref: https://javascript.info/call-apply-decorators