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 闭包
- Event loop 事件循环
- Event capturing and bubbling
- Microtasks
- CSS layout
- Prototype and prototype chains
- instanceof
- Equality and strict equality
new
operator- Promise.all
- 箭头函数的 this 指向问题
- CSS Box Model
- Reference
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
- Hide the variables and leave the interfaces to the clients
- 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:
- Capturing phase is the process in which an event moves from document root to
event.target
. - Target phase is the process in which
event.target
is triggered and the handler is called. - 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:
- The queue is first-in-first-out: tasks enqueued first are run first.
- Execution of a task is initiated only when nothing else is running.
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:
- From top to bottom to execute the code, the code inside
Promise
constructor is called, logpromise1
. .then
handler is put into the microtask queue, waiting for current code to finish.console.log('1', promise1)
is run afterconsole.log('promise1')
but before.then
.console.log(2)
insidefn
is executed and the promise is resolved.console.log('start')
is still in the same marcotask asconsole.log('promise1')
andconsole.log(3);
- After all codes are finished, resolved promises will execute
.then
handler, in this caseconsole.log(res)
, print outsuccess
- 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:
- Execute script as macro1, add micro1 to microqueue
- Add macro2 to the event loop
- Execute macro1, log
start
- Check microqueue, find micro1, execute micro1, log
promise1
, add macro3 to the event loop - Execute macro2, log
timer1
, add micro2 to microqueue - Check microqueue, find micro2, execute micro2, log
promise2
- 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
Prototype and prototype chains
Key points:
- Prototype is like class, with inheritance and all.
- To get an object’s prototype object directly via
Object.getPrototypeOf(obj)
. - Ones begin with
Object.prototype.
are inherited, ones begin withObject.
are not. constructor
property is contained inprototype
property, and every instance object has this property. Also,constructor
is a function can be invoked via parentheses, withnew
keyword, to create a new instance:let person3 = new person1.constructor(...)
.- Add new methods and property outside the constructor function, call
YOUR_OBJECT.prototype.NEW_METHOD/NEW_PROPERTY = ...
. - 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:
- margins
- borders
- padding
- actual content
Difference between IE box model and W3C box model
How width
and height
is defined:
- IE (<= IE6): width = actual visible/rendered width of an element’s box; height = actual visible/rendered height of an element’s box
- W3C (standard): width + padding + border = actual visible/rendered width of an element’s box; height + padding + border = actual visible/rendered height of an element’s box
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;
}