87.場景面試之大數運算:超過js中number最大值的數怎么處理
在 JavaScript 中,Number.MAX_SAFE_INTEGER
(即 2^53 - 1
,即 9007199254740991
)是能被安全表示的最大整數。超過此值時,普通的 Number
類型會出現精度丟失問題,導致運算結果不準確。
如何處理大數運算?
JavaScript 提供了幾種處理大數的方法:
1. 使用 BigInt
(ES11 引入,適用于整數運算)
BigInt
是 JavaScript 原生支持的大整數類型,適用于超過 Number.MAX_SAFE_INTEGER
的整數運算。
const bigNum1 = BigInt("9007199254740991000000");
const bigNum2 = BigInt("123456789123456789");console.log(bigNum1 + bigNum2); // 9007199378197780123456n
console.log(bigNum1 * bigNum2); // 正確計算超大數相乘
注意:
BigInt
不能與Number
直接混合運算,必須轉換。Math
庫不支持BigInt
,例如Math.sqrt(BigInt(16))
會報錯。
2. 使用 big.js
、bignumber.js
、decimal.js
等第三方庫(適用于小數運算)
BigInt
只適用于整數,對于大浮點數運算,可以使用 bignumber.js
這樣的庫:
import BigNumber from "bignumber.js";const num1 = new BigNumber("9007199254740991000000");
const num2 = new BigNumber("123456789.123456789");console.log(num1.plus(num2).toString()); // 精確計算大數加法
console.log(num1.times(num2).toString()); // 精確計算大數乘法
優勢:
- 適用于浮點數運算,避免 JavaScript 內置
Number
的精度問題。 - 計算結果可以轉換為字符串,避免
BigInt
只支持整數的問題。
3. 使用字符串手寫大數運算(適用于特定場景)
如果第三方庫不適用,也可以手寫字符串處理大數:
function addBigNumbers(a, b) {let res = "";let carry = 0;let i = a.length - 1, j = b.length - 1;while (i >= 0 || j >= 0 || carry) {let sum = (i >= 0 ? +a[i] : 0) + (j >= 0 ? +b[j] : 0) + carry;carry = Math.floor(sum / 10);res = (sum % 10) + res;i--; j--;}return res;
}console.log(addBigNumbers("9007199254740991", "123456789123456789"));
// 輸出: "132463988378197780"
適用場景:
- 僅涉及加法、乘法等基本運算。
- 不想引入
BigInt
或外部庫。
總結
方案 | 適用場景 | 優勢 | 限制 |
---|---|---|---|
BigInt | 超大整數運算 | 內置支持,性能好 | 僅支持整數,無法處理小數 |
big.js / bignumber.js | 超大整數 & 浮點數運算 | 兼容小數計算,API 強大 | 需要引入庫 |
字符串模擬計算 | 自定義大數計算 | 適用于前端特殊場景 | 實現復雜,性能較低 |
面試回答時,最好結合具體業務場景推薦方案,比如:
- 如果是整數運算,可以直接使用
BigInt
。 - 如果涉及小數運算,建議使用
bignumber.js
。 - 如果環境不允許引入第三方庫,可使用字符串模擬計算。
這樣既展現了基礎知識,又體現了實踐經驗,能夠給面試官留下不錯的印象! 🚀
88.場景面試之大文件上傳(上下)
回答怎么解決,造個開發遇到的問題然后如何解決
一、問題場景描述
假設在開發一個云盤系統時,用戶需要上傳 10GB 以上的大文件,但在實際測試中遇到以下問題:
- 上傳中斷:網絡波動導致上傳失敗后需重新上傳整個文件。
- 內存溢出:前端直接讀取整個文件導致瀏覽器崩潰。
- 進度反饋缺失:用戶無法感知上傳進度。
- 服務器壓力大:大文件直接上傳占用帶寬且易超時。
二、解決方案:分片上傳 + 斷點續傳
1. 前端核心步驟
a. 文件分片(File Chunking)
-
技術點:使用
Blob.slice()
將文件切割為固定大小(如 5MB)的分片。 -
代碼示例:
function createChunks(file, chunkSize = 5 * 1024 * 1024) {const chunks = [];let start = 0;while (start < file.size) {chunks.push(file.slice(start, start + chunkSize));start += chunkSize;}return chunks; }
b. 生成文件唯一標識(Hash)
-
目的:用于斷點續傳時識別文件。
-
優化:使用 Web Worker + SparkMD5 計算文件 Hash,避免主線程阻塞。
// 在 Web Worker 中計算 Hash self.importScripts('spark-md5.min.js'); self.onmessage = async (e) => {const { chunks } = e.data;const spark = new self.SparkMD5.ArrayBuffer();for (const chunk of chunks) {spark.append(await chunk.arrayBuffer());}self.postMessage(spark.end()); };
c. 上傳分片(并發控制)
-
并發控制:限制同時上傳的分片數(如 3 個并行)。
-
斷點續傳:上傳前向服務端查詢已上傳的分片列表。
async function uploadChunks(chunks, fileHash) {const uploaded = await checkExistingChunks(fileHash); // 查詢已上傳分片const pool = new ConcurrentPool(3); // 自定義并發池chunks.forEach((chunk, index) => {if (!uploaded.includes(index)) {pool.addTask(() => uploadChunk(chunk, index, fileHash));}});await pool.run(); }
d. 進度反饋
-
監聽每個分片進度:使用 Axios 的
onUploadProgress
。 -
匯總總進度:
let uploadedSize = 0; const totalSize = file.size; chunks.forEach((chunk, index) => {uploadChunk(chunk, index).then(() => {uploadedSize += chunk.size;updateProgress(uploadedSize / totalSize * 100);}); });
2. 服務端核心步驟
a. 接收分片
- 接口設計:
POST /upload-chunk
,接收分片內容、文件 Hash、分片索引。 - 存儲分片:將分片保存到臨時目錄(按文件 Hash 分類存儲)。
b. 合并分片
-
接口設計:
POST /merge-chunks
,根據文件 Hash 合并所有分片。 -
合并邏輯(Node.js 示例):
function mergeChunks(fileHash, fileName) {const chunkDir = path.join(UPLOAD_DIR, fileHash);const chunks = fs.readdirSync(chunkDir).sort((a, b) => a - b);const filePath = path.join(UPLOAD_DIR, fileName);const writeStream = fs.createWriteStream(filePath);chunks.forEach(chunk => {const chunkPath = path.join(chunkDir, chunk);writeStream.write(fs.readFileSync(chunkPath));fs.unlinkSync(chunkPath); // 刪除分片});writeStream.end(); }
c. 支持斷點續傳
- 記錄已上傳分片:使用 Redis 或數據庫記錄文件 Hash 對應的已上傳分片索引。
三、解決開發中的典型問題
問題 1:分片上傳后合并失敗
-
原因:分片順序錯亂或丟失。
-
解決方案:
- 服務端按索引順序合并分片。
- 前端上傳時確保分片索引連續。
問題 2:Hash 計算卡頓
- 原因:大文件 Hash 計算阻塞主線程。
- 解決方案:使用 Web Worker 異步計算。
問題 3:上傳進度不準確
- 原因:未考慮分片上傳失敗重試。
- 解決方案:在進度計算中排除失敗分片,重試成功后再累加。
四、優化點
- 動態分片大小:根據網絡質量調整分片大小(弱網時減小分片)。
- 秒傳功能:服務端校驗文件 Hash 已存在時直接返回 URL。
- 壓縮分片:前端對分片進行 Gzip 壓縮(需權衡 CPU 占用)。
- 分片哈希校驗:上傳分片時附帶分片 Hash,服務端校驗完整性。
五、代碼片段示例(前端并發控制)
class ConcurrentPool {constructor(concurrency) {this.concurrency = concurrency;this.queue = [];this.running = 0;}addTask(task) {this.queue.push(task);}async run() {while (this.queue.length > 0) {if (this.running < this.concurrency) {const task = this.queue.shift();this.running++;task().finally(() => {this.running--;this.run();});} else {await new Promise(resolve => setTimeout(resolve, 100));}}}
}
六、總結
通過分片上傳、斷點續傳、并發控制、進度反饋等技術組合,可有效解決大文件上傳的穩定性、性能和用戶體驗問題。核心在于前端合理拆分任務,服務端高效管理分片,同時結合業務需求優化傳輸策略。
89.場景面試之如果你是美團電影的,請問怎么實現一個電影票選座功能
簡要總結:
-
核心功能:
- 可視化座位布局(Canvas/SVG + 動態數據渲染)。
- 實時選座狀態同步(WebSocket + 樂觀鎖沖突處理)。
- 限制選座邏輯(最大數量、相鄰推薦)。
-
技術實現:
- 前端:狀態管理(Redux/Zustand)、交互優化(高亮/動畫)。
- 后端:座位庫存管理(Redis鎖+事務)、API設計(查詢/鎖定/合并)。
-
難點解決:
- 并發沖突:服務端原子化操作 + 前端樂觀更新回滾。
- 性能瓶頸:虛擬滾動/Canvas分塊渲染。
- 弱網兼容:本地緩存 + 狀態同步重試。
一句話總結:通過實時通信、原子化狀態管理和分層渲染優化,實現高并發下的流暢選座體驗,核心解決數據一致性與性能問題。
89.場景面試之函數編程思想的理解
回答要點總結(函數式編程思想)
1. 什么是函數式編程?
函數式編程(FP)是一種以 “純函數”、“不可變數據” 為核心的編程范式,強調 聲明式編程,提高代碼的可讀性、可復用性和可測試性。
2. 核心概念
- 純函數(Pure Function) :相同輸入必定返回相同輸出,無副作用。
- 不可變數據(Immutability) :不直接修改數據,而是返回新數據。
- 高階函數(Higher-Order Function) :函數可以接收函數作為參數或返回函數(如
map
、filter
)。 - 柯里化(Currying) :將多參數函數拆成多個單參數函數,提高復用性。
- 函數組合(Composition) :把小函數組合成大函數,提高代碼整潔度。
3. 對比面向對象編程(OOP)
函數式編程(FP) | 面向對象編程(OOP) | |
---|---|---|
狀態管理 | 不修改狀態,返回新狀態 | 修改對象內部狀態 |
可測試性 | 高,可預測 | 低,狀態依賴上下文 |
適用場景 | 數據流轉換(如 Redux) | 復雜對象建模(如 UI 組件) |
4. React 中的應用
- React 組件:函數組件 + Hooks 遵循 FP 思想(如
useState
)。 - Redux:Reducer 必須是純函數,不能修改
state
。 - 高階組件(HOC) :類似高階函數,提高組件復用性。
5. 典型面試回答示例
“函數式編程是一種編程范式,它強調純函數和不可變數據,減少副作用,提高代碼可維護性。在 React 中,函數式組件、Hooks、Redux 都體現了 FP 思想,例如 useState
遵循不可變性,Redux Reducer 也是純函數。”
90.場景面試之前端水印功能的了解
簡要總結:
-
實現方式:
- CSS 背景:通過重復平鋪背景圖或漸變生成水印,但易被刪除。
- Canvas/SVG:動態生成含文字/logo的水印圖,轉為Base64設為背景。
- DOM 覆蓋:用絕對定位的div覆蓋全屏,設置
pointer-events: none
避免遮擋操作。
-
動態水印:
- 注入用戶信息(如ID、手機號),通過前端JS生成個性化水印。
-
防刪除:
- MutationObserver:監聽水印DOM變化,被刪時重新插入。
- Shadow DOM:隱藏水印元素結構,增加刪除難度。
-
注意事項:
- 性能優化(避免頻繁重繪)。
- 兼容打印場景(
@media print
樣式適配)。 - 后端校驗(防止前端水印被繞過,關鍵數據需后端疊加)。
核心難點:平衡防護強度與性能,純前端無法絕對防篡改,需結合后端驗證。
91.場景面試之設計一個全站請求耗時統計工具
全站請求耗時統計工具設計
1. 需求分析
- 統計所有 HTTP 請求的耗時,包括 API 請求、靜態資源加載等。
- 計算平均請求耗時、最大耗時、最小耗時等關鍵指標。
- 兼容 fetch、XMLHttpRequest(XHR)、Axios 等不同請求方式。
- 可視化展示統計數據,或者上報到監控系統。
2. 方案設計
(1)劫持全局 fetch
和 XMLHttpRequest
在 window
作用域下攔截 HTTP 請求,記錄開始和結束時間,計算耗時。
(2)存儲請求數據
- 采用 數組 存儲每次請求的耗時數據。
- 計算 平均耗時、最大/最小耗時。
- 采用 定時器批量上報 避免性能開銷。
(3)數據上報
- 通過 定時器或閾值觸發 發送統計數據到后端(可結合
localStorage
做數據緩存)。 - 可視化:用 Web Console 或 監控系統(如 Grafana、Sentry) 展示數據。
3. 代碼實現
class RequestMonitor {constructor() {this.requests = [];this.hookFetch();this.hookXHR();}hookFetch() {const originalFetch = window.fetch;window.fetch = async (...args) => {const startTime = performance.now();const response = await originalFetch(...args);const endTime = performance.now();this.logRequest(args[0], endTime - startTime);return response;};}hookXHR() {const originalOpen = XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open = function (...args) {this._url = args[1]; // 記錄請求 URLreturn originalOpen.apply(this, args);};const originalSend = XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send = function (...args) {const startTime = performance.now();this.addEventListener("loadend", () => {const endTime = performance.now();requestMonitor.logRequest(this._url, endTime - startTime);});return originalSend.apply(this, args);};}logRequest(url, duration) {this.requests.push(duration);console.log(`[Request Monitor] ${url} 耗時: ${duration.toFixed(2)}ms`);}getStats() {if (this.requests.length === 0) return { avg: 0, max: 0, min: 0 };const total = this.requests.reduce((sum, time) => sum + time, 0);return {avg: (total / this.requests.length).toFixed(2),max: Math.max(...this.requests).toFixed(2),min: Math.min(...this.requests).toFixed(2),};}
}// 啟動請求監控
const requestMonitor = new RequestMonitor();
4. 統計 & 數據展示
調用 requestMonitor.getStats()
獲取全站請求統計數據
console.log("全站請求耗時統計:", requestMonitor.getStats());
5. 擴展優化
? 支持 Axios
import axios from "axios";axios.interceptors.request.use(config => {config.meta = { startTime: performance.now() };return config;
});axios.interceptors.response.use(response => {const duration = performance.now() - response.config.meta.startTime;requestMonitor.logRequest(response.config.url, duration);return response;
});
? 上報后端
setInterval(() => {const stats = requestMonitor.getStats();navigator.sendBeacon("/api/log", JSON.stringify(stats));
}, 60000); // 每分鐘上報
? 前端可視化 可用 Chart.js / ECharts 繪制請求耗時趨勢圖。
6. 總結
功能點 | 實現方式 |
---|---|
攔截請求 | fetch + XMLHttpRequest + Axios 攔截器 |
計算耗時 | performance.now() 計算請求耗時 |
數據存儲 | 數組存儲請求時間,并計算平均/最大/最小耗時 |
數據上報 | setInterval + navigator.sendBeacon() 定時上報 |
可視化 | console.log + Chart.js 展示數據 |
92.場景面試之深度SEO優化
深度 SEO 優化方案(前端視角)
1. SEO 基礎概念
SEO(Search Engine Optimization)是搜索引擎優化的簡稱,目標是 提高網站在搜索引擎中的排名,增加自然流量。主要分為:
- 白帽 SEO(合規優化,如結構化數據、關鍵詞優化)
- 黑帽 SEO(作弊手段,如關鍵詞堆砌,容易被搜索引擎懲罰)
- 灰帽 SEO(介于兩者之間,如購買外鏈)
2. 深度 SEO 優化方案(前端視角)
(1)HTML 結構優化
? 語義化 HTML
- 使用
<header>
<article>
<section>
<nav>
等語義化標簽,讓搜索引擎更容易理解網頁內容。
? Title & Meta 標簽
<title>React 18 Suspense 新特性解析</title>
<meta name="description" content="本篇文章詳細解析 React 18 的 Suspense 新特性,幫助開發者深入理解并優化應用性能。" />
<meta name="keywords" content="React, React18, Suspense, 前端, JavaScript" />
title
:簡短、包含主要關鍵詞。meta description
:控制搜索引擎摘要,提高點擊率(CTR)。meta keywords
:部分搜索引擎已不使用,但仍可提供參考。
? H1-H6 結構
<h1>React 18 Suspense 新特性解析</h1>
<h2>1. 什么是 Suspense?</h2>
<h2>2. 如何使用 Suspense?</h2>
- H1 只能有一個,確保核心主題明確。
- H2-H6 作為層級結構,幫助搜索引擎理解文章內容。
? 圖片優化
<img src="suspense-example.png" alt="React 18 Suspense 示例代碼" />
- 使用
alt
屬性,描述圖片內容,提升無障礙體驗 & SEO。 - 使用
loading="lazy"
,實現懶加載優化性能。
? 結構化數據(Schema.org)
- 增強搜索結果,支持 富文本摘要。
<script type="application/ld+json">
{"@context": "https://schema.org","@type": "Article","headline": "React 18 Suspense 新特性解析","author": { "@type": "Person", "name": "張三" },"datePublished": "2025-03-04","publisher": { "@type": "Organization", "name": "前端社區" }
}
</script>
(2)前端技術優化
? SSR(服務器端渲染)
- React + Next.js 或 Nuxt.js(Vue),讓搜索引擎爬蟲直接獲取完整 HTML 內容,提高抓取效率。
? 靜態站點生成(SSG)
- 適用于內容不經常變動的網站,例如博客、文檔站點(Gatsby.js、Next.js)。
? 懶加載與代碼拆分
- 避免首屏加載過大,提高首屏加載速度(搜索引擎更喜歡快的網站)。
const LazyComponent = React.lazy(() => import('./LazyComponent'));
(3)性能優化
? 頁面加載速度
- 使用
Lighthouse
分析 Core Web Vitals(核心網頁指標)。 - 減少阻塞渲染的 JS/CSS
<link rel="preload" href="style.css" as="style">
- 啟用 Gzip / Brotli 壓縮 及 HTTP/2。
- 使用 CDN(如 Cloudflare)優化靜態資源加載。
? 圖片優化
- WebP 格式 替代 PNG/JPG,提升加載速度。
- 使用
srcset
提供不同分辨率圖片。
<img src="image.jpg" srcset="image-2x.jpg 2x, image-3x.jpg 3x" />
? 減少不必要的重定向
- 避免 多個 301/302 跳轉,影響 SEO 排名。
(4)移動端優化
? 響應式設計
- 使用
meta viewport
,適配移動端。
<meta name="viewport" content="width=device-width, initial-scale=1">
? PWA(漸進式 Web 應用)
- 提供離線緩存、首頁安裝功能,提高用戶體驗。
(5)外鏈與內部鏈接優化
? 合理的 URL 結構
- URL 短而清晰,如:
? /article?id=12345
? /react-18-suspense
? 內鏈優化
- 文章內部使用錨點鏈接,提升爬取效率。
<a href="/react-18-suspense">深入了解 React 18 Suspense</a>
? 外鏈策略
- 提供權威性高的外鏈(如 MDN、Google 開發者文檔)。
- 處理 死鏈 404(使用
robots.txt
或301 重定向
)。
(6)SEO 監控與優化
? Sitemap.xml
<url><loc>https://www.example.com/react-18-suspense</loc><lastmod>2025-03-04</lastmod><priority>0.8</priority>
</url>
- 讓搜索引擎更快索引你的站點。
? robots.txt
User-agent: *
Disallow: /admin/
Allow: /
- 阻止搜索引擎爬取無關頁面(如后臺管理系統)。
? Google Search Console & 百度搜索資源平臺
- 提交
Sitemap.xml
讓爬蟲更快發現新內容。 - 監測 抓取錯誤 & 索引情況。
? Lighthouse 測試
npx lighthouse https://www.example.com --view
- 通過 Chrome DevTools 進行性能 & SEO 評分分析。
7. 總結
優化點 | 實現方式 |
---|---|
HTML 結構 | 語義化標簽、Title、Meta、H1-H6、Alt 圖片 |
技術選型 | SSR(Next.js)、SSG(Gatsby)、靜態優化 |
性能優化 | 懶加載、CDN、WebP、代碼拆分 |
移動端適配 | 響應式設計、PWA |
外鏈 & 內鏈 | 清晰 URL、站內站外鏈接優化 |
SEO 監控 | Sitemap、robots.txt、Google Search Console |
這樣回答既 系統全面,又 有前端落地方案,面試官肯定會滿意!🔥
93.場景面試之圖片性能優化的方案
前端圖片性能優化方案
1. 選擇合適的圖片格式
-
WebP/AVIF:現代格式,壓縮率高,支持透明和動畫。使用
<picture>
標簽提供回退:<picture><source srcset="image.avif" type="image/avif"><source srcset="image.webp" type="image/webp"><img src="image.jpg" alt="示例"> </picture>
-
SVG:矢量圖形,適合圖標和簡單圖形,縮放無損。
2. 圖片壓縮與優化
- 工具壓縮:使用工具(如 Squoosh、TinyPNG)降低文件大小。
- 質量取舍:調整壓縮率(如 JPEG 質量設為 60-80%)。
3. 響應式圖片加載
-
srcset
與sizes
:按設備分辨率/視口寬度加載合適尺寸:<img src="small.jpg"srcset="medium.jpg 1000w, large.jpg 2000w"sizes="(max-width: 600px) 100vw, 50vw">
4. 懶加載技術
-
原生屬性:
loading="lazy"
(兼容現代瀏覽器):<img src="placeholder.jpg" data-src="image.jpg" loading="lazy">
-
Intersection Observer API:動態加載可視區域圖片:
const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) {const img = entry.target;img.src = img.dataset.src;observer.unobserve(img);}}); }); document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
5. CDN 與緩存策略
- CDN 加速:通過全球節點分發,減少延遲。
- 緩存控制:設置
Cache-Control: max-age=31536000
并添加哈希文件名(如image-abc123.jpg
)實現長期緩存。
6. 減少 HTTP 請求
-
HTTP/2 多路復用:替代雪碧圖,并行加載小文件。
-
內聯小圖:Base64 編碼(適用于 <1KB 圖片):
.icon {background: url(data:image/png;base64,...); }
7. 漸進式加載與占位符
- 漸進式 JPEG:逐步渲染,提升感知速度。
- LQIP (低質量占位符) :先加載模糊縮略圖,再替換為高清圖。
8. 替代方案與高級優化
-
CSS/SVG 替代效果:陰影、漸變、圖標字體減少圖片依賴。
-
視頻替代 GIF:使用 MP4 格式(體積減少 80%+):
<video autoplay loop muted playsinline><source src="animation.mp4" type="video/mp4"> </video>
9. 預加載關鍵資源
-
<link rel="preload">
:提前加載首屏重要圖片:<link rel="preload" href="hero-image.jpg" as="image">
10. 監控與自動化
- 性能工具:Lighthouse、WebPageTest 分析優化點。
- 自動化處理:構建工具(Webpack)集成
image-webpack-loader
自動壓縮。
總結
綜合應用格式選擇、懶加載、CDN 和響應式設計,平衡質量與性能。優先優化首屏資源,持續監控調整策略。
94.場景面試之1000w行的表格你如何渲染,性能問題,那么多的dom節點怎么精簡
前端渲染1000萬行表格的優化方案
1. 核心問題分析
- DOM 數量爆炸:1000萬行直接渲染會導致內存占用過高(約 2GB+),頁面崩潰。
- 渲染性能差:DOM 操作阻塞主線程,滾動卡頓。
- 交互延遲:事件監聽器過多,響應緩慢。
2. 核心解決方案:虛擬滾動(Virtual Scrolling)
原理:僅渲染可視區域內的行,動態替換內容,保持 DOM 節點數恒定(如 50-100個)。
實現步驟:
-
計算可視區域:
- 獲取容器高度(如 600px)和行高(如 30px)→ 每屏顯示 20 行。
-
監聽滾動事件:
- 根據滾動位置計算起始索引(
startIndex = Math.floor(scrollTop / rowHeight)
)。
- 根據滾動位置計算起始索引(
-
動態渲染數據:
- 截取
data.slice(startIndex, startIndex + 20)
進行渲染。
- 截取
-
占位元素撐開滾動條:
- 設置總高度為
totalHeight = rowHeight * 1e7
,模擬完整滾動范圍。
- 設置總高度為
代碼示例:
const container = document.getElementById('table');
const rowHeight = 30;
let startIndex = 0;function renderVisibleRows() {const scrollTop = container.scrollTop;startIndex = Math.floor(scrollTop / rowHeight);const endIndex = startIndex + Math.ceil(container.clientHeight / rowHeight);// 清空現有行container.innerHTML = '';// 添加可視行for (let i = startIndex; i <= endIndex; i++) {const row = document.createElement('div');row.style.height = `${rowHeight}px`;row.textContent = `Row ${i + 1}`;container.appendChild(row);}// 設置占位高度container.style.height = `${rowHeight * 1e7}px`;
}container.addEventListener('scroll', renderVisibleRows);
renderVisibleRows(); // 初始渲染
3. 性能優化進階
a. 減少 DOM 復雜度
- 簡化結構:用
<div>
替代<table>
,避免瀏覽器重排開銷。 - 復用 DOM 節點:池化已創建的 DOM 元素,減少創建/銷毀開銷。
b. 異步渲染分塊
-
使用
requestAnimationFrame
或setTimeout
分塊更新,避免阻塞主線程:function chunkedRender() {let i = 0;function renderChunk() {for (let j = 0; j < 10; j++) {if (i >= data.length) return;// 渲染第 i 行i++;}requestAnimationFrame(renderChunk);}renderChunk(); }
c. 高效數據存儲
- 二進制數據:使用
ArrayBuffer
或TypedArray
存儲數值型數據,減少內存占用。 - 按需加載:通過 WebSocket 或分頁 API 動態加載數據,避免一次性加載 1000 萬條。
d. 禁用高耗能操作
-
避免在行元素上綁定獨立事件監聽器,改用事件委托:
container.addEventListener('click', (e) => {const row = e.target.closest('.row');if (row) handleRowClick(row.dataset.id); });
4. 替代方案:Canvas 渲染
適用場景:純展示型表格,交互需求少。
優勢:
- 數萬行數據流暢渲染(直接繪制,無 DOM 開銷)。
- 支持復雜視覺效果(漸變、動畫)。
實現思路:
- 繪制表頭、表格線。
- 根據滾動位置計算渲染的數據范圍。
- 使用
canvas.drawText()
繪制文本內容。
限制:
- 文本選擇、點擊交互需手動實現(通過坐標計算命中區域)。
5. 使用現成庫加速開發
-
React 生態:
react-window
:支持虛擬滾動列表/表格。react-virtualized
:高級功能(動態行高、緩存)。
-
Vue 生態:
vue-virtual-scroller
:輕量級虛擬滾動。
-
原生 JS 庫:
lit-virtualizer
(Polymer 團隊)。
6. 極端優化手段
- Web Worker 預處理:將排序/過濾操作移至 Worker 線程。
- GPU 加速:對固定部分使用
transform: translateZ(0)
觸發硬件加速。 - 增量渲染:優先渲染首屏,逐步加載剩余數據。
總結
方案 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
虛擬滾動 | 高交互需求(編輯、復雜樣式) | 平衡性能與功能 | 實現復雜度較高 |
Canvas 渲染 | 純展示、超大數據量(>100萬行) | 極致性能,無 DOM 限制 | 交互實現復雜 |
分頁/懶加載 | 非實時性需求 | 簡單易實現 | 用戶體驗不連貫 |
核心原則:
- 按需渲染:絕不在同一時間操作超過 1000 個 DOM 節點。
- 減少重排/重繪:使用絕對定位、CSS Transform 等優化渲染。
- 內存管理:及時銷毀不可見數據,避免內存泄漏。
95.場景面試之怎么實現前端頁面截圖
場景面試:如何實現前端頁面截圖
在面試中,回答如何實現前端頁面截圖,需要結合 不同的應用場景 和 可行方案,并說明 技術選型的理由。
1. 需求分析
前端頁面截圖通常涉及以下幾種場景:
- 用戶截取當前可見區域
- 用戶截取整個網頁(滾動截圖)
- 截取指定 DOM 元素
- 下載截圖并分享
- 截圖后進行編輯(如裁剪、標注)
2. 方案選擇
方法 1:使用 html2canvas
(適用于可見區域 & DOM 截圖)
? 適用場景:
- 截取 可見區域 或 指定 DOM 元素
- 適用于 現代瀏覽器
- 純前端實現,不依賴服務器
🔧 實現步驟:
- 安裝
html2canvas
npm install html2canvas
- 代碼實現
import html2canvas from "html2canvas";const captureScreenshot = async () => {const element = document.getElementById("captureArea"); // 選擇截圖區域const canvas = await html2canvas(element, {useCORS: true, // 解決跨域圖片問題backgroundColor: null, // 透明背景});const imgURL = canvas.toDataURL("image/png"); // 轉換為 base64 圖片const link = document.createElement("a");link.href = imgURL;link.download = "screenshot.png"; // 下載截圖link.click();
};
📝 優缺點:
優勢 | 劣勢 |
---|---|
純前端實現,無需后端支持 | 不支持跨域圖片(需要 useCORS: true ) |
可截取指定 DOM 元素 | 無法截取 iframe 、視頻等 |
適用于用戶可見區域 | 滾動截圖支持不完善 |
方法 2:使用 dom-to-image
(改進 html2canvas
,支持更多特性)
? 適用場景:
- 需要更精準的 DOM 截圖
- 支持 SVG、字體嵌入
- 適用于 動態內容
🔧 實現代碼
import domtoimage from 'dom-to-image';const captureDomImage = () => {const node = document.getElementById("captureArea");domtoimage.toPng(node).then(dataUrl => {const link = document.createElement("a");link.href = dataUrl;link.download = "screenshot.png";link.click();}).catch(error => console.error("截圖失敗:", error));
};
📝 優缺點:
優勢 | 劣勢 |
---|---|
比 html2canvas 兼容性更好 | 仍然無法截圖 iframe 、視頻 |
支持 SVG、Web 字體 | 性能可能比 html2canvas 略慢 |
方法 3:使用 puppeteer
(適用于整頁截圖,服務器端渲染)
? 適用場景:
- 后臺批量生成網頁截圖
- 支持滾動截圖
- 可截圖
iframe
和跨域內容
🔧 實現步驟
- 安裝 Puppeteer
npm install puppeteer
- Node.js 代碼
const puppeteer = require("puppeteer");(async () => {const browser = await puppeteer.launch();const page = await browser.newPage();await page.goto("https://example.com", { waitUntil: "networkidle2" });// 截取整個頁面await page.screenshot({ path: "screenshot.png", fullPage: true });await browser.close();
})();
📝 優缺點:
優勢 | 劣勢 |
---|---|
可截取整頁,支持滾動 | 需要服務器支持(Node.js) |
支持 iframe 、視頻、跨域 | 不能直接在前端運行 |
方法 4:使用 canvas
結合 drawImage()
(適用于視頻或 iframe
截圖)
? 適用場景:
- 需要截取
iframe
或 視頻 - 適用于 動態內容截圖
🔧 實現代碼
const captureVideoFrame = (videoElement) => {const canvas = document.createElement("canvas");canvas.width = videoElement.videoWidth;canvas.height = videoElement.videoHeight;const ctx = canvas.getContext("2d");ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);const imgURL = canvas.toDataURL("image/png");return imgURL;
};const video = document.querySelector("video");
const screenshot = captureVideoFrame(video);
console.log("截圖 Base64:", screenshot);
📝 優缺點:
優勢 | 劣勢 |
---|---|
可截取 video 或 iframe | 只能截取當前幀 |
適用于動態內容 | 需要用戶交互(如 play() 后再截圖) |
3. 選型對比
方案 | 適用場景 | 支持滾動截圖 | 支持 iframe | 依賴 |
---|---|---|---|---|
html2canvas | 可見區域、DOM 截圖 | ? | ? | 純前端 |
dom-to-image | 更精準的 DOM 截圖 | ? | ? | 純前端 |
puppeteer | 整頁截圖、后臺爬蟲 | ? | ? | Node.js 服務器 |
canvas + drawImage() | 視頻截圖 | ? | ? | 純前端 |
4. 總結
最佳方案
- 普通網頁截圖(可見區域) →
html2canvas
/dom-to-image
- 完整網頁截圖(包括滾動) →
puppeteer
- 視頻 /
iframe
截圖 →canvas.drawImage()
面試回答時,可以結合具體業務場景,提出最合適的解決方案,同時說明 優缺點 和 技術選型依據,這樣更顯得專業!🔥
96.場景面試之移動端適配問題如何解決
場景面試:移動端適配問題如何解決?
在前端面試中,針對移動端適配問題,最佳的回答方式是 先分析適配的常見問題,再 提出不同的解決方案,最后 結合具體場景推薦最佳方案。
1. 移動端適配的常見問題
- 設備屏幕尺寸多樣化(不同設備寬高比、像素密度不同)
- 不同 DPR(設備像素比) (如 Retina 屏,1px 在高分屏上可能占多個物理像素)
- 不同系統、瀏覽器的默認樣式(iOS/Android/Chrome/Safari/微信瀏覽器差異)
- 字體、圖片模糊問題(高分屏需要更高清的圖片資源)
- 觸摸事件與鼠標事件的差異(點擊延遲、滑動優化)
2. 適配方案
針對以上問題,常見的適配方案有以下幾種:
方案 1:使用 viewport
進行屏幕縮放
在 <head>
中添加 meta viewport
控制視口:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
? 優點:
- 控制視口寬度,避免縮放影響布局
user-scalable=no
防止用戶縮放頁面
? 缺點:
- 不能解決不同設備
px
值不一樣的問題
方案 2:CSS 響應式布局(媒體查詢)
使用 @media
適配不同屏幕:
@media screen and (max-width: 768px) {body {font-size: 14px;}
}@media screen and (max-width: 375px) {body {font-size: 12px;}
}
? 優點:
- 適用于各種尺寸設備
- 代碼易讀,適用于
rem
/em
配合viewport
? 缺點:
- 需要手動編寫多套樣式,維護成本較高
方案 3:使用 rem
+ vw
進行動態適配
使用 rem
(配合 html
動態 font-size
)
- 設置
html
根字體大小
function setRem() {const baseSize = 16;const scale = document.documentElement.clientWidth / 375; // 以 iPhone 6(375px)為基準document.documentElement.style.fontSize = `${baseSize * scale}px`;
}
setRem();
window.onresize = setRem; // 監聽窗口變化
- 使用
rem
進行布局
.container {width: 10rem; /* 適配不同設備 */
}
使用 vw
(CSS 直接適配)
.container {width: 50vw; /* 視口寬度的 50% */
}
? 優點:
rem
適用于不同屏幕px
變化vw
適用于 全屏自適應布局
? 缺點:
rem
需要動態計算font-size
vw
在小屏幕下可能導致內容過大
方案 4:使用 flex
和 grid
進行自適應布局
使用 flex
可以適配不同屏幕:
.container {display: flex;flex-wrap: wrap;justify-content: space-between;
}
? 優點:
- 適用于不同屏幕
- 不需要手動指定
width
,自適應效果好
? 缺點:
- 僅適用于 容器內部元素布局
方案 5:使用 圖片
和 文字
的 DPR
適配
高清圖片
@media screen and (-webkit-min-device-pixel-ratio: 2),screen and (min-resolution: 192dpi) {.logo {background-image: url("logo@2x.png");}
}
CSS image-set
.logo {background-image: image-set("logo.png" 1x,"logo@2x.png" 2x,"logo@3x.png" 3x);
}
? 優點:
- 適配
Retina
屏幕,圖片更清晰
? 缺點:
- 需要提供不同
DPR
版本的圖片資源
3. 選型對比
方案 | 適用場景 | 適配能力 | 代碼復雜度 | 維護成本 |
---|---|---|---|---|
meta viewport | 適配初始縮放 | ??? | ? | ? |
@media 媒體查詢 | 適配不同設備 | ??? | ?? | ?? |
rem + vw | 全局適配 | ???? | ??? | ?? |
flex/grid | 自適應布局 | ???? | ?? | ? |
DPR 適配 | 高分屏優化 | ??? | ?? | ?? |
4. 總結
最佳適配方案
- 小型項目(簡單適配) →
viewport + @media
- 中型項目(動態布局) →
rem + vw
- 大型項目(高分屏 & 靈活適配) →
flex + vw + DPR 適配
97.場景題之web應用中如何對靜態資源加載失敗的場景做降級處理
場景題:Web 應用中如何對靜態資源加載失敗的場景做降級處理?
在 Web 應用中,靜態資源(如 CSS、JS、圖片、字體等)可能由于 CDN 故障、網絡問題、文件丟失 等原因加載失敗。為了保證用戶體驗,需要實現 降級處理,確保頁面在關鍵資源缺失時仍能正常運行。
1. 常見靜態資源加載失敗的原因
- CDN 資源不可用(服務器宕機、DNS 解析失敗)
- 網絡問題(用戶網絡波動、弱網環境)
- 瀏覽器攔截(CORS 限制、廣告攔截插件)
- 文件丟失或路徑錯誤(錯誤的資源 URL)
- 版本更新問題(緩存未更新導致 404)
2. 具體降級方案
針對不同類型的靜態資源,采用不同的降級策略:
📌 (1) JavaScript 資源降級
問題:核心 JS 資源(如 Vue、React 框架)加載失敗,可能導致頁面無法交互。
解決方案:
- 多 CDN 備選方案(動態切換)
<script src="https://cdn1.example.com/react.min.js" onerror="loadFallbackJS()"></script>
<script>function loadFallbackJS() {var script = document.createElement("script");script.src = "https://cdn2.example.com/react.min.js";document.head.appendChild(script);}
</script>
- 本地備份(CDN + 本地)
<script src="https://cdn.example.com/vue.min.js" onerror="this.onerror=null;this.src='/local/vue.min.js'"></script>
? 優點:減少單點依賴,保障 JS 可用
? 缺點:可能造成一定的延遲
📌 (2) CSS 資源降級
問題:CSS 失敗可能導致頁面錯亂
解決方案:
- 備用 CSS(多 CDN 或本地降級)
<link rel="stylesheet" href="https://cdn.example.com/style.css" onerror="this.onerror=null;this.href='/fallback/style.css';">
- 使用
<noscript>
兜底 如果 JS 和 CSS 都失效,可以提供最基本的noscript
樣式:
<noscript><link rel="stylesheet" href="/fallback/basic.css">
</noscript>
? 優點:確保最基本的樣式可用
? 缺點:無法應對所有 CSS 丟失的情況
📌 (3) 圖片資源降級
問題:圖片加載失敗影響用戶體驗
解決方案:
- 使用默認占位圖
<img src="https://cdn.example.com/avatar.jpg" onerror="this.onerror=null;this.src='/fallback/default-avatar.jpg';">
- 使用
object-fit
確保布局穩定
img {width: 100px;height: 100px;object-fit: cover;background-color: #f0f0f0; /* 避免顯示空白 */
}
? 優點:避免圖片影響布局
? 缺點:占位圖可能無法滿足所有業務需求
📌 (4) 字體資源降級
問題:Web 字體加載失敗會導致 FOUT(Flash of Unstyled Text)或頁面顯示異常
解決方案:
- 使用
font-display: swap
@font-face {font-family: "CustomFont";src: url("/fonts/custom.woff2") format("woff2");font-display: swap;
}
? 優點:優先使用系統字體,避免頁面空白
? 缺點:字體可能會有短暫的切換閃爍
- 提供多個字體備選
body {font-family: "CustomFont", "Arial", sans-serif;
}
? 優點:保證頁面最起碼的可讀性
📌 (5) 整體降級策略
當多個資源加載失敗時,可以通過 JS 監控 + 策略性降級 提供更優體驗:
- 全局監聽資源加載失敗
window.addEventListener("error", function (event) {if (event.target.tagName === "SCRIPT") {console.error("JS 加載失敗,嘗試使用備用方案");loadFallbackJS();} else if (event.target.tagName === "LINK") {console.error("CSS 加載失敗,加載降級樣式");document.head.insertAdjacentHTML("beforeend",'<link rel="stylesheet" href="/fallback/style.css">');} else if (event.target.tagName === "IMG") {event.target.src = "/fallback/default-image.jpg";}
}, true);
? 優點:全局監控并自動降級
? 缺點:可能增加額外的降級邏輯
3. 監控與上報
為了防止資源加載失敗影響用戶體驗,可以上報錯誤并分析:
window.addEventListener("error", function (event) {fetch("/log/error", {method: "POST",body: JSON.stringify({type: event.target.tagName,url: event.target.src || event.target.href,time: new Date().toISOString()})});
}, true);
? 優點:幫助分析資源加載失敗原因
? 缺點:需要額外的日志存儲服務
4. 總結
📌 資源降級策略對比
資源類型 | 降級方案 | 適用場景 | 額外成本 |
---|---|---|---|
JS 資源 | 備用 CDN / 本地備份 | 核心框架 & 業務 JS | 低 |
CSS 資源 | 備用 CDN / noscript 方案 | 關鍵樣式 | 低 |
圖片資源 | 默認占位圖 | 頭像、Banner | 低 |
字體資源 | font-display: swap / 備用字體 | 自定義字體 | 低 |
監控 & 上報 | window.addEventListener("error") | 統計加載失敗 | 中 |
📌 面試回答建議
- 先分析問題(靜態資源加載失敗的原因)
- 再提供不同資源類型的降級方案
- 最后補充監控 & 上報方案
- 結合實際場景選擇最優方案
這樣回答,既展現了技術廣度(覆蓋 JS、CSS、圖片、字體等資源),又體現了深度(主動監控 + 降級方案 + 上報分析),
98.場景題之如何修改第三方npm包
場景題:如何修改第三方 NPM 包?
在實際開發中,有時候我們需要修改 第三方 NPM 包,可能是因為:
- Bug 修復:第三方庫存在未修復的問題
- 功能擴展:需要新增或修改原有功能
- 兼容性問題:與當前項目或新版本的依賴沖突
- 個性化定制:需要更改樣式、邏輯、API 等
下面介紹 不同場景下的修改方式,并分析 優缺點。
方式 1:直接修改 node_modules
目錄下的文件(不推薦)
? 適用場景:
- 臨時調試或快速驗證修改是否生效
? 缺點:
npm install
或pnpm install
后會被覆蓋- 團隊協作難以維護
示例:
cd node_modules/some-package
vim index.js # 直接修改文件
🚨 不推薦,修改后不能持久化!
方式 2:使用 patch-package
生成補丁(推薦)
? 適用場景:
- 修改第三方包 但不想手動維護 fork 版本
- 可持續管理修改,方便團隊協作
步驟:
- 安裝
patch-package
npm install patch-package postinstall-postinstall --save-dev
postinstall-postinstall
確保補丁在npm install
后自動應用
- 修改
node_modules
內的代碼
vim node_modules/some-package/index.js # 進行修改
- 生成補丁
npx patch-package some-package
生成
patches/some-package+1.0.0.patch
文件,記錄改動
- 在
package.json
添加postinstall
"scripts": {"postinstall": "patch-package"
}
- 提交
patches
目錄到 Git
git add patches
git commit -m "fix: modify some-package for compatibility"
📌 優點:
- ? 不會丟失,
npm install
后會自動應用補丁 - ? 團隊協作友好,代碼改動可被 Git 追蹤
- ? 不影響原包的更新,可隨時調整
🚀 推薦使用!
方式 3:Fork 該 NPM 包,維護自己的版本
? 適用場景:
- 長期維護 或 修改較多 的情況下
- 官方倉庫不接受 PR 或修復周期太長
步驟:
- Fork 該 NPM 包的 GitHub 倉庫
- 在本地克隆
git clone https://github.com/your-username/some-package.git
cd some-package
- 修改代碼
- 發布到 NPM(可選)
npm version patch # 更新版本號
npm publish --access public # 發布自己的 NPM 包
- 使用修改后的包
npm install your-username/some-package
📌 優點:
- ? 完全掌控代碼,可隨時更新
- ? 適用于大規模修改
? 缺點:
- 需要自己維護更新,官方如果有新版本,需要手動同步
🚀 適合長期定制需求!
方式 4:在項目代碼中重新封裝
? 適用場景:
- 僅修改部分 API,但不想改動原包
思路:
- 通過繼承或高階函數封裝
- 代理某些方法,增強功能
- 攔截包的導入,替換原實現
示例 1:封裝增強功能
import OriginalLib from "some-package";export default function CustomLib(...args) {const instance = new OriginalLib(...args);// 增強方法instance.newMethod = function () {console.log("This is a custom method!");};return instance;
}
示例 2:Webpack / Vite Alias 直接替換原包
"alias": {"some-package": "/src/custom-some-package.js"
}
🚀 適合小修改,減少維護成本!
總結
方式 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
直接修改 node_modules | 快速調試 | 立即生效 | 不能持久化,安裝后會丟失 |
patch-package ?(推薦) | 修改少量代碼 | 易維護,自動應用 | 仍依賴原包,適用小改動 |
Fork 倉庫 & 維護 | 長期維護/大改動 | 完全掌控代碼 | 需要自己維護更新 |
封裝重寫 | 修改 API / 兼容性 | 低維護成本,不影響原包 | 適用于部分 API 的調整 |
📌 最佳實踐:
- 小改動用
patch-package
- 大改動 Fork 維護
- 不想改源碼就封裝 API
99.場景題之實現網頁加載進度條
場景題:實現網頁加載進度條
在 Web 開發中,加載進度條 可以提升用戶體驗,常見的場景包括:
- 單頁應用(SPA) 頁面切換時的加載進度
- 頁面資源加載(CSS/JS/圖片等)
- 異步請求加載(Ajax 請求、接口調用)
- 長時間任務(文件上傳、數據處理)
方案 1:使用 NProgress 實現進度條(推薦)
NProgress 是一個輕量級的進度條庫,能在 頁面加載或 AJAX 請求時 顯示進度條。
安裝 NProgress
npm install nprogress
基本使用
import NProgress from "nprogress";
import "nprogress/nprogress.css"; // 引入樣式// 頁面開始加載時
NProgress.start();// 頁面加載完成
NProgress.done();
與 fetch
結合
NProgress.start();
fetch("https://api.example.com/data").then(response => response.json()).finally(() => NProgress.done());
📌 優點:
- 簡單易用,適合異步請求、路由跳轉
- 可自定義樣式
- 自動處理多次請求
🚀 適合大多數場景,推薦!
方案 2:基于 XMLHttpRequest
監聽網絡請求進度
對于 文件上傳 或 大數據請求,可以使用 XMLHttpRequest
監聽 progress
事件。
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/large-file", true);xhr.onprogress = function (event) {if (event.lengthComputable) {let percent = (event.loaded / event.total) * 100;console.log(`加載進度:${percent.toFixed(2)}%`);}
};xhr.onload = function () {console.log("加載完成");
};xhr.send();
📌 適用場景:
- 大文件下載
- 流式加載
🚀 適用于特定場景,如文件上傳
方案 3:基于 window.onload
監聽靜態資源加載
用于 網頁所有資源(CSS/JS/圖片)加載完畢 時顯示進度條。
<div id="progress-bar" style="width: 0%; height: 5px; background: blue; position: fixed; top: 0; left: 0;"></div>
<script>let progressBar = document.getElementById("progress-bar");function updateProgress(percent) {progressBar.style.width = percent + "%";}document.addEventListener("DOMContentLoaded", () => updateProgress(50));window.onload = () => updateProgress(100);
</script>
📌 適用場景:
- 整頁加載
- 首屏優化
🚀 適合全局加載進度條
方案 4:監聽路由變化(適用于 Vue / React)
對于 單頁應用(SPA) ,可以監聽路由變化,并在切換時顯示進度條。
Vue 結合 NProgress
import NProgress from "nprogress";
import { createRouter, createWebHistory } from "vue-router";const router = createRouter({history: createWebHistory(),routes: [ /* 路由配置 */ ]
});router.beforeEach(() => NProgress.start());
router.afterEach(() => NProgress.done());
React 結合 NProgress
import { useEffect } from "react";
import NProgress from "nprogress";
import { useLocation } from "react-router-dom";const ProgressBar = () => {const location = useLocation();useEffect(() => {NProgress.start();return () => NProgress.done();}, [location.pathname]);return null;
};export default ProgressBar;
📌 適用場景:
- Vue / React 單頁應用
- 路由切換時顯示進度
🚀 單頁應用推薦方案
總結
方案 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
NProgress ?(推薦) | Ajax、SPA 路由 | 簡單易用,UI 友好 | 需引入庫 |
XMLHttpRequest 進度監聽 | 文件上傳、大數據 | 進度精準 | 僅適用于請求 |
window.onload 資源加載 | 靜態資源、首屏優化 | 適用于整頁 | 不適用于 AJAX |
Vue/React 路由進度條 | 單頁應用 | 結合 NProgress,效果好 | 需框架支持 |
最佳實踐
- AJAX / 路由加載 ? NProgress
- 文件上傳 ?
XMLHttpRequest
監聽progress
- 整頁加載 ?
window.onload
- Vue / React ? 結合
beforeEach
/useEffect
100.場景題之小程序雙線程模型
小程序雙線程模型解析
1. 核心架構
小程序采用**渲染層(View Layer)和邏輯層(App Service Layer)**分離的雙線程模型,通過 Native(客戶端) 橋接通信,實現安全性與性能的平衡。
2. 線程職責
線程 | 運行環境 | 核心職責 | 限制 |
---|---|---|---|
渲染層 | WebView(iOS WKWebView / Android X5) | - 頁面渲染(WXML/WXSS) - 用戶交互事件監聽 | 無法直接執行 JavaScript 業務邏輯 |
邏輯層 | JavaScriptCore(iOS) / V8(Android) | - 業務邏輯處理(JS) - 數據管理(setData) - 調用原生 API | 無法直接操作 DOM |
3. 通信機制
-
事件驅動:
- 用戶交互(如點擊):渲染層捕獲事件 → 通過
WeixinJSBridge
通知邏輯層。 - 數據更新:邏輯層調用
setData
→ 數據序列化為字符串 → Native 轉發 → 渲染層解析并更新視圖。
// 邏輯層(業務邏輯) Page({handleClick() {this.setData({ text: "Updated" }); // 數據變更通知渲染層} });
- 用戶交互(如點擊):渲染層捕獲事件 → 通過
-
通信優化:
- 數據合并:多次
setData
調用合并為一次通信。 - 局部更新:僅傳遞變化的數據字段,減少傳輸量。
- 數據合并:多次
4. 設計優勢
-
安全性:
- 邏輯層無法直接操作 DOM,防止惡意腳本攻擊。
- 敏感 API(如支付)由 Native 層控制,JS 需通過權限申請。
-
性能:
- 渲染與邏輯分離,避免單線程阻塞(如長耗時邏輯不影響頁面渲染)。
- WebView 與 JS 引擎獨立,內存管理更高效。
-
穩定性:
- 單頁面崩潰不影響整體應用(各頁面渲染層獨立)。
5. 性能瓶頸與優化
-
通信開銷:
-
問題:頻繁
setData
或大數據量傳輸導致延遲。 -
解決:
- 使用
this.data.xxx
直接修改數據,僅在必要時調用setData
。 - 分頁加載數據,避免一次性傳遞超長列表。
- 使用
-
-
渲染層性能:
-
問題:復雜動畫或過多節點導致卡頓。
-
解決:
- 使用 CSS 動畫替代 JS 動畫。
- 簡化 WXML 結構,減少嵌套層級。
-
6. 與 Web 單線程模型的對比
對比項 | Web 單線程模型 | 小程序雙線程模型 |
---|---|---|
線程模型 | 渲染、邏輯、DOM 操作均在同一線程 | 渲染與邏輯分離,Native 橋接通信 |
安全性 | 可通過 JS 直接操作 DOM,風險較高 | 邏輯層無法操作 DOM,安全性更強 |
性能瓶頸 | 長任務阻塞渲染(如復雜計算) | 通信延遲(邏輯與渲染層數據傳遞) |
開發限制 | 無強制限制,靈活性高 | API 調用受限,需遵循小程序規范 |
7. 典型場景示例
用戶輸入實時搜索:
- 渲染層監聽輸入事件 → 通知邏輯層。
- 邏輯層防抖處理 → 發起網絡請求。
- 請求返回后通過
setData
更新列表 → 渲染層重新渲染。
優化點:
- 防抖減少請求次數。
- 僅更新差異數據,避免全列表刷新。
總結
雙線程模型通過邏輯與渲染隔離保障了小程序的安全與流暢性,但開發者需注意:
- 控制
setData
的頻率與數據量。 - 避免在渲染層執行復雜邏輯。
- 合理使用 Native 能力(如 Worker)處理耗時任務。
這一設計是小程序高性能、高安全性的基石,但也對開發者的優化意識提出了更高要求。
101.場景題之如何使用一個鏈接實現PC打開是web應用,手機打開是h5應用
解決方案:通過設備檢測實現自適應內容分發
1. 核心思路
使用 服務端 User-Agent 檢測 區分設備類型,同一 URL 返回 PC 或 H5 的不同前端資源,實現“一鏈雙端”。
2. 實現方案
方案一:服務端動態渲染(SSR)
-
步驟:
-
檢測 User-Agent:服務端(如 Node.js、Nginx)解析請求頭中的
User-Agent
。 -
返回對應內容:
- PC:返回 PC 版 HTML/CSS/JS。
- 移動端:返回 H5 版 HTML/CSS/JS。
-
-
代碼示例(Node.js) :
const express = require('express'); const mobileDetect = require('mobile-detect'); const app = express();app.get('/', (req, res) => {const md = new mobileDetect(req.headers['user-agent']);if (md.mobile()) {res.sendFile('h5-index.html', { root: './h5' });} else {res.sendFile('pc-index.html', { root: './pc' });} });
-
優點:
- URL 不變,無重定向延遲。
- SEO 友好,可針對不同設備優化內容。
方案二:Nginx 路徑分發
-
配置示例:
map $http_user_agent $device {default "pc";~*(android|iphone|ipod|ipad) "mobile"; }server {location / {root /usr/share/nginx/html/$device;try_files $uri $uri/ /index.html;} }
-
說明:
- 根據 UA 將請求映射到
pc
或mobile
目錄,加載不同前端資源。
- 根據 UA 將請求映射到
方案三:前端動態加載(CSR)
-
步驟:
- 前端通過 JS 檢測設備類型。
- 動態加載對應版本的組件或路由。
// 檢測移動端 const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);// 動態加載組件 if (isMobile) {import('./H5App').then(module => render(module.default)); } else {import('./PCApp').then(module => render(module.default)); }
-
缺點:
- 首屏加載所有代碼,影響性能。
- 無法徹底隔離 PC/H5 的依賴包。
3. 關鍵優化點
-
精準 UA 檢測:
- 使用成熟庫(如
mobile-detect
、react-device-detect
)提高識別準確率。
- 使用成熟庫(如
-
緩存策略:
- 設置
Vary: User-Agent
響應頭,避免 CDN 緩存混淆不同設備內容。
- 設置
-
降級方案:
- 當檢測失敗時,默認返回 PC 版并提供跳轉鏈接(如“切換到移動版”)。
-
SEO 處理:
- 確保 PC/H5 頁面核心內容一致,避免被判定為“內容重復”。
4. 方案對比
方案 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
服務端動態渲染 | 需嚴格區分功能/設計的場景 | 性能高、SEO 友好 | 需維護兩套前端代碼 |
Nginx 分發 | 靜態資源托管場景 | 配置簡單、無業務邏輯侵入 | 靈活性較低 |
前端動態加載 | 功能差異較小的輕量級應用 | 單代碼庫維護 | 首屏性能差、包體積大 |
5. 高級場景:響應式 + 動態增強
-
混合方案:
- 基礎 UI 使用響應式布局適配所有設備。
- 針對復雜功能(如大屏圖表),通過 UA 檢測動態加載 PC 專屬模塊。
// 響應式布局 + 按需加載 if (!isMobile && isDataVisualizationPage) {import('heavy-charts-module').then(initCharts); }
總結
-
推薦方案:服務端動態渲染(SSR)或 Nginx 分發,優先保證性能與體驗一致性。
-
核心原則:
- 設備檢測精準化:避免誤判導致功能錯亂。
- 代碼維護低成本:通過組件復用減少雙端開發量。
- 用戶體驗無縫銜接:確保 URL 一致,功能切換自然。
102.場景題之移動端上拉加載,下拉刷新實現方案
場景題:移動端上拉加載 & 下拉刷新
在移動端開發中,上拉加載(加載更多數據)和 下拉刷新(重新獲取最新數據)是常見的交互方式,常用于 列表、新聞流、商品展示等頁面。
一、核心思路
-
下拉刷新(Pull to Refresh)
- 用戶下拉頁面,觸發 數據刷新
- 適用于最新數據加載,如 新聞、社交動態
- 依賴
touchstart
touchmove
touchend
事件 - 也可以用 原生 WebView 下拉刷新 或 JS 框架支持
-
上拉加載(Infinite Scroll)
- 用戶滑動到底部,自動加載更多數據
- 適用于分頁數據,如 商品列表、文章列表
- 依賴
scroll
事件 或IntersectionObserver
二、手寫原生實現
1. 下拉刷新(手寫實現)
let startY = 0;
let isRefreshing = false;document.addEventListener("touchstart", (e) => {startY = e.touches[0].pageY;
});document.addEventListener("touchmove", (e) => {if (isRefreshing) return;let moveY = e.touches[0].pageY - startY;if (moveY > 50) { // 下拉閾值console.log("觸發刷新...");isRefreshing = true;refreshData();}
});function refreshData() {setTimeout(() => {console.log("數據刷新完成");isRefreshing = false;}, 1500);
}
📌 適用場景:
- 適合輕量級項目,可結合 CSS 增強效果
- 手動實現,可定制性高
🚀 優化方向:
- 添加動畫效果(例如旋轉 loading 圖標)
- 防止多次觸發
- 結合CSS3
transform
過渡
2. 上拉加載(監聽滾動事件)
window.addEventListener("scroll", () => {let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;let clientHeight = document.documentElement.clientHeight;let scrollHeight = document.documentElement.scrollHeight;if (scrollTop + clientHeight >= scrollHeight - 10) {console.log("觸發加載更多...");loadMoreData();}
});function loadMoreData() {setTimeout(() => {console.log("加載完成");}, 1000);
}
📌 適用場景:
- 適合PC + 移動端
- 兼容性較好,但
scroll
事件可能會觸發頻繁
🚀 優化方向:
- 防抖優化(減少滾動觸發頻率)
- 監聽
scrollHeight
變化,防止無限觸發
3. 上拉加載(IntersectionObserver 方案)
現代瀏覽器推薦使用 IntersectionObserver
監聽目標元素是否進入視口。
let observer = new IntersectionObserver((entries) => {if (entries[0].isIntersecting) {console.log("觸發加載更多...");loadMoreData();}
});observer.observe(document.querySelector("#load-more"));
📌 適用場景:
- 性能更優,比
scroll
監聽更高效 - 適用于 商品列表、無限滾動
三、使用前端框架(Vue/React)
1. Vue 實現
Vue 推薦使用 vueuse/useScroll
或 better-scroll
:
<template><div ref="list" @scroll="onScroll"><div v-for="item in listData" :key="item.id">{{ item.title }}</div><div v-if="loading">加載中...</div></div>
</template><script setup>
import { ref } from "vue";const listData = ref([...Array(20).keys()]); // 模擬數據
const loading = ref(false);const onScroll = (e) => {const { scrollTop, scrollHeight, clientHeight } = e.target;if (scrollTop + clientHeight >= scrollHeight - 10 && !loading.value) {loadMoreData();}
};const loadMoreData = () => {loading.value = true;setTimeout(() => {listData.value.push(...Array(10).keys());loading.value = false;}, 1000);
};
</script>
📌 適用場景:
- 適用于 Vue 2 & Vue 3
- 可結合
better-scroll
提供更流暢滾動
2. React 實現
React 也可以使用 useEffect
+ IntersectionObserver
:
import { useEffect, useState, useRef } from "react";const InfiniteScroll = () => {const [items, setItems] = useState(Array.from({ length: 20 }));const [loading, setLoading] = useState(false);const loadMoreRef = useRef(null);useEffect(() => {const observer = new IntersectionObserver(([entry]) => {if (entry.isIntersecting && !loading) {setLoading(true);setTimeout(() => {setItems((prev) => [...prev, ...Array.from({ length: 10 })]);setLoading(false);}, 1000);}});if (loadMoreRef.current) observer.observe(loadMoreRef.current);return () => observer.disconnect();}, [loading]);return (<div>{items.map((_, index) => (<div key={index}>Item {index}</div>))}<div ref={loadMoreRef}>{loading ? "加載中..." : "滑動加載更多"}</div></div>);
};export default InfiniteScroll;
📌 適用場景:
- React Hooks 風格
IntersectionObserver
減少性能開銷
四、總結
方法 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
原生 touch 事件 | 適用于下拉刷新 | 控制靈活,適配廣 | 需要手寫邏輯 |
scroll 事件 | 上拉加載 | 適用 PC/移動端 | 可能性能開銷較大 |
IntersectionObserver ?(推薦) | 上拉加載 | 性能優越,簡單高效 | 兼容性略低(IE 需 polyfill) |
Vue/React 方案 | SPA 項目 | 結合框架特性 | 需要組件化開發 |
最佳實踐
- ? 普通 Web ?
scroll
事件 + 防抖 - ? 現代 Web ?
IntersectionObserver
- ? Vue/React ? 結合
better-scroll
或useEffect
這樣回答,面試官會覺得你思路清晰、方案全面,絕對加分🔥!
103.區分并發和并行
并發(Concurrency)的概念
在計算機原理中,并發(Concurrency) 是指在同一時間段內處理多個任務,但這些任務不一定是同時執行的,而是通過任務切換來提高系統的吞吐量和資源利用率。
1. 并發 vs. 并行
- 并發(Concurrency) :多個任務在同一時間段內交替執行(看起來像是同時運行的)。
- 并行(Parallelism) :多個任務在同一時刻真正同時執行(需要多核 CPU)。
示例
- 并發:單核 CPU 運行多個任務,任務 A 運行一會兒,切換到任務 B,再切換回來(任務交替執行)。
- 并行:多核 CPU,任務 A 在核心 1 上運行,任務 B 在核心 2 上運行(真正的同時執行)。
2. 并發的實現方式
(1)多線程(Multi-threading)
- 一個進程可以擁有多個線程,每個線程執行不同的任務。
- 線程可以共享進程的內存,提高資源利用率。
- 示例:瀏覽器渲染(主線程處理 UI 渲染,Worker 線程處理計算任務)。
(2)異步編程(Asynchronous Programming)
- 適用于單線程環境(如 JavaScript)。
- 通過事件循環(Event Loop)和回調,讓任務在等待 I/O 時不阻塞其他操作。
- 示例:JavaScript 的
Promise
、async/await
機制。
(3)協程(Coroutine)
- 輕量級的線程,可以在執行過程中手動掛起和恢復。
- 比傳統多線程開銷更小,更適合高并發場景。
- 示例:Python
asyncio
、Go 協程(Goroutine) 。
3. 并發的挑戰
- 競爭條件(Race Condition) :多個任務同時訪問和修改共享資源,可能導致數據不一致。
- 死鎖(Deadlock) :多個任務相互等待對方釋放資源,導致程序無法繼續執行。
- 資源爭搶:任務切換會帶來性能開銷(如 CPU 線程調度)。
4. 并發的應用場景
- Web 服務器(同時處理多個用戶請求)
- 數據庫管理(多個事務并發執行)
- 多任務操作系統(任務調度)
- 前端異步操作(React 18 的并發模式)
5. 總結
- 并發 是指多個任務在同一時間段內交替執行,提高 CPU 利用率。
- 并行 是指多個任務在同一時刻同時執行,需要多核處理器支持。
- 常見并發技術:多線程、異步編程、協程等。
- 并發的挑戰:競爭條件、死鎖、資源爭搶等問題需要妥善管理。
在前端開發(如 React 18)中,并發模式優化了渲染,使得 UI 更新更加流暢,也體現了計算機并發原理的實際應用。
104.為什么React中super()和super(props)不傳props在子類構造函數中打印props是undefined,在生命周期中打印是正常訪問
在 React 類組件中,構造函數內 this.props
為 undefined
的根本原因是:未將 props
傳遞給父類構造函數,導致父類 React.Component
未能正確初始化 this.props
。以下是詳細分析:
一、現象復現
class MyComponent extends React.Component {constructor(props) {super(); // 未傳遞 propsconsole.log(this.props); // undefined}
}
二、底層原理
1. JavaScript 類繼承機制
super()
的作用:調用父類構造函數,初始化父類屬性和方法。- 參數傳遞:若子類構造函數中調用
super(props)
,則父類構造函數會接收到props
參數;若調用super()
,則父類構造函數接收的props
為undefined
。
2. React 的 Component
類實現
React 的父類構造函數會接收 props
并初始化 this.props
:
class Component {constructor(props) {this.props = props; // 父類構造函數將 props 賦值給 this.props// ...其他初始化邏輯}
}
- 若調用
super(props)
:父類構造函數將props
賦值給this.props
,子類構造函數中可訪問。 - 若調用
super()
:父類構造函數中props
為undefined
,因此this.props
未被正確初始化。
三、為什么其他生命周期方法中 this.props
正常?
React 在組件實例化后(構造函數執行完畢后),會再次將 props
掛載到實例上,因此:
- 構造函數外(如
render
、componentDidMount
):this.props
由 React 自動注入,與super()
是否傳遞props
無關。 - 構造函數內:
this.props
的初始化依賴父類構造函數的執行,因此必須通過super(props)
顯式傳遞。
四、驗證代碼
class Parent {constructor(props) {this.props = props;}
}class Child extends Parent {constructor(props) {super(); // 不傳遞 propsconsole.log("構造函數內 this.props:", this.props); // undefined}method() {console.log("方法中 this.props:", this.props); // 正常(假設外部手動賦值)}
}const child = new Child({ name: "foo" });
child.props = { name: "foo" }; // 模擬 React 的 props 注入
child.method(); // 輸出:{ name: "foo" }
五、React 源碼中的關鍵邏輯
在 React 的組件實例化過程中,即使構造函數中未傳遞 props
,React 也會在后續步驟將 props
賦值給實例:
// React 源碼簡化邏輯
const instance = new Component(props); // 構造函數中可能未正確初始化 this.props
instance.props = props; // React 強制覆蓋 this.props
六、總結
場景 | super(props) | super() |
---|---|---|
父類構造函數 | 正確接收 props ,初始化 this.props | 接收 props 為 undefined ,this.props 未初始化 |
子類構造函數內 | this.props 可用 | this.props 為 undefined |
其他生命周期方法 | this.props 正常 | this.props 正常(由 React 注入) |
結論:在構造函數中訪問 this.props
前,必須調用 super(props)
確保父類正確初始化。若不需要在構造函數中使用 props
,可省略 super(props)
,但 React 仍會在外部注入 this.props
。