JS

DOM

Just a quick recap on DOM: an API provided by browsers. Click on below to expand.
components of a DOM
  • document: the root element
  • node: basically every element in the tree
  • element: only html tags are counted as elements, i.e., node minus attributes, text, comments, etc.
  • nodeList: a list of nodes; access through myList[index] or myList.item(index)
  • attributes: html attributes
  • namedNodeMap: a map with order
  • some key functions
  • document.querySelector("selector"): selector is a css selector; returns an object
  • document.querySelectorAll("selector")
  • document.createElement("name"): name is an html element name
  • parentNode.appendChild(node): can nest appendChild() calls onto a single node
  • element.innerHTML and element.style
  • element.getAttribute("name") and element.setAttribute("name", value)
  • element.addEventListener("event type", listener...): listener is null or a function, there are more parameters in this function
  • Basis

    client-side JS: run on browsers, respond to user actions such as onclick
    server-side JS: run on servers, pretty much like Java servers

    Variables

    Scopes of variables: Three ways to declare variables:
    var x = 3;
    function countNumber(){
        var x = 4;
        console.log(x); // evaluates to 4, these two x are essentially the same
    }
    console.log(x); // this is also 4 
    let x = 3;
    function countNumber(){
        let x = 4; // error: cannot declare x twice
        console.log(x); 
    }
    console.log(x);
    Additional notes:
    Hoisted declaration: if you access a var before it is declared, it's actually ok, but it'll evaluate to undefined. Note that this does NOT apply to let and const.
    Constants cannot have the same name as other functions or variables in the same scope.
    Constants allow mutations: you cannot change value, but you can change values inside.
    const MY_ARR = [100, 200];
    MY_ARR[0] = 200; // this is allowed
    MY_ARR = [200, 300]; // this is NOT allowed

    Data Types

    // strings, numbers, booleans
    let str1 = 'hello'; 
    let str2 = "hello";
    let num = 5.20;
    let success = true;
    
    // BigInt: cannot directly convert into primitives
    const a = Number(1n) + 2; // 3
    const b = 1n + BigInt(2); // 3n
    
    // arrays: doesn't have to be same data type
    let names = ["Amy", 2023, "Bob", 4096, "Carly", true];
    
    // objects
    let header = document.querySelector('h1');
    Notes on data types:
    JS is dynamically typed.
    Apart from primitive types (boolean, number...), there are wrappers called literals, pretty much the same idea as Java primitive and wrappers (int and Integer). Strings, arrays and objects are literals.

    More about numbers:
    JS numbers are 64 bit doubles. For numbers that require more digits, use BigInt.
    apart from numeric values, numbers have three other values: +Infinity, -Infinity, NaN.
    binary 0b1001, octal 0o1234, hex 0x1234.
    exponent 123e-2=1.23, 123e2=12300. Note that e and E are the same.
    number object:
    const biggestNum = Number.MAX_VALUE;
    const smallestNum = Number.MIN_VALUE;
    const infiniteNum = Number.POSITIVE_INFINITY;
    const negInfiniteNum = Number.NEGATIVE_INFINITY;
    const notANum = Number.NaN;
    math object:
    Math.PI;
    Math.sin(3.14);
    Math.abs(-3);
    Math.pow(2);
    Math.floor(2.4);
    Math.min(2,4,5);
    More about strings:
    + are concats when one of the operands is a string.
    string interpolation:
    const name = 'Lev', time = 'today';
    `Hello ${name}, how are you ${time}?`

    Operators

    ** // exponentiation
    >> // signed right shift (left pad with 1 if the number is negative)
    >>> // unsigned right shift (left pad with 0 regardless of the sign), BigInt doesn't support this
    x??=1; // assigns x=1 if x is null
    desctructuring assignment
    const foo = ["one", "two", "three"];
    
    // without destructuring
    const one = foo[0];
    const two = foo[1];
    const three = foo[2];
    
    // with destructuring
    const [one, two, three] = foo;
    arithmatics
    1/0 === Infinity;
    1/2 === 1.0/2.0;
    logical operators (short circuit)
    const and1 = "Cat" && "Dog"; // true && true returns Dog
    
    const or1 = "Cat" || "Dog"; // true || true returns Cat
    const or2 = false || "Cat"; // f || t returns Cat
    const or3 = "Cat" || false; // t || f returns Cat
    
    const not1 = !"Cat"; // !t returns false
    delete operator: deletes an object's property. Don't use it on arrays.
    delete object.property;
    delete object[propertyKey];
    delete objectName[index];
    
    // note that you cannot delete system defined properties
    delete Math.PI; // can't do this
    typeof operator
    typeof myFun; // const myFun = new Function("5 + 2"); returns "function"
    typeof shape; // const shape = "round"; returns "string"
    typeof size; // const size = 1; returns "number"
    typeof foo; // const foo = [1,2,3]; returns "object"
    typeof null; // returns "object"
    typeof doesntExist; // returns "undefined" 
    in operator: check if the object contains that property
    // Arrays
    const trees = ["redwood", "bay", "cedar", "oak", "maple"];
    0 in trees; // returns true, 0 is index
    "bay" in trees; // returns false (CANNOT use in to check if the array contains an element)
    "length" in trees; // returns true (length is an Array property)
    
    // Strings
    const myString = new String("coral");
    "length" in myString; // returns true
    
    // Objects
    const mycar = { make: "Honda", model: "Accord", year: 1998 };
    "model" in mycar; // returns true
    instanceof operator: check if the object is of a type
    const theDay = new Date(1995, 12, 17);
    if (theDay instanceof Date) {
      // statements to execute
    }

    Control Flow

    Never, ever use == in JS. Use === and !==.
    These are considered false in evaluation: false, undefined, null, 0, NaN, "".
    // boolean objects and primitives are different!
    const obj = new Boolean(false);
    if (obj) // true 
    if (obj == false) // true
    if (obj === false) // false
    
    const pri = false;
    if (pri) // false 
    if (pri == false) // true
    if (pri === false) // true

    Loops

    Let's skip for, while, do-while, and look at the JS version of foreach: two variations, for...in and for...of.
    const arr = [3, 5, 7];
    arr.foo = "hello";
    
    for (const i in arr) // for...in iterates over keys, which are indexes for arrayss
        console.log(i); // "0" "1" "2" "foo"
    
    for (const i of arr) // for...of iterates over values
        console.log(i); // Logs: 3 5 7 there is no hello because it only looks at indexed property
                    

    Label Statement

    Basically an alternative to boolean flag, very similar to goto in C.
    Note that when you use lable on a loop, the value of the iterator does not start all over but continues with what's left.
    // The first for statement is labeled "loop1"
    loop1: for (let i = 0; i < 3; i++) {
        // The second for statement is labeled "loop2"
        loop2: for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1)
            continue loop1; // can use lable statement with either continue or break
        console.log(`i = ${i}, j = ${j}`);
        }
    }
    
    // Logs:
    // i = 0, j = 0
    // i = 0, j = 1
    // i = 0, j = 2
    // i = 1, j = 0 if you change continue to break, logs stop after this line is printed
    // i = 2, j = 0
    // i = 2, j = 1
    // i = 2, j = 2

    Error Handling

    funtion myFunc() {
        // ... 
        if (success)
            return true;
        throw new Error("Execution failed!");
    }
    
    try {
        myFunc();
    } catch (e) {
        console.error(e, e.stack); // other alternatives available, e.g., console.error(e.message);
    } finally {
        // clean up...
        // if there is a return statement here, whatever returned here is the return value of this try-catch-finally block
        // return statements and throw statements prior to this return will be ignored
    }
    Nesting try-catch-finally: possible. but the inner block must either has a catch block or a finally block.

    Functions

    Hoisted declaration

    Functions are essentially objects in JS. Similar to hoisted declaration of variables, function declarations are also hoisted. You can assume that they appear at the top of the module wherever they are declared.
    But note that hoisted declaration DOES NOT apply to function expressions mentioned below.

    JS is pass by value, but that doesn't mean you can't change the fields of an object. See below.
    function changeStuff(a, b, c)
    {
      a = a * 10;
      b.item = "changed";
      c = {item: "changed"};
    }
    
    var num = 10;
    var obj1 = {item: "unchanged"};
    var obj2 = {item: "unchanged"};
    
    changeStuff(num, obj1, obj2);
    
    console.log(num); // 10, num is not changed because it's a primitive
    console.log(obj1.item); // changed, you can change the field of a non-primitive such as array or object
    console.log(obj2.item); // unchanged, you can change the field, but it's pass by value, you can't change the var itself

    Function expressions

    Basically assigning a function to a variable, so that you can pass it to other functions.
    Since you are treating it as a variable, you can use if-statement on function definition, pretty much like how you initialize a variable with an if-statement.
    function map(f, a) { // takes a function as an argument
      const result = new Array(a.length);
      for (let i = 0; i < a.length; i++)
        result[i] = f(a[i]);
      return result;
    }
    
    const cube = function (x) { // assign this unnamed function to a variable called cube
      return x * x * x;         // even if it's named, say function cubeNum(x), it won't work if you call cubeNum()
    };                          // only works if you refer to this function as cube
    
    const numbers = [0, 1, 2, 5, 10];
    console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000]

    Function scope and nested functions

    A function can access whatever that shares the same scope or with a greater scope. e.g. A function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables to which the parent function has access, but the parent cannot access the child because the child is private to the parent (scope is greater).
    function outside(x) {
      function inside(y) {
        return x + y;
      }
      return inside;
    }
    
    const fnInside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
    console.log(fnInside(5)); // 8
    console.log(outside(3)(5)); // 8

    Parameters and arguments

    Arguments can be accessed as an array within a function. Just use arguments[idx].
    function multiply(a, b = 1) { // you can set default parameters, otherwise they are undefined by default
      return a * b;
    }
    console.log(multiply(5)); // 5
    
    function multiply(multiplier, ...theArgs) { // or use rest parameter, which allows an infinite number of arguments passed to this function
        return theArgs.map((x) => multiplier * x);
    }
    const arr = multiply(2, 1, 2, 3);
    console.log(arr); // [2, 4, 6]

    Arrow functions

    const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
    
    const a2 = a.map(function (s) {
      return s.length;
    });
    
    console.log(a2); // [8, 6, 7, 9]
    
    const a3 = a.map((s) => s.length);
    
    console.log(a3); // [8, 6, 7, 9]
    arrow functions vs regular functions declare with function:
    1. this inside an arrow function always refer to the outer function; but in a regular function it could refer to many things
    2. arrow function cannot be used with the new keyword, i.e., constructors have to be regular functions
    3. you don't need to return in an arrow function

    Data Structures

    Array Literals

    Arrays are not variables, they are literals.
    let names = ["Amy", 2023, , 4096, "Carly", true]; // this is okay. names.length = 6, names[2] = <empty item>
    let names = ["Amy", 2023, , 4096, "Carly", true,] // names.length = 6, only trailing commas are ignored

    Objects

    const car = { myCar: "Saturn", getCar: carTypes("Honda"), special: sales };
    const car = { "!": "Saturn", "?": carTypes("Honda"), "|": sales }; // invalid property names must be enclosed in quotes.

    Collections

    OOP

    Encapsulation with nested functions

    More about nested functions, see closures.
    const getCode = (function () {
      const apiCode = "0]Ealeh&2"; // A code we do not want outsiders to be able to modify…
    
      return function () {
        return apiCode;
      };
    })();
    
    console.log(getCode()); // "0]Ealeh&2"