Front-end programming topics

11 May 2021

To be a well-rounded front-end programmer, one should have a solid understanding of underlying mechanism of tools she uses. Some might think that knowing how to use the tools is good enough, but the mistakes usually come from the lack of understanding. What’s worse, these mistakes are not easy to detect since ones who make them might not realize they are mistakes.

The topics in this post are all about my own understandings, there might be mistakes. So if you find any, do contact me via Email.

Table of Contents

Closure 闭包


Definition from MDN:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

In my own words, a closure is made up of 3 parts, a function, the usage of variables which are not defined by the function, and an outer scope relative to the function. Essentially, the core of closure is to make use of the scope in different level, e.g. inner function has access to the outer function’s scope (get the value, change the value). However, the power of closure is in binding the variables in the outer scope to instances created. In other words, function instances with their own outer scope variables won’t affect each other when they make changes to the variables, which is not the case if you have a global variable.

Practical use of closure

  1. Hide the variables and leave the interfaces to the clients
  2. Emulate private methods which can only be accessed by other methods in the same class (a function that return an object which contains functions that have access to its outer scope. Outside the function, instances of the function will have their own lexical environment, in effect, create a class with attributes and methods)

Event loop 事件循环


The event loop is a single-threaded loop which runs in the browser, and manages all events. When an event is triggered, the event is added to the queue.

Event loop general implementation:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

JavaScript uses a run-to-completion model, meaning it will not handle a new event until the current event has completed.

With a queue to manage the events, and run-to-completion model, the events will be sequentially processed without affecting each other. The function calls as event handler will be maintained in a stack.

When an event occurs, or a user triggers the event via mouse or keyboard, if the event has listener, then this event (message) will be added to the event loop (message queue), while the events without any listener will be lost.

Event capturing and bubbling

When an event happens, the target binded with the event listener is labelled as event.target. Three phases for the event:

  1. Capturing phase is the process in which an event moves from document root to event.target.
  2. Target phase is the process in which event.target is triggered and the handler is called.
  3. Bubbling phase is the process in which an event moves from the event.target to document root.

Relating to method AddEventListerner third argument[Capture: true]: optional for switching whether the event should be triggered in capturing phase (true for yes, false/default for no), controlling the event handlers are called during the capturing phase or bubbling phase.

event.stopPropagation() is used to stop the event handler in the target the method assigned to, effectly stop bubbling, but it is rarely used. event.stopPropagation() can only stop the event listener which is binded with this method, event.stopImmediatePropagation() can stop all handlers.

Microtasks


Promise handlers .then/.catch/.finally are always asynchronous.

Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue PromiseJobs, more often referred to as the “microtask queue” (V8 term).

As stated in the specification:

Contrast to microtasks, macrotasks are tasks in the event loop including scripts, event handlers, setTimeout(), etc.. Since the event loop is a single-threaded loop, while executing one task, other tasks will have to wait for it to finish, i.e. no rendering will happen and if the task takes too long, the browser will freeze.

Microtasks are tasks relating to promises. .then/catch/finally handlers will be put into the microtask queue, and only when current code (marcotask) is finished, or the handlers will be called.

code example from this post

Example 1

const promise1 = new Promise((resolve, reject) => {
  console.log("promise1");
});
promise1.then(() => {
  console.log(3);
});
console.log("1", promise1);

const fn = () =>
  new Promise((resolve, reject) => {
    console.log(2);
    resolve("success");
  });
fn().then((res) => {
  console.log(res);
});
console.log("start");

Analysis:

  1. From top to bottom to execute the code, the code inside Promise constructor is called, log promise1.
  2. .then handler is put into the microtask queue, waiting for current code to finish.
  3. console.log('1', promise1) is run after console.log('promise1') but before .then.
  4. console.log(2) inside fn is executed and the promise is resolved.
  5. console.log('start') is still in the same marcotask as console.log('promise1') and console.log(3);
  6. After all codes are finished, resolved promises will execute .then handler, in this case console.log(res), print out success
  7. Since promise1 is nevered resolved, .then handler is never called.

Result:

promise1
1 Promise { <pending> }
2
start
success

Example 2

// macro1 -- Whole script
Promise.resolve().then(() => {
  // micro1 -- .then
  console.log("promise1");
  const timer2 = setTimeout(() => {
    // macro3 -- setTimeout
    console.log("timer2");
  }, 0);
});
const timer1 = setTimeout(() => {
  // macro2 -- setTimeout
  console.log("timer1");
  Promise.resolve().then(() => {
    // micro2
    console.log("promise2");
  });
}, 0);
console.log("start"); // macro1 -- execute -- start

Analysis:

  1. Execute script as macro1, add micro1 to microqueue
  2. Add macro2 to the event loop
  3. Execute macro1, log start
  4. Check microqueue, find micro1, execute micro1, log promise1, add macro3 to the event loop
  5. Execute macro2, log timer1, add micro2 to microqueue
  6. Check microqueue, find micro2, execute micro2, log promise2
  7. Execute macro3, log timer2

