Theme NexT works best with JavaScript enabled
0%

JavaScript Summary


IMPORTANT:
Some of the content here is a personal summary/abbreviation of contents on the Mozilla JavaScript Guide and Runoob JavaScript Guide. Feel free to refer to that site if you think some of the sections written here are not clear.


Introduction to JavaScript

A brief history of JavaScript is omitted, but some key points are listed here.

  • ECMAScript

    ECMAScript (ES) is a standard defined for scripting languages, and JavaScript is an implementation of it.

    • so this means that JavaScript is like Python, it does not need a compiler
  • Browser Engines

    Here I refer to engines for executing JavaScript. Different browsers execute JavaScript differently, if they have a different engine. For example:

    • Chrome uses v8 (currently the fastest)
    • Firefox uses SpiderMonkey
  • Components of JavaScript

    On a high level, JavaScript implements the following three components:

    image-20201116213455863

    where:

    • ECMAScript - the standard of scripting languages
    • DOM - controls webpage activities
    • BOM - controls browser activities
  • Object Oriented Programming

    Similar to programs such as Java, it is a OOP.

Basics

This guide assumes some prior knowledge of HTML and CSS, as well as any another programming language, and it will only lists summaries/key points of what I have learnt.

Basic Usages

For controlling website related events/content, JavaScript manipulates the document object:

  • JavaScript is like pieces of code that gets injected into your html during the “compile” time. Therefore, upon testing, the precedence is as from top to bottom of your code:

    • for example:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <!-- JavaScript goes into the script tag -->
      <script>
      document.write("This comes first");
      </script>
      </head>
      <body>
      <script>
      document.write("This comes second");
      </script>
      </body>
      </html>
  • JavaScript can be inserted in any position (head, body) of the html file, using the <script> tag

    • upon testing, precedence of running related JavaScript is:

      • first it searches from current line towards the bottom

      • then it searches in the <body> tag

      • finally, it searches in the <head> tag

        • for example:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <title>Title</title>
          <script>
          function myfunction() {
          document.getElementById("1").innerHTML="Hi,1";
          }
          </script>
          </head>
          <body>
          <script>
          function myfunction() {
          document.getElementById("0").innerHTML="Hi,0";
          }
          </script>
          <p id="0">
          test
          </p>
          <p id="1">
          test
          </p>
          <button type="button" onclick="myfunction()">点击这里</button>
          </body>
          </html>

          where:

          • only

            1
            2
            3
            4
            5
            <script>
            function myfunction() {
            document.getElementById("0").innerHTML="Hi,0";
            }
            </script>

            will be executed

  • Using document.write() during the HTML stream/“compilation”, then it is equivalent of appending to the current html content. However, using document.write() after your page is loaded, it will overwrite the entire webpage.

    • for example:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <script>
      function myfunction(){
      document.write("This will overwrite the entire page");
      }
      document.write("<h1>This is a paragraph</h1>");
      document.write("<p>This is another paragraph</p>");
      </script>
      <p >
      You only use <strong>document.write during the HTML output stream</strong>。
      </p>
      <button type="button" onclick="myfunction()">Click here</button>

Outputting Data

  1. window.alert() 弹出警告框。
  2. document.write() 方法将内容写到 HTML 文档中。
  3. innerHTML 写入到 HTML 元素。
  4. console.log() 写入到浏览器的控制台。

Basic Syntax

Variable Identifiers are case-sensitive and uses the Unicode character set.

  • For example, the word Früh is legal as variable name.

    1
    let Früh = "foobar";
  • However, similar to Java and C, you can only use characters, numbers and symbols _$.

Statements are separated by semicolons (;).

  • For example, the variable declaration above.
  • A semicolon is not necessary after a statement if it is written on its own line. However, it is considered the best practice to use a ; for every statement.

Comments is pretty much the same in Java/C++ and other languages.

  • An example is the easiest:

    1
    2
    3
    4
    5
    6
    7
    // a one line comment

    /* this is a longer,
    * multi-line comment
    */

    /* You can't, however, /* nest comments */ SyntaxError */

Variable Declarations - JavaScript has three kinds of variable declarations.

  • var

    Declares a variable, optionally initializing it to a value.

    • For example, var x = 42;. This syntax can be used to declare both local and global variables, depending on the execution context.

var declarations, wherever they occur, are created (but not assigned) before any code is executed. This is called hoisting

  • let

    Declares a block-scoped, local variable, optionally initializing it to a value.

    • For example, let y = 13. This syntax can be used to declare a block-scope local variable.
  • const

    Declares a block-scoped, read-only named constant.

    • however, const only protects the assignment of the variable. Fields of the const will not be protected (similar to const in C):

      1
      2
      const MY_OBJECT = {'key': 'value'};
      MY_OBJECT.key = 'otherValue'; // is allowed
  • Undeclared Global Variable

    • For example, x = 42. This form creates an undeclared global variable. It also generates a strict JavaScript warning

Undefined Variables

  • A variable declared using the var or let statement with no assigned value specified has the value of undefined.

    • An attempt to access an undeclared variable results in a ReferenceError exception
  • undefined can be used to determine whether a variable has a value

    • for example:

      1
      2
      3
      4
      5
      6
      var input;
      if (input === undefined) {
      doThis();
      } else {
      doThat();
      }
  • undefined behaves the same as false when used in a Boolean context

    • for example:

      1
      2
      var myArray = [];
      if (!myArray[0]) myFunction();

      where:

      • myFunction executes if myArray element is undefined
  • undefined value converts to NaN when used in numeric context.

    • for example:

      1
      2
      var a;
      a + 2; // Evaluates to NaN

null Variable

  • null behaves as 0 in numeric contexts and as false in Boolean contexts.

    • this means it’s “safer” than undefined

    • for example:

      1
      2
      var n = null;
      console.log(n * 32); // Will log 0 to the console

Variable Scope

  • global variable is available to any other code in the current document

    • variable declared outside of any function
  • block-scope variable is available to any other code in the current block

    • a variable declared with var is available within the function/global scope

      • for example:

        1
        2
        3
        4
        5
        if (true) {
        var x = 5; // available to the entire outer function.
        // In this case, it will be global context
        }
        console.log(x); // x is 5
    • a variable declared with let is available within the current code block

      • for example:

        1
        2
        3
        4
        if (true) {
        let y = 5; // only available within this if block
        }
        console.log(y); // ReferenceError: y is not defined

In short:

  • variable declaration var follows the rule specified above
  • variable declaration with let or const have a more limited scope and behaves in the same way
    • except that const is ready only and requires a value at initialization time
    • cannot declare a const with the same name as a function or variable in the same scope.

Variable Hoisting

Hoisting refer to the ability of referring to a variable declared later, without getting an exception.

  • Variables in JavaScript are, in a sense, “hoisted” (or “lifted”) to the top of the function or statement.

However, variables that are hoisted return a value of undefined.

  • In ECMAScript 2015, let and const are hoisted as well but not initialized.

    • for example:

      1
      2
      console.log(x); // ReferenceError
      let x = 3;

Variable hoisting example:

1
2
3
4
5
6
var myvar = 'my value';

(function() {
console.log(myvar); // undefined
var myvar = 'local value';
})();

can be interpreted as:

1
2
3
4
5
6
7
var myvar = 'my value';

(function() {
var myvar;
console.log(myvar); // undefined
myvar = 'local value';
})();

Function Hoisting

For functions, the idea is the same:

  • only declaration/initialization are hoisted, but the actual value/function expressions are not.

For example:

1
2
3
4
5
baz(); // TypeError: baz is not a function

var baz = function() {
console.log('bar2');
};

would be the same as

1
2
3
4
5
6
var baz;
baz(); // TypeError: baz is not a function

baz = function() {
console.log('bar2');
};

Using Global Variables

Global variables are in fact properties of the global object.

  • In web pages, the global object is window (and inside that browser window, you have your HTML page)

