Advanced JS Notebook

How JavaScript works?

JavaScript is a single-threaded language that can be non-blocking.

how JavaScript work

JavaScript Engine

For the code below:python

const f()=>{
    const ff()=>{
        console.log('a');
        }
    }
f();

f(), ff()and console.log('a') will be push in the call stack one by one and be popped out in the reverse order (first in last out).web

  • Single threaded means the JavaScript Engine has only one call stack. (simple, multithreaded environment is complicated and has many issues like 'deadlock' to deal with.)
  • Synchronous programming means the program gets executed one line after another in order. If we have one function that takes a lot of time, the whole line will be held up.
    When we need to do things like image processing or making requests over the network like API calls, we should do asynchronous programming.

JavaScript Run-time environment

We can do asynchronous programming with setTimeout(), for example:express

console.log('1');
setTimeout(() => {
    console.log('2');
}, 2000);
console.log('3');
//>> 1, 3, 2
  • console.log('1') is pushed in the call stack and executed, then popped out;
  • setTimeout() was pushed in the call stack. Bc it is not part of JavaScript but part of Web APIs, it triggers the Web APIs, then is popped out of the call stack. And the Web APIs starts a timer of 2 seconds.
  • During the 2 seconds, as the call stack is empty, console.log('3') is pushed in, executed and pushed out.
  • After 2 seconds, the Web APIs adds a callback of setTimeout to the callback queue, ready to rune the content inside of setTimeout().
  • The event loop keeps checking the callback stack and only if the call stack is empty, it will push the first thing in the queue to the stack and do some work. So the callback is removed from the callback queue and added in the call stack, and by running it we add console.log('2') in the call stack, when it is done, the callback is popped out. Now everything is empty.

⚠️ if the time in setTimeout() is set to 0, the output order is still the same, bc only if the call stack is empty, it will push the first thing in the queue to the stack and do some work.app

⚠️ JavaScript is synchronous in the sense that it is single-threaded but can have asynchronous code by using the event queue on things like AJAX requests.async

S

Data structures

Data types

The latest ECMAScript standard defines seven data types (built-in types):ide

  • Six primitive data types:oop

    • Boolean
    • Null
    • Undefine
    • Number
    • String
    • Symbol (new in ECMAScript6)
  • Object

Array and Function are two specialized version of the Object type. Use typeof operator to figure out the type of the value that held by a variable (only values have types, variables are only containers):ui

var a;
typeof a; // >>"undefined"

var b = null;
typeof b; // >>"object"

var c = undefined;
typeof c;// >>"undefined"

typeof function() {} === 'function'; // >>"True"

var arr = [1, 2, 3];
typeof arr; // >>"object"
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays

⚠️ typeof null returns Object instead of null is a long-standing bug in JS, but one that is likely never going to be fixed. Too much code on the Web relies on the bug and thus fixing it would cause a lot more bugs!this

⚠️ The typeof of arrays is object.spa

⚠️ A variable value can be undefined by 1. explicitly assignment, 2. declared with no assignment, 3. function return with no value, 4. usage of void operator

Coercion

Truthy & Falsy

A non-boolean value can always be coerced to a boolean value. The specific list of "falsy" values are as follows:

  • empty string: ""
  • 0, -0
  • invalid number: NaN
  • null, undefined
  • false

Equality & Inequality

There are four equality operators: ==, ===, !=, and !==.
Overall, the difference between == and === is that == checks the equality with coercion allowed, and === checks the equality without coercion allowd ("strictly equality").

The Strict Equality Comparison Algorithm (x === y)

check Ecma-262 Edition 5.1 Language Specification here

  • if: typeof x differs from typeof y, return false
  • elif: typeof x is 'undefine' or 'null', return true, else:
  • elif: typeof x is 'number'

    • if: x or y is NaN, return false (⚠️NaN === nothing)
    • elif: x is the same number value as y, return true
    • elif: x is +0, y is -0 and vice versa, return true
    • else: return false
  • elif: typeof x is 'string'

    • if: x and y are exactly the same sequence of characters (same length and same characters in corresponding positions), return true
    • else: return false
  • elif: typeof x is 'boolean'

    • if: x and y are both 'true' or 'false', return true
    • else: return false
  • elif: typeof x is 'object'

    • if: x and y both refer to the same object, return true
    • else: retrun false
  • else: pass

