You don't know JavaScript

·5 min read

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`

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 compiled, 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 occurs 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 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

What 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 prototype as defined in function/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;
}