Result:

start
promise1
timer1
promise2
timer2

Example 3

// macro1
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // macro2
    resolve("success"); // promise1 resolve
  }, 1000);
});
const promise2 = promise1.then(() => {
  // promise1 pending promise2 pending
  throw new Error("error!!!"); // micro1
});
console.log("promise1", promise1); // macro1
console.log("promise2", promise2); // macro1
setTimeout(() => {
  // macro3
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 2000);

Result:

promise1 promise { <pending> }
promise2 promise { <pending> }
Uncaught in promise Error
promise1 promise { <resolved> "success" }
promise2 promise { <rejected>: Error: error!!!}

Example 4

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // macro2
    resolve("success"); // promise1 resolved
    console.log("timer1"); // macro2
  }, 1000);
  console.log("promise1里的内容"); // macro1
});
const promise2 = promise1.then(() => {
  // promise pending
  throw new Error("error!!!"); // micro1 after macro2
});
console.log("promise1", promise1); // macro1
console.log("promise2", promise2); // macro1
setTimeout(() => {
  // macro3
  console.log("timer2"); // macro3
  console.log("promise1", promise1); // macro3
  console.log("promise2", promise2); // macro3
}, 2000);

Result:

promise1里的内容
promise1 promise { <pending> }
promise2 promise { <pending> }
timer1
error
timer2
promise1 promise { <resolved> "success" }
promise2 promise { <rejected>: Error: error!!!}

CSS layout


Fixed left box and responsive right box

Left
Right

Prototype and prototype chains


Key points:

  1. Prototype is like class, with inheritance and all.
  2. To get an object’s prototype object directly via Object.getPrototypeOf(obj).
  3. Ones begin with Object.prototype. are inherited, ones begin with Object. are not.
  4. constructor property is contained in prototype property, and every instance object has this property. Also, constructor is a function can be invoked via parentheses, with new keyword, to create a new instance: let person3 = new person1.constructor(...).
  5. Add new methods and property outside the constructor function, call YOUR_OBJECT.prototype.NEW_METHOD/NEW_PROPERTY = ....
  6. ES6 Class Syntax, with class YOUR_CLASS { constructor(...), YOUR_PROPERTIES, YOUR_METHODS(...) ...}

instanceof


The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value.

Syntax: object instanceof constructor/class

My implementation:

const myInstanceof = (object, constructor) => {
  let proto = object.__proto__;
  while (proto !== null) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = proto.__proto__;
  }
  return false;
};

Code example

Open console, check the script right below and interact with the Person Object.

Code example from MDN.

Equality and strict equality


Definition from MDN:

The strict equality operator (===) checks whether its two operands are equal, returning a Boolean result. Unlike the equality operator, the strict equality operator always considers operands of different types to be different.

The equality operator (==) checks whether its two operands are equal, returning a Boolean result. Unlike the strict equality operator, it attempts to convert and compare operands that are of different types.

Comparison between different types, try to convert them to the same type before comparing, see The Abstract Equality Comparison Algorithm.

Compare a object and a string or a number, convert the object using ToPrimitive() function, which

"1" == 1; // true
1 == "1"; // true
0 == false; // true
0 == null; // false
0 == undefined; // false
0 == !!null; // true, look at Logical NOT operator
0 == !!undefined; // true, look at Logical NOT operator
null == undefined; // true

const number1 = new Number(3);
const number2 = new Number(3);
number1 == 3; // true
number1 == number2; // false, need to refer to same object

new operator


Definition from MDN

The new operator lets developers create an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

new operator takes parameters of constructor and arguments (a list of values that the constructor will be called with).

Promise.all


Code example from MDN

Handling possible rejections by add .catch handler for each promise in the iterable:

var p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve("p1_delayed_resolution"), 1000);
});

var p2 = new Promise((resolve, reject) => {
  reject(new Error("p2_immediate_rejection"));
});

Promise.all([
  p1.catch((error) => {
    return error;
  }),
  p2.catch((error) => {
    return error;
  }),
]).then((values) => {
  console.log(values[0]); // "p1_delayed_resolution"
  console.error(values[1]); // "Error: p2_immediate_rejection"
});

箭头函数的 this 指向问题

Arrow functions do not bind their own this, instead, they inherit the one from the parent scope, which is called “lexical scoping”.

var name = "x";
var people = {
  name: "y",
  setName: (name) => {
    this.name = name; // this -> window
    return () => {
      return this.name;
    };
  },
};
debugger;
var getName = people.setName(name); // 'x' -> this.name = 'x' -> window.name = 'x'
console.log(people.name); // y -> people.name
console.log(getName()); // x -> window.name

CSS Box Model


Parts:

Difference between IE box model and W3C box model

How width and height is defined:

Use of box-sizing

content-box, padding-box, border-box

When use border-box, it changes the box model to be the way where an element’s specified width and height aren’t affected by padding or borders.

Universal Box Sizing with Inheritance (better practice?)

html {
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: inherit;
}

Reference