This means:

  • The window object is the object representing the entire browser window
  • all JavaScript global variables are fields of the window object
    • in fact, this includes the HTML DOM document object
    • window.document.getElementById("header"); is the same as document.getElementById("header");
  • all JavaScript global functions are fields of the window object

Consequently, you can access global variables across scripts loaded inside the same browser window

Data Types

There are seven primitive types, and Object

  1. boolean. true and false. (case sensitive)
  2. null. A special keyword denoting a null value (case sensitive).
  3. undefined. A top-level property whose value is not defined.
  4. Number. An integer or floating point number. For example: 42 or 3.14159.
  5. [BigInt. An integer with arbitrary precision. For example: 9007199254740992n.
  6. String. For example: “Howdy”
  7. Symbol (new in ECMAScript 2015). A data type whose instances are unique and immutable.

Data Type Conversion

JavaScript is a dynamically typed language. This means:

  • you don’t have to specify the data type of a variable when you declare it.
  • data types are automatically converted as-needed during script execution.

For example:

  • You can do the follows:

    1
    2
    3
    var answer = 42;
    // some other code
    answer = 'Thanks for all the fish...';

String Concatenation with + Operator

This is similar in other languages, except that:

  • numbers are only converted to strings with the + operator. In the other cases, strings will be converted to number

For example:

  • '37' + 7 // "377", using the + operator
    '37' - 7 // 30, not using + operator
    <!--19-->
    
      An integer between `2` and `36` that represents the *radix* (the **base** in mathematical numeral systems) of the `string`. Be careful—this does ***not*** default to `10`! 
    
    - **returns** the parsed number or `NaN` when
      - the `radix` is smaller than `2` or bigger than `36`, or
      - the **first non-whitespace character** cannot be converted to a number.
  • parseFloat()

    • parseFloat(string)
      <!--20-->
  • Floating-point

    • A floating-point literal can have the following parts:

      • [(+|-)][digits].[digits][(E|e)[(+|-)]digits]

      • for example:

        1
        2
        3
        4
        3.1415926
        -.123456789
        -3.1E+12
        .1e-23
  • Boolean

    • Boolean type has two literal values: true and false.
  • String

    • both single quote and double quote works

    • for example:

      1
      2
      3
      "John Doe"

      'John Doe'
  • Template

    • it can be used as interpolation like %s inside a string for C

      • for example:

        1
        2
        var name = 'Bob', time = 'today';
        var interpolated = `Hello ${name}, how are you ${time}?`
    • or, it can be used for forming a multiline strings

      • for example:

        1
        2
        3
        4
        5
        var poem = 
        `Roses are red,
        Violets are blue.
        Sugar is sweet,
        and so is foo.`
  • Array

    • element inside an array can be of any type, and different elements of the same array can be of different types

    • empty value in an array is automatically made undefined (trailing comma will be ignored)

    • for example:

      1
      [40, "Hi", 1, 5, , 25, true]  // undefined between 5 and 25
  • RegEx

    • A regex literal is a pattern enclosed between slashes.

    • for example:

      1
      var re = /ab+c/;
  • Object

    • a list (map) of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).

    • for example:

      1
      {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"}

      or:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function person(firstname,lastname,age,eyecolor)
      {
      this.firstname=firstname;
      this.lastname=lastname;
      this.age=age;
      this.eyecolor=eyecolor;

      this.changeName=changeName;
      // method of type person
      function changeName(name)
      {
      this.lastname=name;
      }
      }

      myMother=new person("Sally","Rally",48,"green");
      myMother.changeName("Doe");

Objects

The idea is similar with most OOP, yet:

  • Properties of JavaScript objects can also be accessed or set using a bracket notation

    • note that all keys in the square bracket notation are converted to string unless they’re Symbols
  • you can enumerate the properties of an Object

For Example

1
2
3
4
5
6
7
8
9
10
11
12
13
// four variables are created and assigned in a single go,
// separated by commas
var myObj = new Object(),
str = 'myString',
rand = Math.random(),
obj = new Object();

myObj.type = 'Dot syntax';
myObj['date created'] = 'String with space';
myObj[str] = 'String value';
myObj[rand] = 'Random Number';
myObj[obj] = 'Object';
myObj[''] = 'Even an empty string';

where:

  • in the above code, when the key obj of type Object is added to the myObj, JavaScript will call the obj.toString() method, and use this result string as the new key.

For Example: Iterating Over All Properties

1
2
3
4
5
6
7
8
9
10
11
function MyObj(){
this.name = "Jason";
this.id = 1;
}
var myObj = new MyObj();

var result = ``;
for(var i in myObj){
result += `myObj.${i} = ${myObj[i]} \n`;
}
console.log(result);

and the output is:

1
2
myObj.name = Jason 
myObj.id = 1

Creating new Objects

In summary, you can create a new object using:

  • object initializers
  • constructor functions
  • Object.create method

Object Initializers

This would basically be the same as C and C++

1
2
3
4
var obj = { property_1:   value_1,   // property_# may be an identifier...
2: value_2, // or a number...
// ...,
'property n': value_n }; // or a string

where:

  • obj is the name of the new object,
  • each property_i is an identifier (either a name, a number, or a string literal)
  • each value_i is an expression whose value is assigned to the property_i.

Note

  • Identical object initializers create distinct objects that will not compare to each other as equal. In fact, those objects are created as if a call to new Object() were made; that is, objects made from object literal expressions are instances of Object.

Constructor Functions

This would be the same as most OOP:

  1. Define the object type by writing a constructor function.
    • There is a strong convention, with good reason, to use a capital initial letter.
  2. Create an instance of the object with new.

For Example

1
2
3
4
5
6
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var mycar = new Car('Eagle', 'Talon TSi', 1993);

Note

  • since the assignment does not check types, you can in fact pass in anything you want.
    • for example, make could refer to an Object

Object.create Method

This is useful in the sense that:

  • it allows you to choose the prototype object for the object you want to create, without having to define a constructor function.
    • The object being inherited from is known as the prototype, and the inherited properties can be found in the prototype object of the constructor. (see [Prototype Chain](#Prototype Chain))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Animal properties and method encapsulation
var Animal = {
type: 'Invertebrates', // Default value of properties
displayType: function() { // Method which will display type of Animal
console.log(this.type);
}
};

// Create new animal type called animal1
var animal1 = Object.create(Animal);
animal1.displayType(); // Output:Invertebrates

// Create new animal type called Fishes
var fish = Object.create(Animal);
fish.type = 'Fishes';
fish.displayType(); // Output:Fishes

Enumerate the Properties of an Object

there are three native ways to list/traverse object properties:

  • for...in loops

    This method traverses all enumerable properties of an object and its [prototype chain](#Prototype Chain).

  • Object.keys(o)

    This method returns an array with all the own (not in the prototype chain) enumerable properties’ names (“keys”) of an object o.

  • Object.getOwnPropertyNames(o)

    This method returns an array containing all own properties’ names (enumerable or not) of an object o.

For Example: Object.keys(o)

1
2
3
4
5
6
7
8
9
10
11
// simple array
const arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// array-like object
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// array-like object with random key ordering
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

For Example: Object.getOwnPropertyNames()

1
2
3
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.getOwnPropertyNames(obj).sort()); // .sort() is an array method.
// logs ["0", "1", "2"]

Prototype Chain

A prototype of an object can be understood as the “parent” of the object (like in Java)

  • so that properties to be inherited goes in the prototype field

In JavaScript, functions are able to have properties/fields. All functions have a special property named prototype.

  • There is one exception that arrow function doesn’t have a default prototype property

For Example

1
2
3
4
5
6
7
function doSomething(){}
console.log( doSomething.prototype );
// It does not matter how you declare the function, a
// function in JavaScript will always have a default
// prototype property.
var doSomething = function(){};
console.log( doSomething.prototype );

and the output is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
constructor: ƒ doSomething()
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

At this point, this means you can add properties to a function in two ways:

  • add the property to the function directly
  • instantiate it as an Object and add a property
    • Calling a function with the new operator returns an object that is an instance of the function. Properties can then be added onto this object.
1
2
3
4
5
6
function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototyped directly

var doSomeInstancing = new doSomething(); // instantiated an object
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );

this produces the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
prop: "some value"
__proto__:
foo: "bar"
constructor: ƒ doSomething()
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

This tells us three things:

  1. the __proto__ of doSomeInstancing is doSomething.prototype.

  2. constructor and its “superclass“ information is stored in its __proto__ field

    • in the above, the constructor is doSomething(), and its “superclass” is Object
  3. There is a prototype chain that holds property of things, which is used for property/field look up

    • If doSomeInstancing does not have the property, then the browser looks for the property in the __proto__ of doSomeInstancing (a.k.a. doSomething.prototype). If the __proto__ of doSomeInstancing has the property being looked for, then that property on the __proto__ of doSomeInstancing is used.

    • for example:

      1
      2
      3
      4
      5
      6
      function doSomething(){}
      doSomething.prototype.foo = "bar";

      var doSomeInstancing = new doSomething();
      doSomeInstancing.prop = "some value";
      console.log(doSomeInstancing.foo); // returns "bar"

Defining Methods

Methods are defined the way normal functions are defined, except that they have to be assigned as the property of an object.

For Example: Object Created using Initializer

1
2
3
4
5
6
7
8
var person = {
firstName: "John",
lastName : "Doe",
id : 5566,
fullName : function() { // notice no function name after "function" keyword
return this.firstName + " " + this.lastName;
}
};

For Example: Objects Created using Constructor

1
2
3
4
5
6
7
function MyObj(){
this.name = "Jason";
this.id = 1;
this.print = function(color){
console.log(`${this.name} is ${color}`);
}
}

Combing the above two, it means you can attach any function to any type as you want, by simply assigning the function as a property (hence becoming a method).

  • provided that you know something about the object

Getters and Setters

In summary, getters and setters are functions with a special keyword that can be either

  • defined using object initializers, or
  • added later to any object at any time using a getter or setter adding method.

However, there is one extra restriction on getter/setter:

  • the getter method must not expect a parameter
  • the setter method expects exactly one parameter

Note

  • in a sense, getter and setters are more like “computable” fields rather than methods when executing.

For Example: Getter and Setter for Object Initializers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var o = {
a: 7,
get b() { // get the property a
return this.a + 1;
},
set c(x) { //
this.a = x / 2;
}
};

console.log(o.a); // 7
console.log(o.b); // 8 <-- At this point the get b() method is initiated.
o.c = 50; // <-- At this point the set c(x) method is initiated
console.log(o.a); // 25

which would be the same as:

1
2
3
4
5
6
7
8
9
10
11
var o = { a: 0 };

Object.defineProperties(o, {
'b': { get: function() { return this.a + 1; } },
'c': { set: function(x) { this.a = x / 2; } }
});

console.log(o.a); // 7
console.log(o.b); // 8
o.c = 50;
console.log(o.a); // 25

Deleting Properties

You can remove a non-inherited property by using the delete operator. The following code shows how to remove a property.

1
2
3
4
5
6
7
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

// Removes the a property, leaving myobj with only the b property.
delete myobj.a;
console.log ('a' in myobj); // output: "false"

Comparing Objects

Two distinct objects are never equal, even if they have the same properties. Only comparing the same object reference with itself yields true.

Reminder

  • Strict equal (===) Returns true if the operands are equal and of the same type.

For Example

1
2
3
4
5
6
// Two variables, two distinct objects with the same properties
var fruit = {name: 'apple'};
var fruitbear = {name: 'apple'};

fruit == fruitbear; // return false
fruit === fruitbear; // return false

and only:

1
2
3
4
5
6
7
// Two variables, a single object
var fruit = {name: 'apple'};
var fruitbear = fruit; // Assign fruit object reference to fruitbear

// Here fruit and fruitbear are pointing to same object
fruit == fruitbear; // return true
fruit === fruitbear; // return true

Control Flows

Since for most languages, the idea for control flows are the same, this section only discusses some important/interesting ones that JavaScript has.

Conditional Statements

The first thing to know are the expressions that evaluate to true/false:

  • False Values

    • false
    • undefined
    • null
    • 0
    • NaN
    • the empty string ("")
  • True Values

    • All other values, including all objects

In this sense, it is more similar to languages like C rather than Java

Note

  • There is a non-trivial difference between the object Boolean and the values of true and false:

    1
    2
    3
    var b = new Boolean(false);
    if (b) // this condition evaluates to true
    if (b == true) // this condition evaluates to false

If-Else

This is basically the same across most languages:

1
2
3
4
5
6
7
8
9
if (condition_1) {
statement_1;
} else if (condition_2) {
statement_2;
} else if (condition_n) {
statement_n;
} else {
statement_last;
}

Switch

Again, this is the same in languages like Java:

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression) {
case label_1:
statements_1
[break;]
case label_2:
statements_2
[break;]

default:
statements_def
[break;]
}
  1. The program first looks for a case clause with a label matching the value of expression and then transfers control to that clause, executing the associated statements.

  2. If no matching label is found, the program looks for the optional default clause:

    • If a default clause is found, the program transfers control to that clause, executing the associated statements.
    • If no default clause is found, the program resumes execution at the statement following the end of switch.

Note that:

  • Same as Java, the optional break statement associated with each case clause ensures that the program breaks out of switch once the matched statement is executed.
    • If break is omitted, the program continues execution inside the switch statement (and will evaluate the next case, and so on).

Handling Exceptions

There are some differences between JavaScript and languages like Java:

  1. Just about any object can be thrown in JavaScript.

For Example: Throwing any Object

1
2
3
4
throw 'Error2';   // String type
throw 42; // Number type
throw true; // Boolean type
throw {toString: function() { return "I'm an object!"; } };

For Example: Throwing a Simple Custom Exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create an object type UserException
function UserException(message) {
this.message = message;
this.name = 'UserException';
}

// Make the exception convert to a pretty string when used as a string
// (e.g., by the error console)
UserException.prototype.toString = function() {
return `${this.name}: "${this.message}"`;
}

// Create an instance of the object type and throw it
throw new UserException('Value too high');

where:

  • in general, it is a good practice to have a name and message field (see [Utilizing Error Objects](#Utilizing Error Objects))

Catch and Finally

Since JavaScript is a weakly typed language (as compared to Java), it only makes sense to have at most one catch block:

1
2
3
4
5
6
7
8
openMyFile();
try {
writeMyFile(theData); // This may throw an error
} catch(e) { // As compared to Java, does not differentiate between "types"
handleError(e); // If an error occurred, handle it
} finally {
closeMyFile(); // Always close the resource
}

Note that:

  • Same as in languages like Java, using finally means you might “mask the exceptions” thrown in the original try block:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function f() {
    try {
    throw 'bogus';
    } catch(e) {
    console.log('caught inner "bogus"');
    throw e; // this throw statement is suspended until
    // finally block has completed
    } finally {
    return false; // OVERWRITES/MASKS the previous "throw"
    }
    // "return false" is executed now
    }

    where:

    • in fact, in this case, you will never be able to throw an exception from the function f()

Utilizing Error Objects

In general, two fields are often used:

  1. thename property provides the general class of Error (such as DOMException or Error)
  2. the message generally provides a more succinct message than one would get by converting the error object to a string.

A simple example would be:

1
2
3
4
5
6
try {
doSomethingErrorProne();
} catch (e) { // NOW, we actually use `console.error()`
console.error(e.name); // logs 'Error'
console.error(e.message); // logs 'The message', or a JavaScript error message
}

Loops and Iteration

In summary, you have:

  • for loop

    • similar to the Java and C for loop.

      1
      2
      3
      4
      5
      for (let i = 0; i < selectObject.options.length; i++) {
      if (selectObject.options[i].selected) {
      numberSelected++;
      }
      }

      where:

      • let is often used because the variable would be then local scoped
  • do...while loop

    • again, similar to Java

      1
      2
      3
      do
      statement
      while (condition);
  • while loop

    • while (condition)
        statement
      <!--59-->
  • to break:

    1
    2
    break;
    break [label];

For Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let x = 0;
let z = 0;
labelCancelLoops:
while (true) {
console.log('Outer loops: ' + x);
x += 1;
z = 1;
while (true) {
console.log('Inner loops: ' + z);
z += 1;
if (z === 10 && x === 10) {
break labelCancelLoops; // break the outer loop
} else if (z === 10) {
break;
}
}
}

Enhanced for Loops

There are “two” enhanced for loops:

  • for...in
    • iterates a specified variable over all the enumerable properties of an object
  • for...of
    • iterates over iterable objects (includingArray, Map, Set, arguments object and so on)

For Example: for...in Loop

1
2
3
4
5
6
7
8
function dump_props(obj, obj_name) {
let result = '';
for (let i in obj) {
result += obj_name + '.' + i + ' = ' + obj[i] + '<br>';
}
result += '<hr>';
return result;
}

For an object car with properties make and model, result would be:

1
2
car.make = Ford
car.model = Mustang

Note: If you want to iterate over an array:

  • In most cases, it is better to use a traditional for loop with a numeric index when iterating over arrays, because the for...in statement iterates over user-defined properties in addition to the array elements, if you modify the Array object (such as adding custom properties or methods).

For Example: for...of Loop

1
2
3
4
5
6
7
8
9
10
const arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) { // also iterate the property foo
console.log(i); // logs "0", "1", "2", "foo"
}

for (let i of arr) { // does not iterate the property foo
console.log(i); // logs 3, 5, 7
}

Functions

Defining Functions

A function definition (also called a function declaration, or function statement) consists of the function keyword, followed by:

  • The name of the function.
  • A list of arguments
  • The JavaScript statements that define the function, enclosed in curly brackets, {...}.

Note

  • Same as Java and C, JavaScript passes parameters by value. So that
    • primitive passes by value of the primitive
    • object passes y the value of the address

For Example

1
2
3
function functionName(arguement1, arguement2) {
return arguement1 * arguement2;
}

Function Expressions

This basically treats functions as expressions, and you can also pass them around like variables.

A function expression:

  • can be anonymous
  • assigned to a variable to pass around

For Example: Function Statement

1
2
3
const factorial = function fac(n) { return n < 2 ? 1 : n * fac(n - 1) }

console.log(factorial(3))

Reminder

  • In C, you have the equivalent like this:

    1
    const int (*factorial)(int n) = &fac;

For Example: Passing in a Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function map(f, a) {
let result = []; // Create a new Array
let i; // Declare variable
for (i = 0; i != a.length; i++)
result[i] = f(a[i]); // Uses the function f
return result;
}
const f = function(x) {
return x * x * x;
}

let numbers = [0, 1, 2, 5, 10];
let cube = map(f,numbers);
console.log(cube);

Function returns: [0, 1, 8, 125, 1000].

Function Hoisting

Function declaration, like variables, are hoisted.

However:

  • function expressions are not hoisted.

For Example:

1
2
3
console.log(square(5));
/* ... */
function square(n) { return n * n } // returns 25

However:

1
2
3
4
5
console.log(square)    // square is hoisted with an initial value undefined.
console.log(square(5)) // Uncaught TypeError: square is not a function
const square = function(n) { // function expression
return n * n;
}

Recursion

A function can refer to and call itself. There are three ways for a function to refer to itself:

  1. The function’s name
  2. arguments.callee
  3. An in-scope variable that refers to the function

For example

1
2
3
var foo = function bar() {
// statements go here
}

where, within the function body, the following are all equivalent:

  1. bar()
  2. arguments.callee()
  3. foo()

Closure

Closure describes the property of nested functions such that:

  • the inner function full access to all the variables and functions defined inside the outer function (and all other variables and functions that the outer function has access to)
  • the outer function does not have access to the variables and functions defined inside the inner function.

As a result

  • since the inner function has access to the scope of the outer function, the variables and functions defined in the outer function will live longer than the duration of the outer function execution, if the inner function manages to survive beyond the life of the outer function

For Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var createPet = function(name) {
var sex;

return {
setName: function(newName) {
name = newName;
},

getName: function() {
return name;
},

getSex: function() {
return sex;
},

setSex: function(newSex) {
if(typeof newSex === 'string' && (newSex.toLowerCase() === 'male' ||
newSex.toLowerCase() === 'female')) {
sex = newSex;
}
}
}
}

var pet = createPet('Vivie');
pet.getName(); // Vivie (lived longer than the scope of createPet)

pet.setName('Oliver');
pet.setSex('male');
pet.getSex(); // male
pet.getName(); // Oliver

Variable Hiding

This talks about the phenomena that:

  • If an enclosed function defines a variable with the same name as a variable in the outer scope, then there is no way to refer to the variable in the outer scope again.

For Example

1
2
3
4
5
6
7
var createPet = function(name) {  // The outer function defines a variable called "name".
return {
setName: function(name) {
name = name; // there is no way to refer to the outer function "name" variable
}
}
}

Arguments Object

The arguments of a function are maintained in an array-like object.

  • it is strictly speaking not an Array (see [Rest Parameters](#Rest Parameters))

Within a function, you can address the arguments passed to it as arguments[i].

  • in a sense, this means varargs comes naturally to JavaScript without specification

For Example:

1
2
3
4
function easy(){
return 5;
}
easy(10, 20); // 5

For Example: Using Arguments Object

1
2
3
4
5
6
7
8
9
function myConcat(separator) {
var result = ''; // initialize list
var i;
// iterate through arguments, starts from 1 to skip the `seperator` argument
for (i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}

and calling it:

1
2
// returns "red, orange, blue, "
myConcat(', ', 'red', 'orange', 'blue');

Arrow Functions

Basically, this idea is analogous to the lambda expressions in Java.

One common usage of arrow function is its combination with Array.prototype.map()

  • The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

For Example:

1
2
3
4
5
6
7
const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

Function Parameters

Starting with ECMAScript 2015, there are two new kinds of parameters:

  • default parameters
  • rest parameters

Default Parameters

In JavaScript, parameters of functions default to undefined. However, in some situations it might be useful to set a different default value.

For Example:

1
2
3
4
5
6
function multiply(a, b = 1) {
return a * b;
}

multiply(5); // 5
multiply(); // NaN

Rest Parameters

This is basically varargs

  • in particular, the rest parameters behaves exactly like an Array

For Example:

1
2
3
4
5
6
function multiply(multiplier, ...theArgs) {
return theArgs.map(x => multiplier * x);
}

var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

Difference between Rest Parameters and Argument Object

  • Arguments object includes all arguments passed to the function, whereas rest parameters are those, which are not given another name.
  • The rest parameters are Array instances, whereas arguments object isn’t an array. Array instances can use the following methods: map, sort, pop, etc

Predefined Functions

JavaScript has several top-level, built-in functions (for a full list, please visit this link):

eval()

  • The eval() method evaluates JavaScript code represented as a string.
  • for example, eval("2+2") gives 4

uneval()

  • The uneval() method creates a string representation of the source code of an Object

isNaN()

  • The isNaN() function determines whether a value is NaN or not. Note: coercion inside the isNaN function has interesting rules; you may alternatively want to use Number.isNaN(), as defined in ECMAScript 2015, or you can use typeof to determine if the value is Not-A-Number.

parseFloat()

  • The parseFloat() function parses a string argument and returns a floating point number.

parseInt()

  • The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).

decodeURI()

  • The decodeURI() function decodes a Uniform Resource Identifier (URI) previously created by encodeURI or by a similar routine.

decodeURIComponent()

  • The decodeURIComponent() method decodes a Uniform Resource Identifier (URI) component previously created by encodeURIComponent or by a similar routine.

encodeURI()

  • The encodeURI() method encodes a Uniform Resource Identifier (URI) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two “surrogate” characters).

encodeURIComponent()

  • The encodeURIComponent() method encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two “surrogate” characters).

Prototype and Inheritance

In Java, suppose you want to have something like:

1
2
3
4
5
6
7
public class Manager extends Employee {
public Employee[] reports = new Employee[0];
}

public class WorkerBee extends Employee {
public String[] projects = new String[0];
}

In JavaScript, you need to do:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;

function WorkerBee() {
Employee.call(this);
this.projects = [];
}
WorkerBee.prototype = Object.create(Employee.prototype);
WorkerBee.prototype.constructor = WorkerBee;

Reminder

  • remember that constructor and its superclass information of a custom type is stored in the <CustomeType>.prototype field (see [Prototype Chain](#Prototype Chain)).

Prototypes

In short, a prototype of an Object defines what gets inherited. (It is not to be seen as “prototype object of the current object”).

  • this means that any functions defined in Object.prototype will get inherited in any other Object, and other functions defined only with Object will not be inherited

As a result

  • So Object.prototype.toString(), Object.prototype.valueOf(), etc., are available to any object types that inherit from Object.prototype, including new object instances created from the Person() constructor.

For Example:

1
2
3
4
5
6
7
8
9
10
11
function Person(first, last, age, gender, interests) {
// property and method definitions
this.name = {
'first': first,
'last' : last
};
this.age = age;
this.gender = gender;
}

Person.toString(); // works because it is in Object.prototype.toString()

then, compare:

1
2
3
Person.prototype;
// constructor: ƒ Person(first, last, age, gender, interests)
// __proto__: Object

and:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype;
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()

Note:

  • In the above example, the constructor automatically becomes a property of the prototype.
  • This means that all Objects from Person will have constructor available

Revisiting create()

In summary, Object.create() lets you create an Object with a custom prototype:

1
2
let person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
let person2 = Object.create(person1); // recall person1 is of type Person

then:

1
2
person2.__proto__
// Person {name: {…}, age: 32, gender: "male"}

Using the constructor property

As shown in the example in the Prototypes section above, a constructor property is automatically placed in prototype, so that

  • it is available to all instances of the object
  • and all children inheriting from this object

For Example:

1
2
3
4
5
let person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
let person2 = Object.create(person1);

person1.constructor;
person2.constructor; // outputs the same as above

and more interestingly:

1
let person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

works as well.

Modifying Prototypes

In summary, I show:

  • how to add a method/function to the Person‘s prototype, so that all instances of it/children of it can use the function

For Example:

1
2
3
4
// adding a function `farewell()` to prototype
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
};

then call it with either:

1
2
person1.farewell();
person2.farewell();

where both would output the same.

Note that:

  • be careful when defining fields using this. For instance, below would not work:

    1
    2
    // `this` refers to the global scope
    Person.prototype.fullName = this.name.first + ' ' + this.name.last;

    and only function scope works properly

    1
    2
    3
    4
    Person.prototype.farewell = function() {
    // this refers to the Object calling the function (function scope)
    alert(this.name.first + ' has left the building. Bye for now!');
    };

Inheritance with Prototype

In short, to inherit Teacher from another object Person, you need to:

  1. use Person.call(this, ...paremeters)

    • this sets up the constructor to inherit properties of another object
  2. use Teacher.prototype = Object.create(Person.prototype)

    • this sets up the prototype field of the Teacher object
    • however, this also sets the prototype.constructor to Person (might cause further inheritance issues)
  3. use Object.defineProperty() to reset the constructor to Teacher()

    • so that now prototype.constructor will be Teacher, and inheriting from Teacher would lead to the correct constructor being inherited

Recall that:

  • the Person.prototype would basically have the following content:

    1
    2
    3
    farewell: ƒ ()
    constructor: ƒ Person(first, last, age, gender, interests)
    __proto__: Object

For Example:

Consider the same Person object we had before:

1
2
3
4
5
6
7
8
function Person(first, last, age, gender, interests) {
this.name = {
'first': first,
'last' : last
};
this.age = age;
this.gender = gender;
}
  1. to create another object Teacher that inherits its fields/properties:
1
2
3
4
5
6
function Teacher(first, last, age, gender, interests, subject) {
// basically `super(first, last, age, gender, interests);`
Person.call(this, first, last, age, gender, interests);

this.subject = subject;
}
  1. and then:
1
Teacher.prototype = Object.create(Person.prototype);

where:

  • In this case we are using it to create a new object and make it the value of Teacher.prototype.
  1. Finally, we reset the prototype.constructor to make sure future correct behavior for inheriting from Teacher:
1
2
3
4
Object.defineProperty(Teacher.prototype, 'constructor', {
value: Teacher,
enumerable: false, // so that it does not appear in 'for in' loop
writable: true });

Inheritance with Class

This is applicable since ECMAScript 2015.

In short, this basically allows syntaxes like Java/C++.

For Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}

greeting() {
console.log(`Hi! I'm ${this.name.first}`);
};

farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
};
}