The Abstract Equality Comparison Algorithm (x == y)

  • if: typeof x is the same as typeof y, return the same result as x === y
  • elif: typeof x is undefined and typeof y is null, and vice versa, return true
  • elif: typeof x is 'string' and typeof y is 'number', return the result of ToNumber(x) == y, and vice versa
  • elif: typeof x is 'boolean', return the result of ToNumber(x) == y, and vice versa ⁉️
  • elif: typeof x is 'object', typeof y is 'string' / 'number', return the result of ToPremitive(x) == y, and vice versa ⁉️
  • else: return false

Sameness comparison of == and ===

Sameness comparison of == and  ===

Relational Comparison ??

Relational Comparison operators incluede <, >, <=, and >=.

var a = 32;
var b = "31";
var c = "c";
var d = "111";

a > b; // >>true
c > b; // >>true
d > b; // >>false

a > c; // >>false
a < c; // >>false
a == c; // >>false

As for x > y, concluded from the cases above:

  • if: both of the operands are 'string's, the comparison is made lexicographically (aka alphabetically like a dictionary)
  • if: one of the operands is 'number' and the other one is 'string':

    • if: the 'string' can be coerced to be 'number', a typical numeric comparison occurs
    • else: the 'string' is coerced to NaN, none of the relations (>, <, >=, <=, ==, ===) are valid.

Arrays

The arrays in JS is similar with those in python with use square brackets []. Some predefined methods are listed below:

var li = [1, 2, 3, 4, 5];
var empty_li = [];

//shift()
var first_of_li = li.shift(); //removes the first element and returns that element
console.log(li) //>> [2, 3, 4, 5]

//pop()
var last_of_li = li.pop(); //removes the last element and returns that element
console.log(li) //>> [2, 3, 4]

//push()
var new_length = li.push(6, 7, 8); //adds items to the end and returns the new lenght of that array
console.log(li) //>> [2, 3, 4, 6, 7, 8]

//concat()
var merged_li = li.concat([9, 10]);//concatenate two arrays without changging them

//sort()
//sorts the elements of an array in place and returns the array. The default sort order is according to string Unicode code points.
var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months) //>> ["Dec", "Feb", "Jan", "March"]
var array1 = [1, 30, 4, 21];
array1.sort();
console.log(array1) //>> [1, 21, 30, 4]

//trim()
//removes whitespace from both ends of a string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.).
var greeting = '   Hello world!   ';
console.log(greeting.trim()); //>> "Hello world!";

//join(separator)
//creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string.
//The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If the separator is an empty string, all elements are joined without any characters in between them.
//If an element is undefined or null, it is converted to the empty string.
var a = ['Wind', 'Rain', 'Fire'];
a.join();      // 'Wind,Rain,Fire'
a.join(', ');  // 'Wind, Rain, Fire'
a.join(' + '); // 'Wind + Rain + Fire'
a.join('');    // 'WindRainFire'
//The following example joins array-like object (arguments), by calling Function.prototype.call on Array.prototype.join.
function f(a, b, c) {
  var s = Array.prototype.join.call(arguments);
  console.log(s); 
}
f(1, 'a', true); //>> "1,a,true"

As for looping, instead of using forEach which simply loop an array but does nothing to its elements, map, filter and reduce are way more convenient.

map

It iterates elements and applying the operations to each of them, in the expectation that the operations return elements. e.g.:

/* double the elements in array */

var array = [5, 2, 10, 26];
var mapArray = array.map(ele => ele * 2);//shortform of 'array.map(ele => {return ele*2;})' coz there is only a single line

//using forEach
var double = [];
array.forEach(ele => {double.push(ele * 2);});

ATTENTION: usaully 'map()' function will not change the original array bc the first argument stores the value of the current element; however, when the elements are objects, the first argument stores its address, so the original array will be changed. e.g: in following codes, array's value will changed into map_Array after using the map() function.

const array = [{
        username: "john",
        team: "red",
        score: 5,
        items: ["ball", "book", "pen"]
    },
    {
        username: "becky",
        team: "blue",
        score: 10,
        items: ["tape", "backpack", "pen"]
    },
    {
        username: "susy",
        team: "red",
        score: 55,
        items: ["ball", "eraser", "pen"]
    },
    {
        username: "tyson",
        team: "green",
        score: 1,
        items: ["book", "pen"]
    },

];

//create a new list with all user information, but add "!" to the end of each items they own.
var mapArray = array.map(t => {t.items = t.items.map(itt => itt+"!"); return t;})

filter

As its name suggests, it filters elements with conditions. As with map, it has to return something.

