You don't know JavaScript

WTF is this

Scope

Scope is the current context of execution
  • Global scope
  • Global scope is the outermost scope. Variables declared in the global scope are accessible everywhere
    const foo = 'bar';
    function bar() {
        console.log(foo);
    }
    bar(); // bar
  • Function scope
  • Scoped to use the variables declared inside the function
    function bar() {
        const foo = 'bar1';
        console.log(foo);
    }
    const foo = 'bar2';
    bar(); // bar1
    // throw `foo is not accessible here`
  • Block scope
  • Scoped to use the variables declared inside {} can be called
    {
        const foo = 'bar1';
        console.log(foo); // bar1
    }
    console.log(foo); // throw `foo is not accessible here`

After understanding the scope, let's dive into Lexical Scope

Lexical scope is the scope of the function defined at the time of declaration (Static Scope)
In other words, when the program is complied, it already defined the scope. But when it comes to use case of dynamic scoping, the value is determined by the function that called the current function.
  • Example: bar() and baz() share the same lexical scope (global)
  • const foo = 'bar';
    function bar() {
        console.log(foo);
    }
    function baz() {
        const foo = 'baz';
        bar();
    }
    baz(); // bar

    this

    When a function is invoked, an execution context is created. This context has a property called this which refers to the object that called the function.

    1. When a function is invoked in the global scope, this refers to the global object

    2. When a function is invoked in strict mode, this is undefined
    3. When a function is invoked in the method, this refers to the object that called the function

    4. When a function is invoked with new keyword, this refers to the newly created object

    5. When a function is invoked with call, apply, bind, this refers to the object passed in

    Global context
    - Function defined in global scope is called in global context

    Object context
    • Inside object this refers to the object itself
    const obj = {
      name: "Example",
      regularFunc: function() {
        console.log(this.name); // Refers to 'name' property of 'obj'
      },
      arrowFunc: () => {
        console.log(this.name); // 'this' inherits from the lexical scope
      }
    };
     
    obj.regularFunc(); // Outputs: "Example"
    obj.arrowFunc(); // Outputs: undefined (or whatever is in the global context)
     

    Arrow function

    Arrow function does not have this context
    It directly inherits from lexical context (the context where it is defined). It is not possible to bind this to arrow function. Because of this, arrow function is not suitable for method definition
    // bug occrus as it is not possible to bind `this` to arrow function
    // then it inherits from lexical context
    // which is the global context
    // so `this.name` is undefined
    const foo = {
        name: 'bar',
        sayName: () => {
            console.log(this.name);
            return;
        }
    }
    Suggested use cases of arrow function are: callback function, event handlers.
    const foo = {
        name: 'bar',
        sayName: function() {
            setTimeout(() => {
                console.log(this.name);
            }, 1000);
        }
    }

    Prototype/proto

    In JavaScript, every object/function (using new) has a prototype. The prototype is also an object. All objects inherit their properties and methods from their prototype. The idea of prototype is to to share properties/methods between objects when defining similar objects.

    Here is a catch: for function without new keyword, the prototype is undefined. For function using new keyword to create an object, the prototype is the object that is created by the function.

    function Person() {
        this.name = 'John';
    }
    Person.prototype.age = 24;
     
    const foo = Person(); // no prototype
    const foo2 = new Person(); // has prototype
    WTH does new keyword do?
  • Create an object
  • Set the prototype of the object (i.e. __proto__) to the prototype of the function, making prototype chaining
  • Execute the function with this pointing to the newly created object
  • Return the newly created object
  • Prototype chaining

    JavaScript uses prototype chaining to find the properties and methods of an object by traversing the prototype chain until undefined is found, which is the end of the prototype chain.
    Example: String.prototype, Array.prototype, Object.prototype, Function.prototype, Number.prototype, Boolean.prototype, Date.prototype, RegExp.prototype, Error.prototype, Symbol.prototype, Promise.prototype, Map.prototype, Set.prototype, WeakMap.prototype, WeakSet.prototype, ArrayBuffer.prototype, SharedArrayBuffer.prototype, Atomics.prototype, DataView.prototype, JSON.prototype, Math.prototype, Reflect.prototype

    const name : string = 'John';
    console.log(name.toUpperCase()); // `name` is a string but does not have toUpperCase method
    // find the method in the prototype chain (String.prototype)
     
    const arr = [1, 2, 3];
    console.log(arr.map((item) => item + 1)); // `arr` is an array but does not have map method
    // find the method in the prototype chain (Array.prototype)
     
    const obj = { name: 'John' };
    console.log(obj.hasOwnProperty('name')); // `obj` is an object but does not have hasOwnProperty method
    // find the method in the prototype chain (Object.prototype)

    __proto__

    Only object has __proto__. It is a property of an object that points to the prototype of the object
    The object is inheriting the prototpye as defined in fucntion/class and saving it in __proto__
    function Person(name) {
        this.name = name;
    }
     
    Person.prototype.greet = function() {
        return `Hello, my name is ${this.name}`;
    };
     
    let person1 = new Person("Alice");
    console.log(person1.greet()); // "Hello, my name is Alice"
     
    console.log(person1.__proto__ === Person.prototype); // true
    console.log(Person.prototype.constructor === Person); // true

    Generator

    Generator is a function that can be paused and resumed, using function*/function *foo() syntax.
    Generator function returns an iterator object, which has a next() method that returns an object with two properties: value and done.
    When the generator function is called, the code inside the function is not executed. Instead, it returns an iterator object.
    function* foo() {
        yield 1;
        yield 2;
        yield 3;
    }