instead of the function constructor and prototype definitions.

Additionally, inheritance becomes:

1
2
3
4
5
6
7
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
super(first, last, age, gender, interests); // note that this is required
this.subject = subject;
this.grade = grade;
}
}

Getters and Setters

In both prototype and class definitions, the idea is basically the same:

  • use get <property>() for getters in a class
  • use set <property>() for setters

For Example: Class Based Getter and Setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
super(first, last, age, gender, interests);
// subject and grade are specific to Teacher
this._subject = subject;
this.grade = grade;
}

get subject() {
return this._subject;
}

set subject(newSubject) {
this._subject = newSubject;
}
}

For Example: Prototype Based Getter and Setter

1
2
3
4
Object.defineProperties(Teacher.prototype, {
'subject': { get: function() { return this._subject; } },
'subject': { set: function(newSubject) { this._subject = newSubject; } }
});

Dynamic Client Side Programming

This section talks about the building blocks of programming a dynamic website/application.

Working with JSON

JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.

  • therefore, all you need to do is to parse the JSON string using JSON.parse()

JavaScript provides a global JSON object that has methods available for converting between the two.

Array as JSON

Basically arrays are formatted using the [] symbol.

For Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let heroes = JSON.parse(`[
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
}
]`);
heroes[0]["powers"][0]; // gives "Radiation resistance"