/* find the elements > 5 in an array */
var array = [5, 2, 10, 26];
var filterArray = array.filter(ele => ele > 5);//shortform of 'array.filter(ele => {return ele > 5;})'

reduce

It is another powerful function which you can even use to do 'map' and 'filter'. It use an 'accumulator' to store the results caused in the body of the function.

/* sum all the elements of an array */
var array = [5, 2, 10, 26];
var reduceArray = array.reduce((accumulator, ele) => ele + accumulator, 0);//shortform for 'array.reduce((accumulator, ele) => {return ele + accumulator;}, 0)

Objects

The objects in JS are declared similarly with dicts in Python, and other than using '[]' to query a property, '.' is also applicable. However, when using '[]', the expressing within should be a string. e.g.:

var empty_user = {};

var user = {
    name: "John",//property name can be unquoted if there is no space between words
    age: 34,
    isMarried: False 
    spells: ["blabla", "blablabla"],
    //a function inside an object is a method
    shout: function(){
        console.log("aaaaaaa")//no comma 
    };

var user_age = user["age"];
var user_name = user.name;
  • Add a property:
    Adding a property is just like query a propety using '[]' or '.':

    user.sex = "male";
    user["hobby"] = "soccer";
  • Empty object and null object:
    We can add properties to an empty object but we cannot set property name of a null object.
    empty object and null object

Reference type

Different from the primitive types defined by the programming language, objects are of reference type. Arrays are also objects.

Context

'Context' is an object-based concept whereas 'scope' is function based.

'Context' refers to an object. Within an object, the keyword “this” refers to that object (i.e. “self”), and provides an interface to the properties and methods that are members of that object. When a function is executed, the keyword “this” refers to the object that the function is executed in.
'Scope' pertains to the variable access of a function when it is invoked and is unique to each invocation

An example:
object-context

From the example above, we know that 'this' in the JavaScript refers to the window, so that console.log() and a() are both its methods and can be called with 'window.' or 'this.'.

图片描述

obj.a() refers to obj.

Instantiation

The instantiation of an object is the creation of a real instance of an object and reuse of codes. Before the instantiation, we shall create a class with a constructor which defines the template. As the code shows below, the constructor allow us to customize proprieties 'name' and 'type' every time we create a Player object like what a consturctor does in C++.

class Player{
    constructor(name, type){
        this.name = name;
        this.type = type;
    }
    introduce(){
        console.log(`Hi, I'm ${this.name}, I'm a ${this.type}`) //syntax template string with ``
    }
}

To create a Wizard class which includes all the proprietyies and methods that Player has (a child of Player):

class Wizard extends Player{
    constructor(name, type){
        super(name, type); //call functions on the parent
    }
    play(){console.log(`I'm a wizard of ${this.type}`);}
}

Now we can do instantiation with the key word new.

instantiation

To get a better interpretation of context, lets run the code below:

class Player{
    constructor(name, type){
        console.log('player',this);
        this.name = name;
        this.type = type;
        console.log('player',this);
    }
    introduce(){
        console.log(`Hi, I'm ${this.name}, I'm a ${this.type}`) //syntax template string with ``
    }
}

class Wizard extends Player{
    constructor(name, type){
        super(name, type); //When used in a constructor, the super keyword appears alone and must be used before the this keyword is used
        console.log('wizard', this);
    }
    play(){console.log(`I'm a wizard of ${this.type}`);}
}

var player = new Player("Dali", "painter");
var wizard = new Wizard("Elio", "blackMagic");

context of instatiation

Identifier names

In JavaScript, variable names (including function names) must be valid identifiers.

  • Unicode: The strict and complete rules for valid characters in identifiers are a little complex (omitted)
  • ASCII: An identifier must start with a- z, A - Z, $, or _. It can then contain any of those characters plus the numerals 0 - 9.

certain words cannot be used as variables, but are OK as property names. These words are called "reserved words," and include the JS keywords ( for, in, if, etc.) as well as null, true, and false.

Modules

As JavaScript does not have a module system, we have lots of ways to import and export modules:
Inline scripts -> Script tags -> IFFE -> ES6 + Webpack2

  • Inline scripts:

    • lack of reusability
    • pollution of the global namespace
      (the global namespace is the window object with all the names. When working with members with pieces of code, it is easy to go off the same names.)
  • Script tags:

    • lack of reusability
    • lack of dependency resolutions
      (the scripts should be added in the proper order)
    • pollution of the global namespace
      (all the functions and variables declared in each of the files will be on the window object)
  • IIFE (Immediately-Invoked Function Expressions):

    • lack of reusability
      (still, we use script tags)
    • lack of dependency resolutions
    • pollution of the global namespace
      (still, we expose one object to the global name scope)
  • Browserify (+ common JS)
  • ES6 + Webpack2

IIFE

Pronounced as "Iffy", the key to IIFE pattern is taking a function and turning it into an expression and executing it immediately.

  • The simple IIFE style (with no return):
  • !function() {
        alert("Hello from IIFE!");
    }();// there is ()
    // ! can be replaced by basically any unary operators such as +, -, ~
    // ! can be replaced by "void"
    // those unary operators and "void" forces JavaScript to treat     whatever coming after ! as expression
  • The classical IIFE style:

    // Variation 1 (recommanded)
    (function() {
        alert("I am an IIFE!");
    }());
    
    // Variation 2
    (function() {
        alert("I am an IIFE, too!");
    })();
    
    // These tow stylistic variations differ slightly on how they work. 
    
    // IIFE with parameters
    (function IIFE(msg, times) {
        for (var i = 1; i <= times; i++) {
            console.log(msg);
        }
    }("Hello!", 5));
    
    // IIFE with returns
    var result = (function() {
        return "From IIFE";
    }());
    alert(result); // alerts "From IIFE"
  • Cases where the parentheses can be omitted (not recommended)

    // Parentheses around the function expression basically force the function to become an expression instead of a statement.
    // But when it’s obvious to the JavaScript engine that it’s a function expression, we don’t technically need those surrounding parentheses as shown below.
    
    var result = function() {
        return "From IIFE!";
    }();
    
    // In the above example, function keyword isn’t the first word in the statement. So JavaScript doesn’t treat this as a function statement/definition.
Any variables and functions declared inside the IIFE are not visible to the outside world. e.g.:
(function IIFE_initGame() {
    // Private variables that no one has access to outside this IIFE
    var lives;
    var weapons;
    
    init();

    // Private function that no one has access to outside this IIFE
    function init() {
        lives = 5;
        weapons = 10;
    }
}());
In this way, we can add variables and functions to a global object ((the only one exposed to the global scope) inside an IIFE:
var myApp = {};
    
(function(){
    myApp.add = function(a, b) {
    return a + b;
    }  
})();

So the advantages of using IIFE includes:

  • Wrapping the variables and functions in an IIFE as private can avoid polluting the global environment, and prevent them from being changed by others.
  • Reduce the look-up scope. Without using IIFE, global objects such as window, document and jQuery in the scope defined by the function have to look up their properties from inside to outside. IIFE can pass them inside of the function directly.

    (function($, global, document) {
    // use $ for jQuery, global for window
    }(jQuery, window, document));
  • Minification optimization, avoid name conflict. Since we can paas values to the arguments in IIFE, we areable to reduce the name of each global object to a one letter word. Known that "$" stands for identifier jQuery in jQuery, but used in other ways in other JavaScript libraries, so confict might happens when using jQuery and other JS libraries. This method is especially useful for writing jQuery third-party library plugins.
  • Manage Browser Memory. If the functions are such that they require single execution, we need not to add those function and variable to the gobal scope. Adding them to global scope would consume space on the Browser Memory. When the IIFE is executed, the required functions and variables are created, used and once IIFE has finished execution, they are available for Garbage Collection, Hence freeing the Browser Memory.

An Example of module pattern using IIFE and closure:

var Sequence = (function sequenceIIFE() {
    
    // Private variable to store current counter value.
    var current = 0;
    
    // Object that's returned from the IIFE.
    return {
        getCurrentValue: function() {
            return current;
        },
        
        getNextValue: function() {
            current = current + 1;
            return current;
        }
    };
    
}());

console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2

Where IIFE makes the variable 'current' private and the closure makes it accessble.

Browserify + Common JS

// js1 "./add"
module.exports = function add(a, b){
    return a+b;
}

// js2
var add = require("./add");

Browserify is a module bundler that runs before putting the website online, it bundles all JS files into one massive file. The common JS syntax instructs it to do it automatically.

ES6 + Webpack2

// js1 "./add"
export const add = (a, b) => a + b;
// or (set the function as default function)
export default function add(a, b){
    return a + b;
}
// js2
import {add} from './add'; // destructuring
// or (no need to destructuring since we import hte default function)
import add from './add';

Webpack is a bundler like Browserify which allows us using ES6 and bundle the files into one or multiple filesbased on your needs.