<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] ## 場景題 ### 1.js 實現一個帶并發限制的異步調度器 題目描述:JS 實現一個帶并發限制的異步調度器 Scheduler,保證同時運行的任務最多有兩個。完善代碼中 Scheduler 類,使得以下程序能正確輸出 ``` class Scheduler { add(promiseCreator) { ... } // ... } const timeout = (time) => new Promise(resolve => { setTimeout(resolve, time) }) const scheduler = new Scheduler() const addTask = (time, order) => { scheduler.add(() => timeout(time)).then(() => console.log(order)) } addTask(1000, '1') addTask(500, '2') addTask(300, '3') addTask(400, '4') // output: 2 3 1 4// 一開始,1、2兩個任務進入隊列// 500ms時,2完成,輸出2,任務3進隊// 800ms時,3完成,輸出3,任務4進隊// 1000ms時,1完成,輸出1// 1200ms時,4完成,輸出4 ``` 答案 ``` class Scheduler { this.queue = [] this.running = 0 this.MAX_RUNNING = 2 add(promiseCreator) { return new Promise(resolve => { this.queue.push({ promiseCreator, resolve }) this.schedule() }) } schedule() { while (this.queue.length !== 0 && this.running < this.MAX_RUNNING) { const currTask = queue.shift() this.running += 1 currTask.promiseCreator().then(result => { currTask.resolve(result) this.running -= 1 this.schedule() }) } } } const timeout = (time) => new Promise(resolve => { setTimeout(resolve, time) }) const scheduler = new Scheduler() const addTask = (time, order) => { scheduler.add(() => timeout(time)).then(() => console.log(order)) } ``` ### 2. js 實現版本號排序 在 JavaScript 中,版本號排序是一個常見的需求。版本號通常由多個數字和點號(`.`)組成,例如`1.2.3`或`2.10.1`。由于版本號是字符串形式,直接使用字符串排序會導致錯誤的結果(例如`1.10`會被排在`1.2`前面)。因此,我們需要將版本號拆分為數字部分,然后逐級比較。 ``` function compareVersions(v1, v2) { // 將版本號拆分為數字數組 const parts1 = v1.split('.').map(Number); const parts2 = v2.split('.').map(Number); // 獲取最大長度 const maxLength = Math.max(parts1.length, parts2.length); // 逐級比較 for (let i = 0; i < maxLength; i++) { const num1 = parts1[i] || 0; // 如果部分不存在,默認為 0 const num2 = parts2[i] || 0; if (num1 > num2) return 1; // v1 > v2 if (num1 < num2) return -1; // v1 < v2 } return 0; // 版本號相等 } function sortVersions(versions) { return versions.sort(compareVersions); } // 示例 const versions = ['1.10.0', '2.0.0', '1.2.3', '1.0.0', '2.1.0', '1.5.0']; const sortedVersions = sortVersions(versions); console.log(sortedVersions); // 輸出: ['1.0.0', '1.2.3', '1.5.0', '1.10.0', '2.0.0', '2.1.0'] ``` ### 3. 模擬實現 lodash 中的 get 函數 ``` function get(obj, path, defaultValue) { // 將路徑字符串轉換為數組(支持 'a.b.c' 或 ['a', 'b', 'c']) const keys = Array.isArray(path) ? path : path.split('.'); // 遍歷路徑 let result = obj; for (const key of keys) { if (result && typeof result === 'object' && key in result) { result = result[key]; // 繼續深入 } else { return defaultValue; // 路徑中斷,返回默認值 } } return result !== undefined ? result : defaultValue; } // 示例 const obj = { a: { b: { c: 42, }, }, }; console.log(get(obj, 'a.b.c')); // 42 console.log(get(obj, 'a.b.d', 'default')); // 'default' console.log(get(obj, ['a', 'b', 'c'])); // 42 console.log(get(obj, 'x.y.z', 'not found')); // 'not found' ``` ### 4. 模擬實現 Vue 的發布訂閱,Dep、Watcher * **Dep 類**:負責收集依賴(Watcher 實例),并在數據變化時通知這些依賴。 * **Watcher 類**:負責觀察數據的變化,并在數據變化時執行回調函數。 * **Vue 類**:負責初始化數據,并將數據轉換為響應式。 ``` class Vue { constructor(options) { this.$data = options.data; this.observe(this.$data); } // 將數據轉換為響應式 observe(data) { if (!data || typeof data !== 'object') { return; } Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]); this.proxyData(key); // 將數據代理到 Vue 實例上 }); } // 定義響應式屬性 defineReactive(data, key, val) { const dep = new Dep(); // 每個屬性都有一個 Dep 實例 Object.defineProperty(data, key, { get() { if (Dep.target) { dep.depend(); // 收集依賴 } return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); // 通知依賴更新 } }); } // 將數據代理到 Vue 實例上 proxyData(key) { Object.defineProperty(this, key, { get() { return this.$data[key]; }, set(newVal) { this.$data[key] = newVal; } }); } } class Dep { constructor() { this.subscribers = []; // 存儲所有的 Watcher 實例 } // 添加依賴 depend() { if (Dep.target && !this.subscribers.includes(Dep.target)) { this.subscribers.push(Dep.target); } } // 通知所有依賴更新 notify() { this.subscribers.forEach(sub => sub.update()); } } Dep.target = null; // 全局變量,用于存儲當前的 Watcher 實例 class Watcher { constructor(vm, key, cb) { this.vm = vm; // Vue 實例 this.key = key; // 監聽的屬性 this.cb = cb; // 回調函數 Dep.target = this; // 將當前 Watcher 實例設置為全局的 Dep.target this.value = this.vm[this.key]; // 觸發 getter,收集依賴 Dep.target = null; // 重置 Dep.target } // 更新視圖 update() { const newValue = this.vm[this.key]; if (newValue !== this.value) { this.value = newValue; this.cb(newValue); } } } ``` ### 5. js 實現大數相加 ~~~javaScript function addBigNumbers(num1, num2) { // 若字符串位數不一致,則短的補零 const num1_length = num1.length const num2_length = num2.length const maxLength = Math.max(num1_length, num2_length) if (num1_length < maxLength) { num1.padStart(maxLength, '0') } if (num2_length < maxLength) { num2.padStart(maxLength, '0') } // 從后往前計算;存儲上一步進位結果 let curry = 0 let res = '' for (let i = maxLength - 1; i >= 0; i--) { const number_num1 = Number(num1[i]) const number_num2 = Number(num2[i]) const sum = (curry + number_num1 + number_num2) % 10 curry = Math.floor((number_num1 + number_num2) / 10) res = sum + res } // 若最后仍有進位,補充到最前面 if (curry) { res = String(curry) + res } return res } const num1 = "123456789012345678901234567890"; const num2 = "987654321098765432109876543210"; console.log(addBigNumbers(num1, num2)); // 輸出: 1111111110111111111011111111100 ~~~ ### 6. js 模擬實現 LRU 緩存 LRU(Least Recently Used)緩存是一種常見的緩存淘汰策略,當緩存達到容量上限時,會優先移除最近最少使用的數據。 實現思路: 1. 使用`Map`來存儲緩存項,因為`Map`可以保持插入順序。 2. 當訪問一個緩存項時,將其移到最前面(表示最近使用)。 3. 當緩存達到容量上限時,移除最久未使用的項(即`Map`中的第一項)。 ~~~ class LRUCache { constructor(capacity) { this.capacity = capacity; // 緩存容量 this.cache = new Map(); // 使用Map存儲緩存 } // 獲取緩存項 get(key) { if (!this.cache.has(key)) { return -1; // 如果緩存中不存在,返回-1 } const value = this.cache.get(key); this.cache.delete(key); // 刪除舊的鍵值對 this.cache.set(key, value); // 將該項移到Map的末尾(表示最近使用) return value; } // 添加緩存項 put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); // 如果已存在,先刪除 } this.cache.set(key, value); // 添加新的鍵值對 if (this.cache.size > this.capacity) { // 如果超出容量,移除最久未使用的項(Map中的第一個鍵) const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } } } // 示例用法 const cache = new LRUCache(2); // 創建一個容量為2的LRU緩存 cache.put(1, 1); // 緩存為 {1=1} cache.put(2, 2); // 緩存為 {1=1, 2=2} console.log(cache.get(1)); // 返回 1,緩存為 {2=2, 1=1} cache.put(3, 3); // 移除鍵2,緩存為 {1=1, 3=3} console.log(cache.get(2)); // 返回 -1(未找到) cache.put(4, 4); // 移除鍵1,緩存為 {3=3, 4=4} console.log(cache.get(1)); // 返回 -1(未找到) console.log(cache.get(3)); // 返回 3,緩存為 {4=4, 3=3} console.log(cache.get(4)); // 返回 4,緩存為 {3=3, 4=4} ~~~ ### 7. js模擬實現請求錯誤超時重試并控制重試最大次數和最大超時時間 ``` /** * 帶重試和超時控制的 fetch 請求 * @param {string} url - 請求的 URL * @param {Object} options - 請求選項(如 method、headers 等) * @param {number} maxRetries - 最大重試次數(默認 3 次) * @param {number} maxTimeout - 最大超時時間(默認 5000 毫秒) * @returns {Promise} - 返回一個 Promise,成功時解析為響應,失敗時拒絕為錯誤 */ function fetchWithRetry(url, options = {}, maxRetries = 3, maxTimeout = 5000) { let retries = 0; const controller = new AbortController(); // AbortController API 用于控制是否放棄 fetch 請求 const timeoutId = setTimeout(() => { controller.abort(); throw new Error('請求超時') }, maxTimeout); // 遞歸函數,用于實現重試邏輯 const attemptFetch = async () => { try { const response = await fetch(url, { ...options, signal: controller.signal, // 注意這里如何傳參控制的放棄請求 }); clearTimeout(timeoutId); // 清除超時計時器 if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response; // 請求成功,返回響應 } catch (error) { clearTimeout(timeoutId); // 清除超時計時器 if (retries < maxRetries) { retries++; // const timeout = initialTimeout \* Math.pow(2, retries); // 可通過指數退避緩解服務器壓力 console.log(`請求失敗,正在重試 (${retries}/${maxRetries})...`); return attemptFetch(); // 遞歸重試 } else { throw new Error(`請求失敗,重試次數已用完: ${error.message}`); } } }; return attemptFetch(); } // 使用示例 fetchWithRetry('https://api.example.com/data', { method: 'GET' }, 3, 5000) .then(response => response.json()) .then(data => console.log('請求成功:', data)) .catch(error => console.error('請求失敗:', error.message)); ``` ### 8. 模擬實現圖片懶加載 實現思路 1. **占位符**: * 使用`data-src`屬性存儲圖片的真實 URL,而不是直接使用`src`屬性。 * 初始時,`src`屬性可以設置為一個占位符(如 1x1 的透明圖片)。 2. **檢測圖片是否進入可視區域**: * 使用`IntersectionObserver`API 監聽圖片是否進入可視區域。 * 如果圖片進入可視區域,將`data-src`的值賦給`src`,觸發圖片加載。 3. **兼容性**: * 對于不支持`IntersectionObserver`的瀏覽器,可以回退到監聽`scroll`事件。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>圖片懶加載</title> <style> img { width: 100%; height: 300px; background: #f0f0f0; display: block; margin-bottom: 20px; } </style> </head> <body> <div> <img data-src="https://picsum.photos/600/300?random=1" alt="Image 1"> <img data-src="https://picsum.photos/600/300?random=2" alt="Image 2"> <img data-src="https://picsum.photos/600/300?random=3" alt="Image 3"> <img data-src="https://picsum.photos/600/300?random=4" alt="Image 4"> <img data-src="https://picsum.photos/600/300?random=5" alt="Image 5"> </div> <script> // 獲取所有需要懶加載的圖片 const images = document.querySelectorAll('img[data-src]'); // 若支持 IntersectionObserver API if (window.IntersectionObserver) { // 配置 IntersectionObserver const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { // 如果圖片進入可視區域 const img = entry.target; img.src = img.dataset.src; // 將 data-src 的值賦給 src img.removeAttribute('data-src'); // 移除 data-src 屬性 observer.unobserve(img); // 停止觀察該圖片 } }); }, { rootMargin: '0px', // 視口的邊距 threshold: 0.1, // 當圖片 10% 進入視口時觸發 }); // 開始觀察所有圖片 images.forEach(img => observer.observe(img)); } else { // 檢查圖片是否進入可視區域 const lazyLoad = () => { images.forEach(img => { const rect = img.getBoundingClientRect(); if (rect.top < window.innerHeight && rect.bottom >= 0) { img.src = img.dataset.src; // 將 data-src 的值賦給 src img.removeAttribute('data-src'); // 移除 data-src 屬性 } }); }; // 初始加載可視區域內的圖片 lazyLoad(); // 監聽滾動事件 window.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad); } </script> </body> </html> ``` ### 9.模擬實現虛擬列表 虛擬列表(Virtual List)是一種優化長列表渲染性能的技術。它通過只渲染當前可見區域的內容,而不是渲染整個列表,從而減少 DOM 節點的數量,提升性能。以下是使用 JavaScript 模擬實現虛擬列表的詳細步驟和代碼示例。 ~~~javascript // 初始化數據 const itemCount = 1000; // 列表項總數 const itemHeight = 50; // 每個列表項的高度 const container = document.getElementById('container'); const content = document.getElementById('content'); // 設置內容區域的總高度 content.style.height = `${itemCount * itemHeight}px`; // 渲染可見區域的列表項 function renderVisibleItems() { const scrollTop = container.scrollTop; // 獲取滾動位置 const startIndex = Math.floor(scrollTop / itemHeight); // 計算起始索引 const endIndex = Math.min(startIndex + Math.ceil(container.clientHeight / itemHeight), itemCount - 1); // 計算結束索引 // 清空當前內容 content.innerHTML = ''; // 渲染可見區域的列表項 for (let i = startIndex; i <= endIndex; i++) { const item = document.createElement('div'); item.style.position = 'absolute'; item.style.top = `${i * itemHeight}px`; item.style.height = `${itemHeight}px`; item.style.width = '100%'; item.style.backgroundColor = i % 2 === 0 ? '#f0f0f0' : '#ffffff'; item.textContent = `Item ${i + 1}`; content.appendChild(item); } } // 監聽滾動事件 container.addEventListener('scroll', renderVisibleItems); // 初始化渲染 renderVisibleItems(); ~~~ ### 10. 實現一個可以控制超時時間和最大重試次數的 Promise 函數,適用于網絡請求或其他異步操作(考察 promise.race) ~~~ /** * 創建支持超時和重試的異步函數 * @param {Function} fn - 需要執行的異步函數 * @param {Object} options - 配置選項 * @param {number} [options.timeout=5000] - 超時時間(ms) * @param {number} [options.maxRetries=3] - 最大重試次數 * @returns {Promise} - 返回包裝后的 Promise */ function retryWithTimeout(fn, options = {}) { const { timeout = 5000, maxRetries = 3 } = options; let retries = 0; // 創建帶有超時控制的 Promise function attempt() { let timeoutId; // 超時控制 Promise const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Timeout after ${timeout}ms`)); }, timeout); }); // 執行原始 Promise const executionPromise = fn(); return Promise.race([executionPromise, timeoutPromise]) .finally(() => clearTimeout(timeoutId)) .catch(error => { if (retries >= maxRetries) { error.message = `Max retries exceeded (${maxRetries}): ${error.message}`; throw error; } retries++; return attempt(); }); } return attempt(); } // 使用示例 const fakeAPI = () => new Promise((resolve, reject) => { // 模擬 50% 成功率 Math.random() > 0.5 ? setTimeout(resolve, 3000, 'Data fetched!') : setTimeout(reject, 2000, new Error('API failed')); }); // 測試調用 retryWithTimeout(fakeAPI, { timeout: 2500, maxRetries: 2 }) .then(console.log) .catch(err => console.error('Final error:', err.message)); // 可能輸出: // 成功情況: "Data fetched!" // 失敗情況: "Final error: Max retries exceeded (2): Timeout after 2500ms" // 或 "Final error: Max retries exceeded (2): API failed" ~~~ ## 問答題 ### 1. 簡述 react fiber 原理 **1\. 問題背景** 在 React 16 之前,React 使用**棧調和算法**(Stack Reconciler)來協調虛擬 DOM 的變化。這種算法是**同步**的,一旦開始渲染,就會一直占用主線程,直到整個組件樹渲染完成。對于大型應用或復雜組件樹,這會導致主線程阻塞,用戶交互(如動畫、輸入)無法及時響應,造成卡頓。 * * * **2\. Fiber 的核心思想** Fiber 通過以下方式解決上述問題: 1. **任務拆分**:將渲染任務拆分為多個小任務(Fiber 節點)。 2. **可中斷**:允許 React 在執行過程中中斷渲染,優先處理高優先級任務(如用戶交互)。 3. **優先級調度**:根據任務的優先級動態調整執行順序。 4. **增量渲染**:將渲染過程分成多個幀(Frame),避免長時間占用主線程。 * * * **3\. Fiber 的數據結構** Fiber 是一個**鏈表結構**,每個 Fiber 節點對應一個組件或 DOM 節點。Fiber 節點包含以下關鍵信息: * **類型信息**:組件類型(函數組件、類組件、DOM 節點等)。 * **狀態信息**:組件的 props、state、hooks 等。 * **鏈表指針**: * `child`:指向第一個子節點。 * `sibling`:指向下一個兄弟節點。 * `return`:指向父節點。 * **工作狀態**:當前節點的渲染狀態(如是否已完成)。 * **優先級**:任務的優先級(如高優先級的用戶交互)。 * * * **4\. Fiber 的工作流程** Fiber 的渲染過程分為兩個階段: 1. **Render Phase(渲染階段)**: * 遍歷組件樹,生成 Fiber 樹。 * 對比新舊 Fiber 樹,標記需要更新的節點(Diff 算法)。 * 此階段是**可中斷**的,React 可以根據優先級暫停或恢復工作。 2. **Commit Phase(提交階段)**: * 將渲染結果應用到真實 DOM。 * 此階段是**不可中斷**的,確保 DOM 更新的完整性。 * * * **5\. 優先級調度** Fiber 引入了**優先級調度機制**,React 會根據任務的優先級動態調整執行順序。例如: * **高優先級任務**:用戶交互(如點擊、輸入)會立即處理。 * **低優先級任務**:數據更新、渲染等可以延遲執行。 React 根據任務的類型和上下文動態確定優先級。以下是常見的任務優先級分配規則: (1)**用戶交互任務** * **優先級**:`Immediate`或`UserBlocking` * **示例**: * 點擊事件、輸入框輸入、按鈕點擊等。 * React 會優先處理這些任務,以確保用戶體驗的流暢性。 (2)**動畫更新** * **優先級**:`UserBlocking` * **示例**: * CSS 動畫、過渡效果等。 * React 會確保動畫幀率穩定,避免卡頓。 (3)**數據更新** * **優先級**:`Normal` * **示例**: * `setState`、`useState`觸發的狀態更新。 * 數據獲取后的 UI 渲染。 (4)**后臺任務** * **優先級**:`Low`或`Idle` * **示例**: * 數據預加載、日志記錄、非關鍵渲染等。 * 這些任務會在瀏覽器空閑時執行,避免阻塞高優先級任務。 ~~~ import { useState, useEffect } from 'react'; function App() { const [count, setCount] = useState(0); // 高優先級任務:用戶點擊 const handleClick = () => { setCount((prev) => prev + 1); }; // 低優先級任務:數據預加載 useEffect(() => { const loadData = async () => { // 模擬數據加載 await new Promise((resolve) => setTimeout(resolve, 1000)); console.log('Data loaded'); }; loadData(); }, []); return ( <div> <button onClick={handleClick}>Click me ({count})</button> </div> ); } export default App; ~~~ React 使用瀏覽器的`requestIdleCallback`API(或 polyfill)在空閑時間執行低優先級任務,避免阻塞主線程。 * * * **6\. 增量渲染** Fiber 將渲染任務拆分為多個小任務,并在每一幀中執行一部分任務。這樣可以將渲染工作分攤到多個幀中,避免長時間占用主線程,保證動畫和用戶交互的流暢性。 ### 2. 簡要描述ES6 module require、exports以及module.exports的區別 ES6 模塊是 JavaScript 的官方模塊系統,支持靜態加載(在編譯時確定依賴關系)。 **特點** * 靜態加載:依賴關系在編譯時確定。 * 支持異步加載(通過`import()`動態導入)。 * 瀏覽器和現代 Node.js 環境原生支持。 CommonJS 是 Node.js 的默認模塊系統,支持動態加載(在運行時確定依賴關系)。 **特點** * 動態加載:依賴關系在運行時確定。 * 適用于 Node.js 環境。 * `exports`是`module.exports`的引用,直接覆蓋`exports`會斷開引用。 ### 3. vue 的生命周期,各個生命周期做了什么操作? 1.**`beforeCreate`** * **時機**:在實例初始化之后,數據觀測(data observation)和事件/偵聽器配置之前調用。 * **作用**:此時組件的`data`、`methods`、`computed`等選項還未初始化,通常用于一些與組件狀態無關的初始化操作。 * * * 2.**`created`** * **時機**:在實例創建完成后調用,此時已完成數據觀測、屬性和方法的運算,但尚未掛載到 DOM。 * **作用**:可以訪問`data`、`methods`、`computed`等,適合執行一些異步請求或初始化數據。 * * * 3.**`beforeMount`** * **時機**:在掛載開始之前調用,此時模板已編譯,但尚未將組件渲染到 DOM 中。 * **作用**:適合在 DOM 掛載前執行一些操作,但很少使用。 * * * 4.**`mounted`** * **時機**:在組件掛載到 DOM 后調用,此時可以訪問 DOM 元素。 * **作用**:適合執行 DOM 操作、初始化第三方庫或發送異步請求。 * * * 5.**`beforeUpdate`** * **時機**:在數據變化導致 DOM 重新渲染之前調用。 * **作用**:可以在 DOM 更新前訪問當前狀態,適合執行一些與更新相關的邏輯。 * * * 6.**`updated`** * **時機**:在數據變化導致 DOM 重新渲染之后調用。 * **作用**:適合在 DOM 更新后執行操作,但需要注意避免在此鉤子中修改狀態,否則可能導致無限更新循環。 * * * 7.**`beforeUnmount`** * **時機**:在組件實例卸載之前調用(Vue 2 中為`beforeDestroy`)。 * **作用**:適合執行清理操作,如清除定時器、取消事件監聽或銷毀第三方庫實例。 * * * 8.**`unmounted`** * **時機**:在組件實例卸載之后調用(Vue 2 中為`destroyed`)。 * **作用**:適合執行最終的清理操作,此時組件已從 DOM 中移除。 ### 4. Vue3 與 Vue2 的差別 Vue 3 在性能、開發體驗和功能上都有顯著提升,主要改進包括: 1. 更高效的響應式系統(基于`Proxy`)。 2. Composition API 提供更靈活的代碼組織方式。(setup 語法糖替代之前的 data、methods、computed 選項)。支持了自定義 hooks,將可復用的邏輯提取到單獨的函數中。 3. 更好的 TypeScript 支持。 4. 新特性如`Teleport`、`Suspense`等。`<suspense>`組件,用于處理異步組件的加載狀態,提供 fallback 內容。`<teleport>`組件,可以將組件渲染到 DOM 中的任意位置,常用于模態框、彈窗等場景。 5. 更小的包體積和更快的渲染速度 ### 5. 簡述 vue 的虛擬 dom 及 diff 算法的作用與實現原理 虛擬 DOM 是一個輕量級的 JavaScript 對象,用于描述真實 DOM 的結構。它的主要作用是減少直接操作真實 DOM 的開銷,通過對比新舊虛擬 DOM 的差異,最小化 DOM 操作。 #### **虛擬 DOM 的結構** 虛擬 DOM 是一個樹形結構,每個節點(VNode)包含以下屬性: * `tag`:節點標簽名(如`div`、`span`)。 * `props`:節點的屬性(如`class`、`style`)。 * `children`:子節點(可以是文本、其他 VNode 或組件)。 * `key`:節點的唯一標識,用于 Diff 算法優化。 ``` const vnode = { tag: 'div', props: { class: 'container' }, children: [ { tag: 'p', props: {}, children: 'Hello World' }, { tag: 'button', props: { onClick: handleClick }, children: 'Click Me' }, ], }; ``` ### **Diff 算法** Diff 算法用于比較新舊虛擬 DOM 的差異,并計算出最小的 DOM 更新操作。Vue 的 Diff 算法基于以下策略: #### **Diff 算法的核心思想** 1. **同級比較**:只比較同一層級的節點,不跨層級比較。 2. **Key 的作用**:通過`key`標識節點的唯一性,避免不必要的節點銷毀和重建。 3. **最小化操作**:盡量復用節點,只更新變化的屬性或子節點。 #### **Diff 算法的具體實現** 1. **節點類型不同**: * 如果新舊節點的`tag`不同,直接銷毀舊節點,創建新節點。 2. **節點類型相同**: * 如果`tag`相同,比較節點的`props`和`children`。 * 更新`props`:遍歷新舊`props`,更新變化的屬性。 * 更新`children`:遞歸比較子節點。 3. **子節點比較**: * Vue 使用**雙端比較算法**(頭尾指針法)優化子節點的比較: * 初始化四個指針:`oldStart`、`oldEnd`、`newStart`、`newEnd`。 * 比較`oldStart`和`newStart`、`oldEnd`和`newEnd`、`oldStart`和`newEnd`、`oldEnd`和`newStart`。 * 如果找到可復用的節點,移動指針并更新節點。 * 如果未找到可復用的節點,根據`key`查找舊節點中是否存在可復用的節點。 * 如果舊節點遍歷完畢,剩余的新節點需要創建;如果新節點遍歷完畢,剩余的舊節點需要銷毀。 ### 6. typescript 考點 1. 常用 ts 語法 2. interface 和 type 的區別 * interface 可以被類實現(`implements`)而 type 不行。 * interface 可以被其他接口擴展(`extends`) 而 type 不行。 ``` interface Animal { name: string; } interface Dog extends Animal { bark(): void; } class Labrador implements Dog { name = 'Rex'; bark() { console.log('Woof!'); } } ``` (1) 使用`interface`的場景 * 定義對象的形狀。 * 需要擴展或實現接口。 * 需要聲明合并。 (2) 使用`type`的場景 * 定義聯合類型、交叉類型、元組等復雜類型。 * 定義函數類型、字面量類型。 * 需要定義一次性使用的類型。 ~~~javaScript // 定義類型別名 type StringOrNumber = string | number; let value: StringOrNumber; value = "Hello"; // 合法 value = 42; // 合法 value = true; // 錯誤:boolean 不是 StringOrNumber 類型 // 定義聯合類型 type Status = "active" | "inactive" | "pending"; let userStatus: Status; userStatus = "active"; // 合法 userStatus = "deleted"; // 錯誤:不在聯合類型中 // 定義交叉類型 type Person = { name: string; }; type Employee = { id: number; role: string; }; type EmployeePerson = Person & Employee; const employee: EmployeePerson = { name: "Bob", id: 123, role: "Developer", }; // 定義復雜類型 type Nullable<T> = T | null; type StringOrNumberArray = (string | number)[]; type RecursiveType = { value: string; children?: RecursiveType[]; // 遞歸類型 }; ~~~ 3. 裝飾器語法有哪些用途 **類裝飾器**:修飾整個類。類裝飾器接收類的構造函數作為參數,并可以返回一個新的構造函數或修改原始構造函數。 ~~~javascript function logClass(target) { console.log('Class is decorated:', target.name); // 可以在這里擴展類的功能 target.prototype.newMethod = function () { console.log('This is a new method added by decorator'); }; } @logClass class MyClass { constructor() { console.log('MyClass instance created'); } } const instance = new MyClass(); instance.newMethod(); // 輸出: This is a new method added by decorator ~~~ **方法裝飾器**:修飾類的方法。 方法裝飾器接收三個參數:目標類的原型(如果是靜態方法,則是類的構造函數),方法名稱,方法的屬性描述符(`descriptor`)。 ~~~javascript function logMethod(target, name, descriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args) { console.log(`Calling method ${name} with arguments:`, args); const result = originalMethod.apply(this, args); console.log(`Method ${name} returned:`, result); return result; }; return descriptor; } class MyClass { @logMethod add(a, b) { return a + b; } } const instance = new MyClass(); instance.add(2, 3); // 輸出: Calling method add with arguments: [2, 3] // 輸出: Method add returned: 5 ~~~ 屬性裝飾器 訪問器裝飾器 實際應用? [看這](https://jkchao.github.io/typescript-book-chinese/tips/metadata.html#controller-%E4%B8%8E-get-%E7%9A%84%E5%AE%9E%E7%8E%B0) [node框架 - ts](https://nestjs.com/) 4. 用 Pick 實現 Omit ``` type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; // 使用 type PersonWithoutAddress = MyOmit<Person, 'address'>; ``` ### 7. React 考點 **`useEffect`和`useLayoutEffect`的區別是什么?** * **回答**: * **`useEffect`**: * 在瀏覽器完成渲染后異步執行。 * 適合大多數副作用操作(如數據獲取、訂閱等)。 * **`useLayoutEffect`**: * 在瀏覽器完成渲染前同步執行。 * 適合需要同步更新 DOM 的場景(如測量 DOM 元素尺寸)。 * 使用不當可能導致性能問題。 **`useMemo`和`useCallback`的作用是什么?有什么區別?** **回答**:`useMemo`緩存值,`useCallback`緩存函數。 `useMemo`: * 用于緩存計算結果,避免不必要的重復計算。 * 示例: ``` const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); ``` **`useCallback`**: * 用于緩存函數,避免不必要的函數重建。 * 示例:例如用 useCallback 對 lodash debounce 的函數做處理 ``` const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); ``` **Hooks 的性能優化有哪些方法?** * **回答**: 1. 使用`React.memo`避免不必要的組件渲染。 2. 使用`useMemo`緩存計算結果。 3. 使用`useCallback`緩存函數。 4. 避免在`useEffect`中執行昂貴的操作。 5. 使用`useReducer`管理復雜狀態,減少不必要的狀態更新。 **React Context** React Context 本質上使用的是觀察者模式,當我們在組件中調用狀態管理庫/React Context 暴露出的 hooks 時這個組件相當于訂閱了狀態管理庫維護的 state 的某一變量。當 state 更新時會通知其訂閱者 re-render 但是使用 React Context 目前存在一個性能問題:如下所示我們創建了一個 React Context,包含了 count1 和 count2 的數據,當我們更新 count1 的數據時發現 Count2 組件也觸發了 re-render,即使 Count2 未使用到 count1 數據。這是因為 React 對比 context 前后狀態是否不一致后,若不一致會沿著當前節點遍歷 Fiber 樹來尋找消費了當前 context 的組件,并且對其進行標記代表這個組件應該被重新渲染。這就導致渲染到這個組件時,其觸發了 re-render ~~~JavaScript const context = createContext(null); const Count1 = () => { const { count1, setCount1 } = useContext(context); console.log("Count1 render"); return <div onClick={() => setCount1(count1 + 1)}>count1: {count1}</div>; }; const Count2 = () => { const { count2 } = useContext(context); console.log("Count2 render"); return <div>count2: {count2}</div>; }; const StateProvider = ({ children }) => { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); return ( <context.Provider value={{ count1, count2, setCount1, setCount2 }} > {children} </context.Provider> ); }; const App = () => ( <StateProvider> <Count1 /> <Count2 /> </StateProvider> ); ~~~ ### 8.Map 和對象的差別?WeakMap 和垃圾回收 **Map 和 Object 的區別** 鍵的類型 * **Map**: * 鍵可以是任意類型(包括對象、函數、基本類型等)。 * 例如:`map.set({}, 'value')`或`map.set(1, 'value')`。 * **Object**: * 鍵只能是字符串或 Symbol。 * 如果使用非字符串鍵(如對象或數字),會被自動轉換為字符串。 * 例如:`obj[1]`和`obj['1']`是等價的。 鍵的順序 * **Map**: * 鍵值對的順序是插入順序。 * 遍歷時,鍵值對會按照插入的順序返回。 * **Object**: * 在 ES6 之前,對象的鍵順序是不確定的。 * 在 ES6 之后,普通對象的鍵順序遵循以下規則: 1. 數字鍵按升序排列。 2. 字符串鍵按插入順序排列。 3. Symbol 鍵按插入順序排列。 WeakMap 和 Map 的區別 鍵的類型 * **WeakMap**: * 鍵必須是對象(不能是基本類型)。 * 例如:`weakMap.set({}, 'value')`。 * **Map**: * 鍵可以是任意類型。 弱引用 * **WeakMap**: * 對鍵是弱引用的。如果鍵對象沒有被其他地方引用,它會被垃圾回收機制回收,即使它仍然存在于`WeakMap`中。 * 適合存儲與對象關聯的元數據,而不會阻止對象的垃圾回收。 * **Map**: * 對鍵是強引用的。即使鍵對象沒有被其他地方引用,它也不會被垃圾回收。 可枚舉性 * **WeakMap**: * 不可枚舉。沒有方法可以遍歷鍵或值(如`keys()`、`values()`、`entries()`)。 * 沒有`size`屬性。 * **Map**: * 可枚舉。支持遍歷鍵、值和鍵值對。 * 有`size`屬性。 使用場景 * **WeakMap**: * 適合存儲與對象關聯的私有數據或元數據。 * 例如:緩存與 DOM 元素關聯的數據。 * **Map**: * 適合存儲需要長期保存的鍵值對。 ### 9.簡述 formily 表單框架原理 ① 精確渲染:setState - 每次輸入某一表單字段全量渲染,formily 通過依賴收集機制可以實現精確渲染改動的表單字段 ② 領域模型:formily 內核提供 Field 組件(JSX 寫法),component 屬性表示字段所對應的 UI 組件和 UI 組件屬性,reactions 屬性可以用于監聽其他表單字段的變動并添加對應的副作用操作,validator 屬性可用于控制該字段的校驗邏輯 ③ 路徑系統:提供 form 對象作為頂層模型管理所有字段模型;Formily 提供了一些路徑操作方法:簡化了復雜表單狀態的管理,使開發者能更靈活地操作表單數據。 * **get**: 獲取路徑對應的值。 * **set**: 設置路徑對應的值。 * **exist**: 檢查路徑是否存在。 * **transform**: 對路徑對應的值進行轉換。 ④ 生命周期:對外暴露生命周期鉤子,例如表單初始化,表單提交 ⑤ 協議驅動:如何實現配置化的表單?通過 JSON-SCHEMA 協議來渲染表單[JSON SCHEMA](https://json-schema.org/) 一個簡單的 JSON Schema 示例: ~~~javaScript { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "minLength": 2, "maxLength": 10 }, "age": { "type": "number", "title": "年齡", "minimum": 0, "maximum": 120 } }, "required": ["name"] } ~~~ * **type**: 定義數據類型,如`object`、`string`、`number`、`array`等。 * **properties**: 定義對象的屬性。 * **required**: 定義必填字段。 * **title**: 字段的標題(通常用于表單的標簽)。 * **minLength**/**maxLength**: 字符串的最小和最大長度。 * **minimum**/**maximum**: 數字的最小和最大值。 ### 10. 如何進行頁面性能打點 通過`window.performance.timing`對象訪問以下關鍵時間點: * **navigationStart**:導航開始。 * **unloadEventStart**和**unloadEventEnd**:前一個頁面卸載的開始和結束時間。 * **redirectStart**和**redirectEnd**:重定向的開始和結束時間。 * **fetchStart**:開始獲取文檔。 * **domainLookupStart**和**domainLookupEnd**:DNS 查詢的開始和結束時間。 * **connectStart**和**connectEnd**:建立連接的開始和結束時間。 * **secureConnectionStart**:SSL 握手開始時間。 * **requestStart**和**responseStart**:請求發送和響應開始的時間。 * **responseEnd**:響應結束。 * **domLoading**:開始解析 DOM。 * **domInteractive**:DOM 解析完成,開始加載子資源。 * **domContentLoadedEventStart**和**domContentLoadedEventEnd**:DOMContentLoaded 事件的開始和結束時間。 * **domComplete**:DOM 和子資源加載完成。 * **loadEventStart**和**loadEventEnd**:load 事件的開始和結束時間。 ~~~ window.onload = function() { const timing = performance.timing; const loadTime = timing.loadEventEnd - timing.navigationStart; const domParseTime = timing.domComplete - timing.domLoading; const dnsTime = timing.domainLookupEnd - timing.domainLookupStart; const tcpTime = timing.connectEnd - timing.connectStart; const requestResponseTime = timing.responseEnd - timing.requestStart; console.log(`頁面加載時間: ${loadTime}ms`); console.log(`DOM 解析時間: ${domParseTime}ms`); console.log(`DNS 查詢時間: ${dnsTime}ms`); console.log(`TCP 連接時間: ${tcpTime}ms`); console.log(`請求響應時間: ${requestResponseTime}ms`); }; // 獲取 FP 時間 const fp = performance.getEntriesByName('first-paint')[0].startTime; // 獲取 FCP 時間 const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime; // 獲取 LCP 時間 const lcpEntry = performance.getEntriesByName('largest-contentful-paint')[0]; const lcp = lcpEntry ? lcpEntry.startTime : 0; ~~~ ### 11.回流與重繪 **回流(Reflow)** * **定義**: * 當渲染樹中的一部分或全部因為元素的規模、布局、隱藏等改變而需要重新構建時,瀏覽器會重新計算元素的位置和幾何屬性,這個過程稱為回流。 * **觸發條件**: * 添加或刪除可見的 DOM 元素。 * 元素的位置、尺寸、邊距、填充等幾何屬性發生變化。 * 頁面初始化渲染。 * 瀏覽器窗口大小改變(resize 事件)。 * 讀取某些屬性(如`offsetWidth`、`offsetHeight`、`clientWidth`等),因為瀏覽器需要重新計算布局。 * **性能影響**: * 回流是代價較高的操作,會導致瀏覽器重新計算整個渲染樹。 * * * **重繪(Repaint)** * **定義**: * 當元素的樣式發生變化但不影響其幾何屬性(如顏色、背景色、可見性等)時,瀏覽器會重新繪制受影響的部分,這個過程稱為重繪。 * **觸發條件**: * 改變元素的顏色、背景色、邊框顏色等樣式。 * 改變元素的可見性(如`visibility`)。 * **性能影響**: * 重繪的性能開銷比回流小,但頻繁重繪仍會影響性能。 * * * 4.**回流與重繪的關系** * **回流一定會觸發重繪**: * 當元素的幾何屬性發生變化時,瀏覽器需要重新計算布局并重新繪制。 * **重繪不一定觸發回流**: * 如果只是樣式變化而不影響布局,則只會觸發重繪。修改字體大小或字體類型會觸發回流,因為文本的布局需要重新計算: * `font-size` * `font-family` * `line-height` ### 12.Vite 構建速度為何比 webpack 快? 1.**基于原生 ES 模塊的開發服務器** * **Webpack**:在開發模式下,Webpack 需要先打包整個應用,才能啟動開發服務器,項目越大,啟動時間越長。 * **Vite**:利用現代瀏覽器對原生 ES 模塊的支持,Vite 直接按需提供源碼,無需預先打包,啟動速度極快。 2.**按需加載** * **Webpack**:即使只修改一個文件,Webpack 也可能重新打包整個應用,導致熱更新較慢。 * **Vite**:通過原生 ES 模塊,Vite 只編譯和提供當前請求的文件,熱更新時僅更新相關模塊,速度更快。 3.**利用 Esbuild 進行預構建** * **Vite**:使用 Esbuild 預構建依賴項,Esbuild 是用 Go 編寫的,速度遠超 JavaScript 打包工具。 * **Webpack**:依賴 JavaScript 實現的打包工具,速度相對較慢。 4.**緩存機制** * **Vite**:通過強緩存減少重復構建,依賴項在初次構建后會被緩存,后續啟動時直接使用緩存。 * **Webpack**:雖然也有緩存,但效果不如 Vite 顯著。 5.**開發與生產環境分離** * **Vite**:開發環境利用原生 ES 模塊,生產環境使用 Rollup 進行打包,兼顧開發速度和生產優化。 * **Webpack**:開發和生產環境使用相同的打包機制,無法像 Vite 那樣靈活優化。 6.**現代瀏覽器支持** * **Vite**:面向現代瀏覽器,直接使用原生 ES 模塊等新特性,減少兼容性處理,提升開發效率。 * **Webpack**:需要處理更多兼容性問題,增加了構建復雜性。 ### 13.什么是簡單請求,什么是復雜請求?OPTIONS 預檢請求何時會發送?作用? 瀏覽器會將請求分為**簡單請求**和**復雜請求**,兩者的區別主要體現在**是否需要觸發預檢請求(Preflight Request)** **簡單請求(Simple Request)** 同時滿足以下所有條件時,才是簡單請求: 1. **HTTP 方法**是以下之一: * `GET` * `POST` * `HEAD` 2. **HTTP 頭部**僅包含以下字段(不能有其他自定義頭部): * `Accept` * `Accept-Language` * `Content-Language` * `Content-Type`(且值僅限于以下三種): * `text/plain` * `multipart/form-data` * `application/x-www-form-urlencoded` 3. 請求中不能包含自定義頭部(如`X-Token`)。若使用`XMLHttpRequest`或`Fetch API`,請求不能包含事件監聽器或流式操作。 **復雜請求(Complex Request)** 只要不滿足簡單請求的條件,就是復雜請求。對于復雜請求,瀏覽器會先發送一個**OPTIONS 預檢請求**,確認服務器允許實際請求后,再發送真實請求。常見情況包括: 1. **使用了以下 HTTP 方法**: * `PUT` * `DELETE` * `PATCH` * `OPTIONS` 2. **設置了自定義 HTTP 頭部**(如`Authorization`、`X-Custom-Header`)。 3. **`Content-Type`不是簡單請求允許的值**(如`application/json`)。 4. **請求中使用了`ReadableStream`或事件監聽器**。 **特點** * **會觸發預檢請求(Preflight)**:瀏覽器先發送一個`OPTIONS`請求,詢問服務器是否允許實際請求。 * 服務器必須明確響應以下頭部,否則實際請求會被阻止: * `Access-Control-Allow-Origin` * `Access-Control-Allow-Methods`(允許的方法,如`GET, POST, PUT`) * `Access-Control-Allow-Headers`(允許的自定義頭部,如`X-Custom-Header`) * `Access-Control-Max-Age`(預檢請求的緩存時間)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看