JSON Structure/Object

In general, an object in JSON uses the format:

1
2
3
{
"propertyName": "value"
}

For Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
'{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [
{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
]
},
{
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
},
{
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
]
}'

Using XMLHttpRequest

In short, you can obtain data with with JavaScript using a XMLHttpRequest to a backend server.

The API called XMLHttpRequest (often called XHR). This is a very useful JavaScript object that allows us to make network requests to retrieve resources from a server via JavaScript (e.g. images, text, JSON, even HTML snippets), meaning that we can update small sections of content without having to reload the entire page.

To do this, you usually need to:

  1. prepare the requestURL
  2. create a new XMLHttpRequest()
  3. open the request and set the request methods and request URL
  4. set the expected response type
  5. send the request
  6. format the response in the request.response into your HTML
    • here you will use the onload function

For Example

For step 1-5:

1
2
3
4
5
6
let requestURL =
'https://mdn.github.io/learningarea/javascript/oojs/json/superheroes.json';
let request = new XMLHttpRequest();
request.open("GET", requestURL);
request.responseType = "json";
request.send();

For step 6:

1
2
3
4
5
6
7
request.onload = function() {
// this variable now contains the JavaScript object based on the JSON!
const superHeroes = request.response;
// these two functions below will format the webpage
populateHeader(superHeroes);
showHeroes(superHeroes);
}

