LMX
BlogSelectedWorkSponsorsAbout
Command Palette

Search for a command to run...

Blog

Intermediate Front-End Interview Compilation - Middle Volume

LMXApr 16, 202418 min read#Interview Summary

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.

Event Loop

The JS event loop is a mechanism that allows JavaScript to remain responsive while handling user input, network requests, and other asynchronous events.

It is a continuous loop that performs the following steps:

  1. Check the event queue: The event queue is a queue that stores events to be processed. If there are events in the queue, they are taken out and the corresponding event handlers are executed.
  2. Execute microtasks: Microtasks are small pieces of code in JavaScript that are executed before the current execution context ends. For example, the code in the then() method of a Promise is a microtask.
  3. Execute macrotasks: Macrotasks are pieces of code in JavaScript that are executed after the current execution context ends. For example, the code inside the setTimeout() function is a macrotask.
  4. Render the page: If any changes occur at any stage of the event loop, the browser will re-render the page to reflect these changes.

The event loop in JS is one of the core mechanisms for implementing asynchronous programming. It ensures that JS can handle asynchronous tasks in a single-threaded environment, maintains the execution order of the code, and avoids blocking.

Event Loop Mechanism

The event loop is part of the JS runtime environment and is responsible for managing the execution stack and the task queue. The basic process of the event loop is as follows:

  1. Execution Stack: During the execution of JavaScript code, an execution stack, also known as the call stack, is formed. When the main thread executes synchronous tasks, it places tasks in the execution stack in order to execute them.
  2. Task Queue: After an asynchronous task is completed, it is placed in the task queue awaiting execution. The task queue is divided into the macrotask queue and the microtask queue.
  3. Event Loop: After the main thread finishes executing the current call stack, it will check whether there are tasks in the microtask queue. If there are, it will execute all tasks in the microtask queue one by one. Then, the main thread will take a task from the macrotask queue and place it in the call stack for execution. This process repeats continuously, forming the event loop.

It can actually be understood as:

Check event queue --> Execute microtasks --> Execute macrotasks --> Render the page

Macrotasks and Microtasks

  • Macrotasks: Include the overall code, setTimeout, setInterval, I/O, UI rendering, etc. They are placed in the macrotask queue to wait for execution.
  • Microtasks: Include Promise callback functions, MutationObserver callback functions, etc. They are placed in the microtask queue to wait for execution.

The execution priority of microtasks is higher than that of macrotasks. To be more precise:

After the tasks in the call stack are executed, before executing the next macrotask, the microtask queue will be checked first. If there are tasks, all tasks in the microtask queue will be executed first. Only after all microtasks are completed will the next macrotask be executed.

Example:

<!DOCTYPE html>
<html lang="en">
  <head>
     
    <meta charset="UTF-8" />
     
    <title>微任务和宏任务的优先级</title>
  </head>
  <body>
     <button onclick="handleClick()">点击我</button> ​  
    <script>
          function handleClick() {
            console.log('点击了按钮');
      ​
            setTimeout(() => {
              console.log('计时器');
            }, 0);
      ​
            Promise.resolve().then(() => {
              console.log('Promise');
            });
          }
        
    </script>
  </body>
</html>

When the user clicks the button, a click event is triggered. The browser executes the corresponding click event handler, which outputs "Button clicked" to the console.

Then, the event handler calls the setTimeout() function, which executes a callback function after 0 milliseconds.

Next, the event handler calls the Promise.resolve() method, which creates a new Promise object and resolves it to a successful state.

Because microtasks have higher priority than macrotasks, the code in the Promise's then method executes before the callback function in the setTimeout() function, and outputs "Promise" to the console.

Finally, after all microtasks have executed, all macrotasks are executed. Therefore, the callback function in the setTimeout() function runs after all microtasks and outputs "Timer" to the console.

It is important to note that the concepts of microtasks and macrotasks only exist in the browser environment. In the Node.js environment, the execution order of the event loop is different.

On-demand Loading in Webpack and Vite

Both Webpack and Vite support code on-demand loading (Code Splitting) and lazy loading, but their implementation methods differ.

On-demand Loading in Webpack

Webpack mainly uses dynamic imports and code splitting strategies to implement on-demand loading.

