Summarized some common knowledge points and questions in intermediate front-end interviews, covering topics such as the event loop, on-demand loading in Webpack and Vite, HTTP cache control strategies, and the differences between Vue2 and Vue3.
Basic Types in JS
The basic types in JavaScript refer to: those value types stored in memory. They are immutable, which means that once created, the value type cannot be changed.
7 Basic Types in JS
Boolean: Represents true or false
Number: Represents integers, floats, or NaN
String: Represents a string of characters
Null: Represents an empty value
Undefined: Represents a variable that has not been assigned a value
Symbol: Represents a unique value (added in ES6)
BigInt: Represents an integer of arbitrary precision (added in ES11)
Detailed Introduction
Boolean
Boolean values are only two: true or false. They are usually used to represent conditions or switches.
Undefined indicates that a variable has not been assigned a value.
For example:
let variable;console.log(variable); // undefined
Symbol
Symbol is a new data type introduced in ES6, which represents a unique value. Symbol values are usually used to represent object property keys or private members.
BigInt is a primitive type in JavaScript used to represent integers of arbitrary precision. It can represent integer values beyond the range that the Number type can represent, that is, integers beyond ±(2^53-1) (i.e., Number.MAX_SAFE_INTEGER). BigInt was introduced as a new feature in ECMAScript 2020 (ES11).
BigInt can be created by adding the n suffix to a number, for example:
In this example, 1234567890123456789012345678901234567890n is an integer of type BigInt.
Since the integers represented by BigInt have no range limits, they can be used for operations on large integers, such as in encryption algorithms, numerical computations, and other fields.
Although BigInt is similar to the Number type, they are not completely compatible with each other. BigInt cannot be directly mixed with Number in operations and needs to be manipulated using BigInt methods and operators. Additionally, BigInt does not support the methods on the Number object.
The operations and operators supported by BigInt include addition (+), subtraction (-), multiplication (*), division (/), remainder (%), and comparison operators (<, >, <=, >=, ==, !=, ===, !==). Additionally, BigInt provides some methods for operations and conversions, such as BigInt.asIntN(), BigInt.asUintN(), etc.
It should be noted that since BigInt is a new feature introduced in ES11, it may not be supported in some older browsers. When using BigInt, you should ensure that your runtime environment supports this feature, or use an appropriate polyfill or transpilation tool for compatibility handling.
Reference Types in JS
In JavaScript, reference types refer to objects stored in heap memory, which are mutable, meaning that once created, their values can be changed.
Common Reference Types in JS:
Object
Array
Function
Date
RegExp
Error
Other built-in objects
Detailed Introduction
Object
An object is the most basic reference type in JavaScript. It is an unordered collection composed of key-value pairs. The keys of an object are strings, and the values can be any type of data, including primitive types and other reference types.
For example:
const person = { name: "John Doe", age: 25, address: { city: "New York", state: "NY", zip: "10001", },};
Array
An array is a special type of object used to store an ordered collection of data. Each element of an array can be any type of data, including primitive types and other reference types.
A function is a special kind of object that can be called to execute code. A function can accept parameters and return values, and it can also be used as an object's property and method.
For example:
function add(a, b) { return a + b;}const result = add(1, 2); // 3
Date
The Date type is used to represent dates and times. It is a built-in object in JavaScript and can be used to create instances representing specific dates and times.
For example:
const date = new Date();console.log(date.toString()); // "Thu Feb 16 2023 20:07:12 GMT+0800 (中国标准时间)"
Regular Expressions (RegExp)
Regular expressions are objects used to match string patterns. They can be used to search, replace, and extract specific parts of a string.
The error object represents an error that occurs during program execution.
For example:
try { // 这里可能会发生错误} catch (error) { // 处理错误}
The Particularity of Reference Types in JS
Reference: The value of a reference type is passed by reference. This means that assigning a reference type actually copies the reference to the object, rather than copying the object itself.
Mutability: The value of a reference type can be changed. Changes to the properties of a reference type will affect all variables that reference the object.
Prototype: Each reference type has a prototype object, which contains all the default properties and methods of that type.
Differences Between Arrow Functions and Regular Functions
The differences between arrow functions and regular functions mainly include five aspects: syntax, this binding, arguments object, constructor, and prototype.
Syntax:
Arrow functions are defined using "=>", while regular functions are defined using the function keyword.
For example:
function add(a, b) { return a + b;}const add = (a, b) => a + b;
this binding:
The this in an arrow function always refers to the context object where it was defined, whereas the this in a regular function changes depending on how the function is called.
For example:
const obj = { name: "obj", add: function (a, b) { console.log(this.name); return a + b; },};const add = (a, b) => { console.log(this.name); return a + b;};obj.add(1, 2); // 'obj'add(1, 2); // undefined
arguments object:
Arrow functions do not have an arguments object, whereas in regular functions you can access the function's parameter list through the arguments object.
For example:
const add = function (a, b) { console.log(arguments); return a + b;};const add = (a, b) => { console.log(arguments); // ReferenceError: arguments is not defined return a + b;};add(1, 2);
Constructor:
Arrow functions cannot be used as constructors, whereas regular functions can.
For example:
const Person = function (name) { this.name = name;};const Person = () => { this.name = name;};const person = new Person("John"); // TypeError: Arrow functions cannot be used as constructors
prototype property:
Arrow functions do not have a prototype property, whereas regular functions can use the prototype property to add methods to the function.
For example:
const Person = function (name) { this.name = name;};Person.prototype.sayHello = function () { console.log(`Hello, my name is ${this.name}`);};const person = new Person("John");person.sayHello(); // 'Hello, my name is John'const Person = () => { this.name = name;};const person = new Person("John");person.sayHello(); // TypeError: person.sayHello is not a function
In summary, arrow functions and regular functions each have their advantages and disadvantages. Arrow functions have a more concise syntax and a more explicit this binding, but they cannot be used as constructors, do not have a prototype property, and cannot use the yield command. Regular functions are more powerful, but their syntax is more cumbersome, and the this binding is easy to confuse. In practical development, the appropriate function definition method should be chosen according to specific needs.
Difference Between Deep Copy and Shallow Copy
Shallow copy and deep copy are two common ways of copying objects. The main difference is that shallow copy only copies the reference of the object, while deep copy copies the entire value of the object, including all properties and sub-objects.
Shallow Copy
Shallow copy means assigning the reference of an object to another object, which means that the two objects will point to the same memory address. If you modify a property of one object, the property of the other object will also change.
Deep Copy
Deep copy means creating a new object and copying all its properties and sub-object values into the new object. This means that the two objects have completely different memory addresses and do not affect each other.
In JavaScript, deep copy can be achieved in the following ways:
Using JSON.parse() and JSON.stringify() methods
Using recursive functions
Using third-party libraries
JSON.parse() and JSON.stringify() Methods
The JSON.parse() and JSON.stringify() methods can convert an object into a JSON string, and then parse the JSON string back into an object. This method can achieve deep copy, but it should be noted that it will ignore the object's prototype and circular references.
Recursive Functions
Using recursive functions can achieve deep copy. Recursive functions traverse each property of the object and copy its value to a new object. This method can achieve deep copy, but it should be noted that it may call recursively, leading to performance issues.
For example:
function deepCopy(obj, visited = new WeakMap()) { // 基本类型或 null,直接返回 if (typeof obj !== "object" || obj === null) return obj; // 如果已拷贝的对象则直接进行返回 if (visited.has(obj)) return visited.get(obj); // 对日期类型和正则表达式进行特殊处理,直接复制构造函数 if (obj instanceof Date || obj instanceof RegExp) return new obj.constructor(obj); let copy = Array.isArray(obj) ? [] : {}; // 创建目标对象 visited.set(obj, copy); // 记录已拷贝的对象 for (let key in obj) { // 遍历对象的属性 // 只复制自身属性,非原型链上的属性 if (Object.prototype.hasOwnProperty.call(obj, key)) { copy[key] = deepCopy(obj[key], visited); // 递归拷贝 } } return copy;}
Third-Party Libraries
You can use third-party libraries to implement deep cloning. For example, Underscore.js and Lodash both provide methods for deep cloning.
Differences Between Class Components and Functional Components
In React, there are two ways to create components: one is class components, and the other is functional components. Class components are created using ES6 classes, while functional components are created using JavaScript functions.
Class Components
Created using ES6 classes and extend the React.Component class. They also have the following characteristics:
Have a lifecycle
Can use state
Can use the 'this' keyword to access the component instance
Functional Components
Functional components are a newer type of component in React, introduced in React 16.8. They are created using JavaScript functions but do not have lifecycle methods or state.
No lifecycle
No state
Cannot use 'this' to access the component instance
Differences Between Class Components and Functional Components
The following are the main differences between class components and functional components:
Feature
Class Component
Functional Component
Creation
Using ES6 class
Using JavaScript function
Inheritance
Inherits from React.Component class
None
Lifecycle
Has lifecycle methods
No lifecycle methods
Status
Usable Status
Unusable Status
this Keyword
Can use this keyword to access component instance
Cannot use this keyword to access component instance
Performance
Slightly worse performance
Slightly better performance
Readability
Slightly worse readability
Slightly better readability
Choosing Between Class Components and Functional Components
The following factors should be considered:
Whether the component needs state
Whether the component needs lifecycle methods
The complexity of the component
Code readability
Understanding Closures
In JS, a closure refers to a function being able to access variables in the scope of its outer function. In other words, closures allow functions to remember the environment in which they were created.
Formation of Closures
Closures are formed when one function is defined inside another function; the inner function can access variables in the outer function's scope, even if the outer function has already finished executing.
Example of a Closure
function outer() { let count = 0; function inner() { count++; console.log(count); } return inner;}const fn = outer();fn(); // 1fn(); // 2console.log(count); // undefined
In this example, the inner function is a closure, which can access the variable count in the outer function outer. Even after the outer function has finished executing, the inner function can still access the count variable.
Advantages of Closures
Can encapsulate code, improving code reusability
Can implement private variables, enhancing code security
Can simulate classes in object-oriented programming
Disadvantages of Closures
May cause memory leaks
May make code harder to understand
Use Cases of Closures
Implementing delayed execution
function delay(fn, delay) { return setTimeout(fn, delay);}const fn = () => console.log("Hello");delay(fn, 1000); // Outputs "Hello" after 1 second
Simulating classes in object-oriented programming
function Person(name) { this.name = name; const sayHello = () => console.log(`Hello, my name is ${this.name}`); return { sayHello, };}const person = new Person("John Doe");person.sayHello(); // Outputs "Hello, my name is John Doe"
Creating private variables
function counter() { let count = 0; function increment() { count++; } function getCount() { return count; } return { increment, getCount, };}const counter = counter();counter.increment();counter.increment();console.log(counter.getCount()); // 2
Promise is an object introduced in ES6 for handling asynchronous operations. It represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Promise States
Pending: Initial state, indicating the asynchronous operation is not yet completed and is in progress.
Fulfilled: Indicates the asynchronous operation has completed.
Rejected: Indicates the asynchronous operation has failed.
Promise State Changes
The state of a Promise can only be changed by resolve and reject.
The resolve function changes the state of the Promise to Fulfilled and passes a result as the value.
The reject function changes the state of the Promise to Rejected and passes an error object as the reason.
Once the Promise state changes, it cannot be reversed!
In this example, the initial state of the Promise is Pending. After 1 second, the resolve function is called, changing the state of the Promise to Fulfilled and passing success as the result. Then, the first callback function of the then method is called and outputs success.
In this example, the initial state of the Promise is Pending. After 1 second, the reject function is called, changing the state of the Promise to Rejected, and passing error as the reason. Then, the second callback function of the then method is called, and error is output.
Promise Scenarios
AJAX request
setTimeout
setInterval
Web API
Promise Features
Immutability: Once the state changes to Fulfilled or Rejected, it will no longer change.
Chaining: You can chain calls using the then() method to handle the success or failure of asynchronous operations in order.
Error Handling: You can catch errors in asynchronous operations using the catch() method.
Basic Usage of Promise
Creating a Promise
const promise = new Promise((resolve, reject) => { // 异步操作});
The new Promise statement accepts a function as a parameter, which has two parameters:
resolve: a function used to change the state of the Promise to Fulfilled.
reject: a function used to change the state of the Promise to Rejected.
Promise nesting refers to returning a new Promise instance within the then method of a Promise instance. This kind of nesting can be used to handle dependencies between multiple asynchronous operations.
Promise.all is a static method of Promise in JavaScript, used to wrap multiple Promise instances into a new Promise instance. This method will return a new Promise instance only when all the passed Promise instances are successfully fulfilled or when one of the Promise instances is rejected.
The return value of the new Promise instance returned by the method depends on the state of the passed-in Promise instances:
If all passed-in Promise instances are successfully fulfilled, the new Promise instance will be successfully fulfilled and return an array containing the successful results of all Promise instances.
If any one of the Promise instances is rejected, the new Promise instance will be immediately rejected and will return the error reason of the first rejected Promise instance.
Notes on Promise.all
The method only waits for all passed-in Promise instances to complete, it does not execute them in the order they were passed.
The method does not change the state of the passed-in Promise instances.
If the passed-in Promise instance is not an iterable object, the method will throw a TypeError.
Regarding Whether the Return Results Have a Sequence
The return value of the new Promise instance returned by the method is an array, and the order of this array is consistent with the order of the passed-in Promise instances.
However, please note that the method does not execute the Promise instances in the order they were passed. They are executed in parallel, so their completion order may differ from the order in which they were passed.
Race Execution
This is a static method provided by Promise, used in multiple Promise instances. As long as one instance changes state first (whether Fulfilled or Rejected), it returns the return value of that first state-changed Promise instance. In other words, it is used for racing, returning the result or error of the Promise that completes the fastest.
Promise, Fetch, and Ajax are all technologies in JavaScript used for handling asynchronous operations. There are some key differences among them:
Promise
Promise is an object in JavaScript used to handle asynchronous operations. It makes asynchronous code easier to read and write, and helps you avoid callback hell.
The main features of Promise:
Can encapsulate the result of an asynchronous operation into an object
Can use the then method to handle the result of the asynchronous operation
Can use the catch method to handle errors from the asynchronous operation
Can use the finally method to execute code regardless of success or failure
Fetch
Fetch is an API in JavaScript used to send HTTP requests. It is easier to use than the traditional XMLHttpRequest API and provides more functionality.
The main features of Fetch:
Uses Promises to handle the results of a request
Supports multiple HTTP methods
Supports request headers and response headers
Supports streaming data transfer
Ajax
Ajax stands for Asynchronous JavaScript and XML, and it is a technology that uses JavaScript to communicate with the server without refreshing the page.
The main features of Ajax:
Uses the XMLHttpRequest object to send HTTP requests
Uses callback functions to handle the result of the request
Can be used to send GET, POST, PUT, DELETE, and other HTTP methods
Can be used to load data, update page content, etc.
Comparison
Feature
Promise
Fetch
Ajax
Purpose
Handle asynchronous operations
Send HTTP requests
Communicate with the server
Return
Promise object
Promise object
XMLHttpRequest object
Handling
then method
then method
Callback function
Error
catch method
catch method
Error callback function
Advantages
Readable and easy to write, avoids callback hell
Simple and easy to use, powerful
Good compatibility, supports multiple browsers
Disadvantages
Deep nesting can lead to performance issues
Not supported in IE 9 or earlier versions
Messy code structure, hard to maintain
Summary
Promise, Fetch, and Ajax are all technologies used to handle asynchronous operations. Each has its advantages and is suitable for different scenarios.
If you need to handle general asynchronous operations, you can use Promise.
If you need to send HTTP requests, you can use Fetch.
If you need to support older browsers, you can use Ajax.
It is recommended to choose the appropriate technology based on actual needs.
Understanding the Diff Algorithm in Vue3
Detailed Introduction to the Vue3 Diff Algorithm
The Diff algorithm in Vue3 is an algorithm used to calculate the differences in the virtual DOM tree. It is used to update the real DOM as efficiently as possible when data changes.
The core idea of the DIFF algorithm is minimum path updates, which means only updating the smallest part of the virtual DOM affected by data. Implementation steps:
Depth-First Traversal of the Virtual DOM Tree: Start from the root node and perform a depth-first traversal of the virtual DOM tree, generating a unique key for each node.
Compare the New and Old Virtual DOM Trees: Use the key to match nodes in the new and old virtual DOM trees. If the node keys are the same, compare their attributes and child nodes. If the node keys are different, the node is considered deleted or newly added.
Generate a List of Update Operations: Based on the differences between the new and old virtual DOM trees, generate a list of update operations. Update operations include:
Inserting nodes
Deleting nodes
Updating node attributes
Moving nodes
Apply Update Operations: Update the real DOM according to the list of update operations.
Compared with the Diff algorithm in Vue2, the Vue3 Diff algorithm has the following improvements:
Faster: By adopting the concept of "minimum path update," the Vue3 Diff algorithm can update only the smallest part of the virtual DOM tree affected by data changes, making it faster.
Less Memory Usage: The Vue3 Diff algorithm no longer uses the "longest common subsequence" algorithm to compare two virtual DOM trees, so it uses less memory.
Easier to Understand: The implementation of the Vue3 Diff algorithm is simpler and easier to understand.
Vue3 Diff Algorithm Example
The following example demonstrates how the Vue3 Diff algorithm works:
In the initial state, the virtual DOM tree is as follows:
<div id="app"> <p>0</p> <button>+</button></div>
When the user clicks the button, the value of count increases by 1, and the virtual DOM tree becomes:
<div id="app"> <p>1</p> <button>+</button></div>
The Vue3 diff algorithm calculates the differences between two virtual DOM trees and generates a list of update operations. The list of update operations is as follows:
According to the update operation list, Vue3 will update the real DOM and update the text content to "1".
Summary: Vue3's Diff algorithm is an efficient virtual DOM tree difference calculation algorithm, which allows Vue3 to be more efficient when updating data.
Detailed explanation of localStorage, sessionStorage, and Cookie
Common points of localStorage, sessionStorage, and Cookie:
All are stored on the browser side and are of the same origin (by the way, to explain same origin: the domain name, protocol, and port number are the same).
Differences between localStorage, sessionStorage, and Cookie:
Different storage sizes:
The size of localStorage is generally 5M
The size of sessionStorage is generally 5M
The size of cookies is generally 4K
Different validity periods:
localStorage is permanently valid unless manually deleted.
sessionStorage is valid for the current session and is cleared when the page or browser is closed.
cookies are valid until the set expiration date, after which they become invalid.
Communication with the server:
localStorage does not participate in server-side communication.
sessionStorage does not participate in server-side communication.
cookies participate in server-side communication and are included in the HTTP headers every time (using cookies to store too much data can lead to performance issues).
Detailed differences in scope between localStorage and sessionStorage:
Different browsers cannot share information in localStorage or sessionStorage.
In the same browser, different pages can share the same localStorage (pages belonging to the same domain and port), but information in sessionStorage cannot be shared between different pages or tabs.
Discussion
Support the author