where:

  • The XMLHttpRequest.onload is the function called when an XMLHttpRequest transaction completes successfully.

Lastly, to format the data into the web page:

1
2
3
4
5
6
7
8
9
10
<body>
<header>

</header>

<section>

</section>
<script src="./javascripts/superheroes.js"></script>
</body>

and to populate the header and section:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const header = document.querySelector('header');
const section = document.querySelector('section');

function populateHeader(obj) {
const myH1 = document.createElement('h1'); // creates an <h1> element
myH1.textContent = obj['squadName']; // sets the text content of the element
header.appendChild(myH1); // add the element under the `header`

const myPara = document.createElement('p');
myPara.textContent = 'Hometown: ' + obj['homeTown'] + ' // Formed: ' + obj['formed'];
header.appendChild(myPara);
}

function showHeroes(obj) {
const heroes = obj['members'];

for (let i = 0; i < heroes.length; i++) {
const myArticle = document.createElement('article');
const myH2 = document.createElement('h2');
const myPara1 = document.createElement('p');
const myPara2 = document.createElement('p');
const myPara3 = document.createElement('p');
const myList = document.createElement('ul');

myH2.textContent = heroes[i].name;
myPara1.textContent = 'Secret identity: ' + heroes[i].secretIdentity;
myPara2.textContent = 'Age: ' + heroes[i].age;
myPara3.textContent = 'Superpowers:';

const superPowers = heroes[i].powers;
for (let j = 0; j < superPowers.length; j++) {
const listItem = document.createElement('li');
listItem.textContent = superPowers[j];
myList.appendChild(listItem);
}

myArticle.appendChild(myH2);
myArticle.appendChild(myPara1);
myArticle.appendChild(myPara2);
myArticle.appendChild(myPara3);
myArticle.appendChild(myList);

section.appendChild(myArticle);
}
}