Dynamic Imports

By using the 'import()' syntax to dynamically import modules in code, Webpack will automatically package the imported modules into a separate chunk file.

import("path/to/module").then((module) => {
  // ...
});

Code Splitting

Webpack also provides some configuration options to optimize code splitting, such as automatically separating third-party library files through the optimization.splitChunks option, or manually separating code using import().

Vite's On-Demand Loading

Vite leverages the dynamic import feature of native ES modules, using the browser's native import syntax for on-demand loading in the development environment, while using Rollup to implement code splitting in the production environment.

ES Module Dynamic Import

Vite uses the browser's native import() syntax to achieve dynamic module imports, thereby enabling on-demand loading.

Efficient Caching

In development mode, Vite provides an efficient module caching mechanism, using the browser cache to avoid unnecessary module reloads. For source code, it uses a negotiated cache, while for dependencies, it uses a strong cache strategy, achieving efficient HMR (hot module replacement).

Pre-Built Dependencies

Vite pre-builds third-party dependencies, improving the efficiency of dynamic imports.

Summary

Both Webpack and Vite support on-demand loading, but the implementation methods and focus areas differ. Webpack relies on its bundling mechanism and the import() syntax to achieve code splitting and lazy loading, suitable for large projects that require complex configuration and optimization. Vite, on the other hand, leverages modern browsers' ES module import features, providing faster development experience and efficient on-demand loading, suitable for modern web projects, especially in development environments. In production builds, Vite optimizes via Rollup to achieve efficient code splitting and loading.

HTTP Cache Control Strategy

HTTP cache control strategy refers to the technique where the client and server work together to store resource copies in the client's cache, reducing repeated requests and improving performance. HTTP cache strategies are generally of two types: strong cache and negotiated cache.

Strong Cache

Controlled by the server, forcing the client to cache resources.

Strong cache refers to the server forcing the client to cache resources by setting the Cache-Control and Expires fields in the HTTP response headers.

Cache-Control

Cache-Control is a more fine-grained cache control mechanism, and it can specify the following attributes:

  • max-age: Specifies the cache duration of the resource, in seconds.
  • s-maxage: Specifies the cache duration of the resource in shared caches, in seconds.
  • public: Specifies that the resource can be cached by all cache proxies.
  • private: Specifies that the resource can only be cached by the client.
  • no-cache: Specifies that the resource cannot be cached.
  • no-store: Specifies that the resource cannot be cached and must be fetched from the server for every request.

Example:

Cache-Control: max-age=3600, public

Expires

Expires The expiration time of the specified resource, in GMT.

Expires: Thu, 01 Dec 2023 16:00:00 GMT

Negotiated Caching

Negotiated caching is determined collaboratively by the client and server to decide whether to cache a resource.

The process of negotiated caching is as follows:

  1. The client sends a request to the server.
  2. The server checks whether the resource is in the cache.
  3. If the resource is in the cache, the server checks whether the resource has expired.
  4. If the resource has not expired, the server returns the resource to the client and sets the Etag field in the response header.
  5. If the resource has expired, the server returns the resource to the client and sets the Last-Modified field in the response header.
  6. After receiving the response, the client checks the Etag or Last-Modified fields.
  7. If the Etag or Last-Modified fields match the resource in the client cache, the client uses the cached resource.
  8. If the Etag or Last-Modified fields do not match the resource in the client cache, the client sends a new request to the server.

Etag

Etag is a unique string used to identify the version of a resource.

Last-Modified

Last-Modified is the last modification time of the resource.

Example:

GET /index.html HTTP/1.1
​
If-None-Match: "1234567890"

Cache Strategy Selection

Forced caching is suitable for the following scenarios:

  • Resources are accessed frequently.
  • The content of the resources does not change often.

Negotiated caching is suitable for the following scenarios:

  • The content of the resources may change frequently.
  • Resources that require precise cache control.

Here are some best practices for cache strategies:

  • Use Cache-Control instead of Expires to control caching.
  • Set a longer cache time for static resources.
  • Set a shorter cache time for dynamic resources.
  • Use Etag or Last-Modified for negotiated caching.
  • Regularly update resources in the cache.

Differences between Vue2's data and Vue3's Ref