Converting between Objects and Text

The above section worked with a piece of JSON data directly. However, sometimes data sent are in the forms of a String, or that you need to send data across in String. As a result, you need to convert between a String to a JS Object.

To do this, use the functions of the JSON object:

  • parse(): Accepts a JSON string as a parameter, and returns the corresponding JavaScript object.
  • stringify(): Accepts an object as a parameter, and returns the equivalent JSON string.

For Example:

If instead of JSON, you receive text:

1
2
3
4
5
6
7
8
9
10
request.open('GET', requestURL);
request.responseType = 'text'; // now we're getting a string!
request.send();

request.onload = function() {
const superHeroesText = request.response; // get the string from the response
const superHeroes = JSON.parse(superHeroesText); // convert it to an object
populateHeader(superHeroes);
showHeroes(superHeroes);
}

and to use stringify()

1
2
let myObj = { name: "Chris", age: 38 };
let myString = JSON.stringify(myObj);

Asynchronous JavaScript

This section assumes some prior knowledge of threading and blocking, so that you need to know:

  • a browser blocks user’s inputs when the (JS) application has not been computed/rendered
  • programs by default are single threaded (main thread)

Therefore, the solution is avoid blocking is to:

  1. do some expensive task in another thread:
1
2
  Main thread: Task A --> Task C
Worker thread: Expensive task B
  1. use asynchronous programming with a Promise
1
2
Main thread: Task A                   Task B
Promise: |__async operation__|

For Example: Simple Asynchronous JavaScript

Consider the case when you are downloading an image, and then need to do something (e.g. apply a filter) to that image:

1
2
let response = fetch('myImage.png'); // fetch is asynchronous
let blob = response.blob(); // display your image blob in the UI somehow

where:

  • because fetch is async, it immediately goes on the next line of code without the promise that the download has finished.
    • as a result, the second line will throw an error if the data isn’t ready

Async Callbacks

Reminder: Callback functions

  • Callback functions are just functions passed in as an argument into another function. When the background code finishes running, it calls the callback function to let you know the work is done, or to let you know that something of interest has happened.

  • for example:

    1
    2
    3
    4
    5
    6
    7
    btn.addEventListener('click', () => {
    alert('You clicked me!');

    let pElem = document.createElement('p');
    pElem.textContent = 'This is a newly-added paragraph.';
    document.body.appendChild(pElem);
    });

    where:

    • 'click' is the first argument, specifying the type of event that btn to listen for
    • the second argument is the callback function, so that it is invoked after the event is fired

A more complete and relevant example would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function loadAsset(url, type, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = type;

xhr.onload = function() {
callback(xhr.response);
};

xhr.send();
}

function displayImage(blob) {
let objectURL = URL.createObjectURL(blob);

let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}

loadAsset('coffee.jpg', 'blob', displayImage);

where:

  • callback argument in the loadAsset() serves as a callback function that is waiting on the XHR call to finish downloading the resource (using the onload event handler) before it passes it to the callback.

Promises

Promises are the new style of async code that you’ll see used in modern Web APIs. A good example is the fetch() API, which is basically like a modern, more efficient version of XMLHttpRequest.

In short, a Promise is:

  • it’s the browser’s way of saying “I promise to get back to you with the answer as soon as I can

  • technically, it’s an object representing the completion or failure of the async operation (i.e. it is on its way, but it could succeed or fail)

For Example: Using fetch

1
2
3
4
5
6
7
8
fetch('products.json').then(function(response) { // the first promise is the response
return response.json();
}).then(function(json) { // the second promise is response.json (chained promises)
products = json;
initialize();
}).catch(function(err) {
console.log('Fetch problem: ' + err.message);
});

where:

  • fetch() taking a single parameter — the URL of a resource you want to fetch from the network — and returning a promise

then, after the async fetch comes back with result, you have:

  • Two then() blocks. Both contain a callback function that will run if the previous operation is successful, and each callback receives as input the result of the previous successful operation, so you can go forward and do something else to it.
    • In fact, each .then() block returns another promise, meaning that you can chain multiple .then() blocks onto each other, so multiple asynchronous operations can be made to run in order, one after another.
  • The catch() block at the end runs if any of the .then() blocks fail
    • in a similar way to synchronous try...catch blocks, an error object is made available inside the catch(), which can be used to report the kind of error that has occurred. Note however that synchronous try...catch won’t work with promises, although it will work with async/await, as you’ll learn later on.

In essence:

  • Promises are essentially a returned object to which you attach callback functions (with .then()), rather than having to pass callbacks into a function.

Promise.all

For the code before, we are just waiting for a single promise, and then deal with it asynchronously with .then(). But what if you want to run some code only after a whole bunch of promises have all fulfilled?

To do this, you can use the Promise.all() static method:

1
2
3
Promise.all([a, b, c]).then(values => {
...
});

where:

  • the .then() block runs only after all three Promises a, b, c returned.
  • the returned values will be an array of three Promises, in the order of [resultA, resultB, resultC]

For Example:

Below will send three requests, and render the webpage only when all three request’s data have been received.

Step 1, send the three requests async:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');

function fetchAndDecode(url, type) {
// Returning the top level promise (result of the entire chain)
return fetch(url)
.then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
return response.blob();
} else if(type === 'text') {
return response.text();
}
}
})
.catch(e => {
console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
});
}

Step 2: wait for all three Promises async, and then render the page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Promise.all([coffee, tea, description]).then(values => {
console.log(values);

// Store each value returned from the promises in separate variables
//create object URLs from the blobs
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];

// Display the images in <img> elements
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);

// Display the text in a paragraph
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
});

where:

  • basically now values[0] will be the result from coffee request, values[1] will be from tea request, and values[2] will be from description request

finally for Promises

In short, this is the same as the try...catch...finally logic in languages like Java.

  • the .finally() method can be chained onto the end of your regular promise chain, and is guaranteed to be run in the end

For Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fetchAndDecode(url, type) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
return response.blob();
} else if(type === 'text') {
return response.text();
}
}
})
.catch(e => {
console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
})
.finally(() => {
console.log(`fetch attempt for "${url}" finished.`);
});
}

where:

  • fetch attempt for "${url}" finished. will be printed in console no matter the requests failed or succeeded.

Creating Own Promises

In short, we know that a promise “does two things”:

  1. resolve/fulfill the task
    • the underneath resolve() function
  2. failed to resolve/fulfill the task
    • the underneath reject() function

Therefore, this is the common usage of a Promise() constructor:

1
new Promise(resolveFunction, rejectFunction);

so that you will have code like this:

1
2
3
4
5
6
7
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue) // fulfilled
// or
// reject("failure reason") // rejected
});

For Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function timeoutPromise(message, interval) {
return new Promise((resolve, reject) => {
if (message === '' || typeof message !== 'string') {
reject('Message is empty or not a string');
} else if (interval < 0 || typeof interval !== 'number') {
reject('Interval is negative or not a number');
} else {
setTimeout(function(){
resolve(message);
}, interval);
}
});
};

timeoutPromise('Hello there!', 1000)
// then will execute
.then(message => {
alert(message);
})
.catch(e => {
console.log('Error: ' + e);
});

timeoutPromise('Hello there!', -1)
.then(message => {
alert(message);
}) // catch will execute
.catch(e => {
console.log('Error: ' + e);
});

where:

  • if successful, the data Promise will be a string of"Success" (in analogy of what we have done with fetch, the success result is a the response)
  • if failed, the error will be the string error message inside the reject() clause.

For Example:Mimicking fetch

1
2
3
4
5
6
7
8
9
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText); // data if success
xhr.onerror = () => reject(xhr.statusText); // data/reason if failed
xhr.send();
});
}

where:

  • this basically mimics what happens under a fetch API

Introduction to Events

In the case of the Web, events are fired inside the browser window, and tend to be attached to a specific item that resides in it — this might be a single element, set of elements, the HTML document loaded in the current tab, or the entire browser window.

There are many different types of events that can occur. For example:

  • The user selects a certain element or hovers the cursor over a certain element.
  • The user hits a key on the keyboard.
  • The user resizes or closes the browser window.
  • A web page finishes loading.
  • A form is submitted.
  • A video is played, paused, or finishes.
  • An error occurs.

There are even more in the MDN Event reference.

Now, to interact with the events, you typically use the event handler.

  • Each available event has an event handler, which is a block of code (usually a JavaScript function that you as a programmer create) that runs when the event fires.
    • When such a block of code is defined to run in response to an event, we say we are registering an event handler.

Note:

  • Event handlers are sometimes called event listeners — they are pretty much interchangeable for our purposes, although strictly speaking: the listener listens out for the event happening, and the handler is the code that is run in response to it happening.

For Example: A simple Example

First, in the html, there is a button element:

1
<button>Change color</button>

then, we would like to interact with the click event of the button:

1
2
3
4
5
6
7
8
9
10
const btn = document.querySelector('button');

function random(number) {
return Math.floor(Math.random() * (number+1));
}

btn.onclick = function() {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}

where:

  • first we store a reference to the button inside a constant called btn, using the Document.querySelector() function
  • then we defined a function random() that returns a number
  • finally, we have the event handler: the object button has a number of events that can fire on it, and therefore, event handlers available.
    • Here, we are listening for the click event firing, by setting the onclick event handler property to equal an anonymous function containing code that generates a random RGB color and sets the <body> background-color equal to it.
    • Therefore, the event handler code runs/handles whenever the click event fires on the <button> element, that is, whenever a user selects it.

Event Handler Properties

In short, those are properties that exists to contain the event handler code.

An example would be the above code:

1
2
3
4
5
6
const btn = document.querySelector('button');

btn.onclick = function() {
const rndCol = `rgb(${random(255)},${random(255)},${random(255)})`;
document.body.style.backgroundColor = rndCol;
}

where:

  • The onclick property would contain the code for handling the event click

In fact, some other common event handler properties include:

  • btn.onfocus and btn.onblur
    • The color changes when the button is focused (selected) and unfocused (unselected);
  • btn.ondblclick
    • the color changes when the button is double clicked
  • window.onkeypress, window.onkeydown, window.onkeyup
    • The color changes when a key is pressed on the keyboard.
    • note that we register it on the window object, which represents the entire browser window.
  • btn.onmouseover and btn.onmouseout
    • The color changes when the mouse pointer hovers over the button, or when the pointer moves off the button, respectively.

Note that:

  • Some events are general and available nearly anywhere (e.g. an onclick handler can be registered on nearly any element), whereas some are more specific and only useful in certain situations (e.g. it makes sense to use onplay only on specific elements, such as video).

For Example: Using foreach with handler property

Sometimes, when you want to set a number of buttons on the page to simultaneously, you can do:

  1. select all the button elements
1
const buttons = document.querySelectorAll('button');
  1. Iterate through the array of buttons and assign them:
1
2
3
4
5
6
7
8
9
// either
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = bgChange;
}

// or
buttons.forEach(function(button) {
button.onclick = bgChange;
});

where:

  • The forEach() method of the NodeList interface calls the callback given in parameter once for each value pair in the list

Using addEventListener()

Instead of setting the event handler/listener inside the property, you can use the addEventListener() directly to the object:

1
target.addEventListener(type, listener [, options]);

For instance, converting the previous example into addEventListener() code, you will have:

1
2
3
4
5
6
7
8
9
const btn = document.querySelector('button');

function bgChange() {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}

// listens for the 'click' event and handles with 'bgChange'
btn.addEventListener('click', bgChange);

where:

  • Inside the addEventListener() function, there are two parameters:
    • the name of the event we want to this target=btn to listen to
    • the code that comprises the handler function we want to run in handle it.

There are some advantages of this over the mechanism of using property:

  1. there is a counterpart function, removeEventListener() which removes a previously added listener.
  2. you can perform various functions at the same time with addEventListener()

For Example: Using addEventListener and removeEventListener

To register multiple handlers to execute simultaneously when an event is fired:

1
2
3
4
5
6
7
myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);

/* this will just have `functionB` registered
myElement.onclick = functionA;
myElement.onclick = functionB;
*/

then, to remove one of the handlers, use:

1
myElement.removeEventListener('click', functionA);

The Event Object

The event object is automatically passed to event handlers to provide extra features and information.

  • one useful property of the event object is the event.target - a reference to the element the event occurred upon

For Example: Changing the color of the button itself

To get the button, you can use the e.target instead of the btn Object.

1
2
3
4
5
6
7
function bgChange(e) {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
e.target.style.backgroundColor = rndCol;
console.log(e);
}

btn.addEventListener('click', bgChange);

where:

  • notice that the handler function now takes the argument e/evt/event object, which is automatically passed in when an event occurs

Most event handlers you’ll encounter have a standard set of properties and functions (methods) available on the event object; see the Event object reference for a full list.

Preventing Default Behavior

In short, this uses the function preventDefault() of the event Object

  • this is useful when, for example, a user has filled the form incompletely, and you want to prevent the action of submitting the incomplete form.

For Example:

First, you have the form html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form>
<div>
<label for="fname">First name: </label>
<input id="fname" type="text">
</div>
<div>
<label for="lname">Last name: </label>
<input id="lname" type="text">
</div>
<div>
<input id="submit" type="submit">
</div>
</form>
<p></p>

then, you need to alter the event object so that its default action of submitting (for a form) is prevented:

1
2
3
4
5
6
7
8
9
10
11
const form = document.querySelector('form');
const fname = document.getElementById('fname');
const lname = document.getElementById('lname');
const para = document.querySelector('p');