Both Vue2 and Vue3's ref are used to store reactive data, but there are the following differences between them.

Usage

  • Vue2: The data option is used to define reactive data in components.
  • Vue3: The ref function is used to create reactive variables.

Scope

  • Vue2: Data in the data option is globally visible in the component instance.
  • Vue3: Variables created by the ref function are visible within the current scope.

Reactivity

  • Vue2: Data in the data option is reactive by default.
  • Vue3: Variables created by the ref function need to be accessed with .value to trigger reactive updates.

Type

  • Vue2: Data in the data option can be of any type.
  • Vue3: Variables created by the ref function can be of any type, but it is recommended to use basic types or simple objects to avoid performance issues.

Default Value

  • Vue2: Data in the data option can have default values set.
  • Vue3: Variables created by the ref function require default values to be explicitly passed in.

Updates

  • Vue2: Data in the data option can be directly updated through this.data.xxx.
  • Vue3: Variables created by the ref function can be updated through .value.

Destruction

  • Vue2: Data in the data option will be destroyed when the component is destroyed.
  • Vue3: Variables created by the ref function need to be manually destroyed; otherwise, it will cause memory leaks.

Differences Between watch and watchEffect in Vue3

Listening Method

  • watch: Can listen to one or multiple reactive data sources and specify a callback function to execute when the data changes.
  • watchEffect: Listens to all reactive data used inside the callback function and re-executes the callback when the data changes.

Callback Function Parameters

  • watch: The callback function receives two parameters, the new value and the old value.
  • watchEffect: The callback function does not receive any parameters.

Execution Timing

  • watch: The callback function is not executed during the component's initial render; it only executes when the data changes.
  • watchEffect: The callback function executes immediately when the component initially renders and executes again when the data changes.

Performance

  • watch: Executes the callback only when the watched data changes, so the performance cost is lower.
  • watchEffect: Re-executes the callback whenever any dependent data changes, so the performance cost is relatively higher.

Usage Scenarios

  • watch: Suitable for situations where precise control over the watched scope is needed and when both new and old values are required.
  • watchEffect: Suitable for situations where there is no need to explicitly define the data to watch and only automatic operations based on data changes are needed.

Summary

  • watch and watchEffect are both functions in Vue 3 used to listen to changes in reactive data.
  • watch is more suitable for situations that require precise control over the listening scope and access to both new and old values.
  • watchEffect is more suitable for situations where there is no need to explicitly define the data to be watched and actions only need to be executed automatically based on data changes.

Using WebSocket

WebSocket is a protocol for full-duplex communication between the client and the server. It can be used in various real-time communication applications, such as chat, games, and data streams.

The usage of WebSocket is as follows:

  1. Establish a WebSocket connection on both the client and server sides.
  2. Send and receive data on both the client and server sides.
  3. Close the WebSocket connection.

The following is a simple example of using WebSocket:

Client-side code:

const socket = new WebSocket('ws://localhost:8080');
​
socket.onopen = () => {
  console.log('WebSocket connection opened');
};
​
socket.onmessage = (event) => {
  console.log('Received message:', event.data);
};
​
socket.onclose = () => {
  console.log('WebSocket connection closed');
};
​
socket.send('Hello, world!');

Server-side code:

import asyncio
import websockets
​
async def handler(websocket, path):
  while True:
    message = await websocket.recv()
    await websocket.send(message)
​
async def main():
  async with websockets.serve(handler, 'localhost', 8080):
    await asyncio.Future()  # run forever
​
if __name__ == '__main__':
  asyncio.run(main())

Other ways to achieve real-time communication without using WebSocket are as follows:

  • HTTP Polling: The client regularly sends HTTP requests to the server to get updates.
  • Long Polling: The client sends an HTTP request to the server and keeps the connection open until the server has updated data.
  • SSE (Server-Sent Events): The server sends a continuous stream of events to the client.
  • Comet: A technique that combines HTTP polling and long polling.

The choice of method depends on the specific application requirements.

  • WebSocket is the best choice for real-time communication because it has low latency and high performance.
  • HTTP polling is a simple and easy choice, but it has higher latency.
  • Long polling can reduce latency but increases server load.
  • SSE and Comet are options between HTTP polling and long polling.

Here are some examples of real-time communication without using WebSocket:

  • HTTP Polling:
function poll() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/updates');
  xhr.onload = () => {
    if (xhr.status === 200) {
      const data = JSON.parse(xhr.responseText);
      // 更新 UI
    }
  };
  xhr.send();
}
​
setInterval(poll, 1000);
  • Long Polling:
function poll() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/updates');
  xhr.onload = () => {
    if (xhr.status === 200) {
      const data = JSON.parse(xhr.responseText);
      // 更新 UI
      poll();
    }
  };
  xhr.send();
}
​
poll();
  • SSE:
<script>
const source = new EventSource('/api/updates');
​
source.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 更新 UI
};
</script>
  • Comet:
function poll() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/updates');
  xhr.onload = () => {
    if (xhr.status === 200) {
      const data = JSON.parse(xhr.responseText);
      // 更新 UI
      if (data.more) {
        poll();
      }
    }
  };
  xhr.send();
}
​
poll();

Advantages of Upgrading a Vue2 Project to Vue3

Performance Improvement

  • Faster Virtual DOM: Vue 3 introduces a rewritten virtual DOM algorithm, which significantly improves rendering speed and memory usage compared to Vue 2.
  • Tree Shaking Support: Vue 3's design allows unused code to be removed through tree shaking, meaning the final bundled files can be smaller and load faster.
  • Fragment, Teleport, Suspense: These new features offer more flexibility and, in some cases, can enhance performance or improve user experience.

Enhanced Composition API

  • Better Logic Reuse and Code Organization: The Composition API makes logic reuse easier and helps maintain code organization and clarity in complex components, especially for large projects or applications.
  • Reactive System Improvements: Vue 3's reactive system is based on Proxy, which not only performs better but can also detect more types of changes (such as array indices and adding/removing object properties).

Type Support and TypeScript Integration

  • Better TypeScript Integration: Vue 3 was designed with TypeScript support from the start, providing improved type inference and integration experience. This allows developers to leverage TypeScript's type safety and increased productivity when working on large projects.

New Component Features

  • Composition API: In addition to providing better code organization, it allows more flexible use of Vue’s reactive system.
  • Teleport Component: Allows parts of a child component template to be “teleported” to other locations in the DOM, which is very useful for handling modals, tooltips, and similar cases.
  • Suspense Component: Offers a new way to manage the loading state of asynchronous components, making the user experience smoother.

Custom Directive API

Vue 3 has improved the custom directive API, making it simpler and more intuitive to use.

Modularization

Vue 3 has a higher degree of modularity, allowing developers to import specific functionalities as needed, which helps reduce the size of the application.

Challenges Brought by the Upgrade

Although Vue 3 brings many improvements, there may also be some challenges during the upgrade process, such as needing to relearn new APIs, migrate existing code, and ensure compatibility with third-party libraries/plugins.

Assessing Whether to Upgrade to Vue 3

Project Requirements

  • Performance Requirements: If the project has high performance demands, considering that Vue 3 offers more optimized rendering performance and smaller bundle sizes, upgrading might be a good choice.
  • Feature Requirements: Vue 3 introduces several new features, such as the Composition API, Teleport, and Suspense. If these new features are particularly helpful in meeting specific project needs, then upgrading may be appropriate.

Team Skills and Learning Curve

  • Adaptability to new technologies: Upgrading to Vue 3 may require team members to learn new concepts and APIs, such as the Composition API. The team needs to assess their own learning abilities and rate of adaptability to new technologies.
  • Use of TypeScript: Vue 3 provides better TypeScript support. If the team is already using or planning to use TypeScript, then upgrading to Vue 3 would be a natural choice.

Ecosystem and Third-party Library Support

  • Plugin and library compatibility: Ensure that the third-party libraries and plugins your project depends on are compatible with Vue 3. Although many libraries have been updated to support Vue 3, some may not have been updated yet.
  • Maturity of tools and ecosystem: Although Vue 3’s ecosystem is growing quickly, it is necessary to evaluate whether the development and debugging tools required are already sufficiently mature and stable.