form.onsubmit = function(e) {
if (fname.value === '' || lname.value === '') {
e.preventDefault();
para.textContent = 'You need to fill in both names!';
}
}

Event Capturing and Bubbling

This talks about the phenomena that, since some elements have a layered/nested structure, (e.g. the video element below is inside the div), clicking the video will have also clicked the div:

(source: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events)

As a result, some unwanted action might happen. If you want to only trigger the onclick of the video, then you first need to understand the bubbling and capturing phase:

In the capturing phase:

  • The browser checks to see if the element’s outer-most ancestor (html) has an onclick event handler registered on it for the capturing phase, and runs it if so.
  • Then it moves on to the next element inside <html> and does the same thing, then the next one, and so on until it reaches the element that was actually selected.

In the bubbling phase, the exact opposite occurs:

  • The browser checks to see if the element selected (innermost) has an onclick event handler registered on it for the bubbling phase, and runs it if so.
  • Then it moves on to the next immediate ancestor element and does the same thing, then the next one, and so on until it reaches the <html> element.

In modern browsers, by default, all event handlers are registered for the bubbling phase.

  • this means that it is the Event object that bubbles up from handlers

If you really want to register an event in the capturing phase instead, you can do so by registering your handler using addEventListener(), and setting the optional third property to true.

Therefore, the solution here is to stop the bubbling propagation with the stopPropagation(), so that when the innermost video is clicked, the outer div and so on are not triggered.

  • technically: the standard Event object has a function available on it called stopPropagation() which, when invoked on a handler’s event object, makes it so that first handler is run but the event doesn’t bubble any further up the chain, so no more handlers will be run.

In code, you will need:

1
2
3
4
5
6
7
8
9
10
// `videoBox` is the outer `div` element
videoBox.onclick = function() {
videoBox.setAttribute('class', 'hidden');
};

// `video` is the inner `video` element
video.onclick = function(e) {
e.stopPropagation();
video.play();
};

Event Delegation

This talks about utilizing the bubbling propagation to gain information on its immediate child.

  • since the Event object gets bubbled up, we can check the value of the event.target to know the child value

Consider the case where you have a list of items:

1
2
3
4
5
6
7
8
<ul id="parent-list">
<li id="post-1">Item 1</li>
<li id="post-2">Item 2</li>
<li id="post-3">Item 3</li>
<li id="post-4">Item 4</li>
<li id="post-5">Item 5</li>
<li id="post-6">Item 6</li>
</ul>

then you want to have an event listener for each item if it gets clicked. The most efficient way is to utilize the bubbling and the event object:

1
2
3
4
5
6
7
8
9
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ", e.target.id.replace("post-", ""), " was clicked!");
}
});

where:

  • the Event object is bubbled up, so Event.target contains the innermost clicked information

Introduction to APIs

This section assumes some prior knowledge/experience of using APIs. So that you know what APIs are, and have used them at least for once.

APIs in Client-Side JavaScript

Client-side APIs include:

  • Browser APIs
    • APIs built into your web browser and are able to expose data from the browser and surrounding computer environment and do useful complex things with it.
    • For example, the Web Audio API provides JavaScript constructs for manipulating audio in the browser — taking an audio track, altering its volume, applying effects to it, etc.
  • Third-party APIs
    • APIs not built into the browser by default, and you generally have to retrieve their code and information from somewhere on the Web.
    • For example, the Twitter API allows you to do things like displaying your latest tweets on your website.

And importantly, the distinction between JavaScript Library and JavaScript Frameworks

  • JavaScript libraries — Usually one or more JavaScript files containing custom functions that you can attach to your web page to speed up or enable writing common functionality.
    • Examples include jQuery, Mootools and React.
  • JavaScript frameworks — The next step up from libraries, JavaScript frameworks (e.g. Angular and Ember) tend to be packages of HTML, CSS, JavaScript, and other technologies that you install and then use to write an entire web application from scratch.
    • The key difference between a library and a framework is “Inversion of Control”. When calling a method from a library, the developer is in control. With a framework, the control is inverted: the framework calls the developer’s code. (i.e. you are just “configuring the framework’s behavior”)

Common APIs to Know

  • APIs for manipulating documents loaded into the browser.

    • The most obvious example is the DOM (Document Object Model) API, which allows you to manipulate HTML and CSS — creating, removing and changing HTML, dynamically applying new styles to your page, etc
    • see more in [Manipulating documents](#Manipulating documents)
  • APIs that fetch data from the server to update small sections of a webpage on their own are very commonly used.

    • Some common examples include XMLHttpRequest and the Fetch API. You may also come across the term Ajax, which describes this technique.
    • see more in [Fetching data from the server](#Fetching data from the server)
  • APIs for drawing and manipulating graphics are now widely supported in browsers — the most popular ones are Canvas and WebGL, which allow you to programmatically update the pixel data contained in an HTML canvas element to create 2D and 3D scenes.

  • Audio and Video APIs like HTMLMediaElement, the Web Audio API, and WebRTC allow you to do really interesting things with multimedia

    • For example, creating custom UI controls for playing audio and video, displaying text tracks like captions and subtitles along with your videos
  • Device APIs are basically APIs for manipulating and retrieving data from modern device hardware in a way that is useful for web apps.

    • Examples include telling the user that a useful update is available on a web app via system notifications
  • Client-side storage APIs are becoming a lot more widespread in web browsers — the ability to store data on the client-side is very useful.

    • There are a number of options available, e.g. simple name/value storage with the Web Storage API, and more complex tabular data storage with the IndexedDB API.

In terms of Third-party APIs, some common ones are:

  • The Twitter API, which allows you to do things like displaying your latest tweets on your website.

  • Map APIs like Mapquest and the Google Maps API allows you to do all sorts of things with maps on your web pages.

  • The Facebook suite of APIs enables you to use various parts of the Facebook ecosystem to benefit your app, for example by providing app login using Facebook login, accepting in-app payments, rolling out targeted ad campaigns, etc.

  • etc.

Entry Points of Common APIs

When using an API, you should make sure you know where the entry point is for the API.

  • In The Web Audio API, this is pretty simple — it is the AudioContext object, which needs to be used to do any audio manipulation whatsoever.

  • The Document Object Model (DOM) API also has a simple entry point — its features tend to be found hanging off the Document object, or an instance of an HTML element that you want to affect in some way

  • The Canvas API also relies on getting a context object to use to manipulate things, although in this case, it’s a graphical context rather than an audio context.

    • Its context object is created by getting a reference to the canvas element you want to draw on, and then calling its HTMLCanvasElement.getContext() method

For Example: Using Web Audio API

The entry point is usually the AutioContext Object:

1
2
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();

and then you could add functions such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const audioElement = document.querySelector('audio');
const playBtn = document.querySelector('button');

playBtn.addEventListener('click', function() {
// check if context is in suspended state (autoplay policy)
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}

// if track is stopped, play it
if (this.getAttribute('class') === 'paused') {
audioElement.play();
this.setAttribute('class', 'playing');
this.textContent = 'Pause'
// if track is playing, stop it
} else if (this.getAttribute('class') === 'playing') {
audioElement.pause();
this.setAttribute('class', 'paused');
this.textContent = 'Play';
}
});

// if track ends
audioElement.addEventListener('ended', function() {
playBtn.setAttribute('class', 'paused');
playBtn.textContent = 'Play';
});

For Example: Using DOM APIs

1
2
3
4
const em = document.createElement('em'); // create a new em element
const para = document.querySelector('p'); // reference an existing p element
em.textContent = 'Hello there!'; // give em some text content
para.appendChild(em); // embed em inside para

For Example: Canvas APIs

To get the entry point for a Canvas, you usually use the context of the canvas object:

1
2
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

then to put some custom shapes into the canvas, you can use the context object:

1
2
3
4
5
6
Ball.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
};

Manipulating documents

Fetching data from the server