Project Scale and Complexity

  • New or existing projects: For new projects, using Vue 3 directly may be a good choice, as it allows you to take advantage of all the new features and improvements from the start. For existing projects, the complexity and cost of migration need to be evaluated.
  • Project scale and longevity: For large or long-term projects, investing in upgrading to Vue 3 may bring long-term benefits, especially considering performance optimization and future maintainability.

Time and Cost

  • Upgrade Cost: Assess the time and resource costs during the upgrade process, including learning new APIs, refactoring existing code, testing, and fixing upgrade-related bugs.
  • Long-term Maintenance Cost: Considering that Vue 3 will be the main direction for future development, in the long run, upgrading may help reduce maintenance costs and ensure ongoing project updates.

Front-end Design Patterns

First, it's worth stating that this is just a rough introduction to front-end design patterns. To really elaborate, it could take a lot!

Front-end design patterns refer to general solutions used to solve common problems in front-end development. They help developers improve the maintainability, readability, and reusability of code.

Front-end design patterns can be divided into three major categories:

  • Creational Patterns: Patterns used for creating objects.
  • Structural Patterns: Patterns used for organizing objects.
  • Behavioral Patterns: Patterns used for defining the communication between objects.

Creational Patterns

Creational patterns are used for creating objects. They can help developers create objects more flexibly and decouple the object creation process.

Common creational patterns include:

  • Singleton Pattern: Ensures a class has only one instance.
  • Factory Method Pattern: A factory class that creates an object.
  • Abstract Factory Pattern: A factory class that creates multiple types of objects.
  • Builder Pattern: Breaks down the creation process of a complex object into multiple steps.
  • Prototype Pattern: Creates new objects by cloning existing objects.

Structural Patterns

Structural patterns are used for organizing objects. They help developers combine objects into larger structures and improve code maintainability.

Common structural patterns include:

  • Adapter Pattern: Converts an incompatible interface into a compatible one.
  • Bridge Pattern: Decouples an object's interface and implementation.
  • Composite Pattern: Combines objects into a tree structure.
  • Decorator Pattern: Adds functionality to an object without changing the object itself.
  • Facade Pattern: Provides a simple facade for the interface of a complex system.

Behavioral Patterns

Behavioral patterns are used to define the communication between objects. They help developers improve code scalability and reusability. Common behavioral patterns include:

  • Strategy Pattern: Encapsulates algorithms or behaviors and allows clients to choose different algorithms or behaviors.
  • Observer Pattern: Defines a one-to-many dependency so that when one object's state changes, all objects depending on it are notified.
  • Publish-Subscribe Pattern: A variant of the observer pattern that allows publishers to release messages, which subscribers can subscribe to.
  • Chain of Responsibility Pattern: Connects multiple objects that can handle a request and processes the request one by one.
  • Template Method Pattern: Defines the skeleton of an operation and allows subclasses to redefine certain steps without changing the skeleton structure.
  • Iterator Pattern: Provides a way to traverse elements in a collection.

Application of Front-End Design Patterns

Front-end design patterns can be applied to various front-end development scenarios, such as:

  • Component Development: Design patterns can be used to build maintainable and reusable components.
  • Data Management: Design patterns can be used to manage and manipulate data.
  • User Interaction: Design patterns can be used to design user interaction interfaces.
  • Performance Optimization: Design patterns can be used to optimize front-end performance.

Discussion

Support the author
Previous post

Interviewer: Tell Me Every Array Method—Just the Basics

Next post

Interviewer: Explain Set and Map

LMX BlogWeb · TypeScript · Vue · Node.js
Crafted by
@LMXZQQ
Build
unavailable
Date
2026-10-30
Articles
29
Deployed on
Nginx
Source code
GitHub
License
MIT License
Typeface
Geist
Stack
  • next@16.4.3
  • react@19.3.1
  • @base-ui/react@1.8.0
  • shadcn@4.12.0
  • tailwindcss@4.3.3
Analytics
  • OpenPanel
  • Google Analytics
For agents
  • llms.txt
  • index.md
Inspired by
  1. Tailwind CSS
  2. shadcn/ui
  3. Vercel
  4. Apple
  5. Evil Charts
  6. Devouring Details
  7. Skiper UI
  8. Making Software
  9. shadcncraft
Approved by
静水流深 / LMX
1602
Command Palette

Search for a command to run...