JavaScript性能優化實戰(三):DOM操作性能優化

????????想象一下,你正在精心布置一個豪華蛋糕(你的網頁),每次添加一顆草莓(DOM元素)都要把整個蛋糕從冰箱拿出來、放回去(重排重繪),來來回回幾十次,不僅效率低下,蛋糕也可能被弄壞。DOM操作就像布置這個蛋糕,每一次操作都可能觸發瀏覽器的重排(Reflow)和重繪(Repaint),這可是前端性能的"隱形殺手"。

????????今天我們就來揭秘DOM操作的5大優化技巧,用生動的案例告訴你如何讓頁面操作如絲般順滑,告別卡頓!

1. 批量操作:DocumentFragment的"快遞箱"哲學

????????頻繁的DOM操作就像每次買一件商品都收一次快遞——每次都要開門、簽收、處理包裝,效率極低。DocumentFragment就像一個"虛擬快遞箱",可以把所有要添加的DOM元素先放進去,最后一次送達,大大減少操作次數。

問題代碼:頻繁DOM操作的噩夢

// 糟糕的做法:每次循環都操作DOM
function renderList(items) {const list = document.getElementById('myList');items.forEach(item => {const li = document.createElement('li');li.textContent = item.name;// 每次都觸發DOM更新,引發重排list.appendChild(li); });
}// 測試:渲染1000條數據
const largeDataset = Array.from({length: 1000}, (_, i) => ({name: `項目${i}`}));
renderList(largeDataset); // 觸發1000次DOM更新!

優化方案:用DocumentFragment批量處理

// 優化做法:批量處理后一次性更新
function renderListOptimized(items) {const list = document.getElementById('myList');// 創建文檔片段(虛擬容器)const fragment = document.createDocumentFragment();items.forEach(item => {const li = document.createElement('li');li.textContent = item.name;// 先添加到虛擬容器,不觸發DOM更新fragment.appendChild(li);});// 一次性更新DOM,只觸發1次重排list.appendChild(fragment);
}// 同樣渲染1000條數據,性能提升80%+
renderListOptimized(largeDataset); // 僅觸發1次DOM更新!

為什么這么快?
每次DOM操作都會觸發瀏覽器的重排計算(計算元素位置和大小)和重繪(像素渲染)。1000次單獨操作會產生1000次重排,而使用DocumentFragment只會產生1次,性能差異呈指數級增長。

2. 緩存DOM查詢:別反復"找東西"

????????DOM查詢就像在雜亂的房間找東西——每次找都要翻箱倒柜(遍歷DOM樹),如果頻繁找同一個東西,最好的辦法是找到后放在固定位置(緩存)。

問題代碼:重復查詢DOM的陷阱

// 糟糕的做法:反復查詢同一個DOM元素
function updateUserInfo(user) {// 每次都查詢DOM,性能浪費document.getElementById('username').textContent = user.name;document.getElementById('email').textContent = user.email;document.getElementById('age').textContent = user.age;// 循環中重復查詢,性能殺手!for (let i = 0; i < 100; i++) {const item = document.querySelector(`.list-item-${i}`);item.classList.add('highlight');}
}

優化方案:緩存查詢結果

// 優化做法:緩存DOM查詢結果
// 1. 一次性查詢并緩存常用元素
const userElements = {name: document.getElementById('username'),email: document.getElementById('email'),age: document.getElementById('age')
};function updateUserInfoOptimized(user) {// 直接使用緩存的DOM引用userElements.name.textContent = user.name;userElements.email.textContent = user.email;userElements.age.textContent = user.age;
}// 2. 循環中優化查詢
function highlightItems() {// 先查詢父元素(1次查詢)const list = document.getElementById('itemList');// 從緩存的父元素中查詢子元素(更快)const items = list.querySelectorAll('[class^="list-item-"]');// 直接遍歷緩存的集合items.forEach(item => {item.classList.add('highlight');});
}

性能對比

  • 重復查詢相同DOM元素:每次查詢耗時約10-50ms(視DOM復雜度)
  • 緩存查詢結果:后續訪問耗時≈0ms,性能提升100倍以上

3. 平滑動畫:requestAnimationFrame的"舞蹈節奏"

????????想象你在跳舞時,沒有音樂節奏(setTimeout),動作會僵硬卡頓;而跟著音樂節拍(requestAnimationFrame)跳舞,動作會流暢自然。瀏覽器渲染也有自己的"節拍"(通常60fps),跟著這個節奏更新視覺效果才能流暢。

問題代碼:定時器動畫的卡頓

// 糟糕的做法:用setTimeout做動畫
function animateBoxBad() {const box = document.getElementById('animatedBox');let position = 0;function move() {position += 1;box.style.left = `${position}px`;if (position < 500) {// 不匹配瀏覽器渲染節奏,可能導致卡頓setTimeout(move, 16); // 嘗試模擬60fps,但不精準}}move();
}

優化方案:用requestAnimationFrame同步渲染

// 優化做法:使用requestAnimationFrame
function animateBoxOptimized() {const box = document.getElementById('animatedBox');let position = 0;function move(timestamp) {position += 1;box.style.left = `${position}px`;if (position < 500) {// 告訴瀏覽器:下一幀渲染前調用moverequestAnimationFrame(move);}}// 啟動動畫requestAnimationFrame(move);
}// 高級用法:控制動畫幀率
function animateWithFpsControl() {const box = document.getElementById('animatedBox');let position = 0;const fps = 30; // 目標幀率const interval = 1000 / fps;let lastTime = 0;function move(timestamp) {// 控制幀率if (!lastTime || timestamp - lastTime > interval) {lastTime = timestamp;position += 2; // 每幀移動距離加倍,保持相同速度感box.style.left = `${position}px`;}if (position < 500) {requestAnimationFrame(move);}}requestAnimationFrame(move);
}

為什么更流暢?

  • setTimeout/setInterval:不管瀏覽器是否準備好渲染,到時就執行,可能導致掉幀
  • requestAnimationFrame:由瀏覽器調度,在每次重繪前執行,與瀏覽器渲染節奏完全同步
  • 節能優勢:頁面隱藏時(如切換標簽),動畫會自動暫停,節省CPU資源

4. 避免強制同步布局:別讓瀏覽器"手忙腳亂"

????????瀏覽器渲染有自己的流水線:布局(計算幾何屬性)→ 繪制(填充像素)→ 合成(組合圖層)。正常情況下這個流程是異步的,但如果你先讀取布局屬性(如offsetHeight),再立即修改樣式,會強制瀏覽器同步執行布局計算,造成性能阻塞。

問題代碼:強制同步布局的陷阱

// 糟糕的做法:讀取布局屬性后立即修改
function updateHeightsBad() {const boxes = document.querySelectorAll('.box');boxes.forEach(box => {// 1. 讀取布局屬性(觸發布局計算)const height = box.offsetHeight;// 2. 立即修改樣式(強制瀏覽器同步重新計算布局)box.style.height = `${height + 10}px`;});
}

優化方案:分離讀寫操作

// 優化做法:先批量讀取,再批量修改
function updateHeightsOptimized() {const boxes = document.querySelectorAll('.box');// 1. 第一階段:批量讀取所有必要的布局屬性const heights = Array.from(boxes).map(box => box.offsetHeight);// 2. 第二階段:批量修改樣式(此時不會觸發布局計算)boxes.forEach((box, index) => {box.style.height = `${heights[index] + 10}px`;});
}// 更復雜場景的優化:使用FastDOM庫思想
const fastDOM = {read: (callback) => {// 收集所有讀操作const results = [];// 批量執行讀操作results.push(callback());return results;},write: (callback) => {// 批量執行寫操作callback();}
};// 使用示例
function optimizedUpdate() {const boxes = document.querySelectorAll('.box');const heights = [];// 批量讀取fastDOM.read(() => {boxes.forEach(box => {heights.push(box.offsetHeight);});});// 批量寫入fastDOM.write(() => {boxes.forEach((box, index) => {box.style.height = `${heights[index] + 10}px`;});});
}

性能差異
在包含100個元素的頁面中,強制同步布局可能導致操作耗時增加10-100倍,在低端設備上甚至會造成明顯卡頓。

5. 虛擬DOM:用"藍圖"代替直接施工

????????直接操作DOM就像直接在裝修好的房子里頻繁拆改——成本高、效率低。虛擬DOM則像先在電腦上用3D模型設計(虛擬DOM樹),規劃好所有改動后,再一次性施工(更新真實DOM),大大減少實際操作。

傳統DOM操作的痛點

// 直接操作DOM的繁瑣與低效
function updateTodoList(todos) {const list = document.getElementById('todoList');list.innerHTML = ''; // 清空列表(整個替換,效率低)todos.forEach(todo => {const li = document.createElement('li');li.className = todo.completed ? 'completed' : '';li.innerHTML = `<span>${todo.text}</span><button class="delete">刪除</button>`;list.appendChild(li);});
}// 問題:即使只有一個todo變化,也會重新創建所有DOM元素

虛擬DOM的工作原理(簡化版)

// 1. 定義虛擬DOM節點結構
class VNode {constructor(tag, props, children) {this.tag = tag;this.props = props;this.children = children;}// 2. 渲染為真實DOMrender() {const el = document.createElement(this.tag);// 設置屬性Object.keys(this.props).forEach(key => {el.setAttribute(key, this.props[key]);});// 渲染子節點this.children.forEach(child => {const childEl = child instanceof VNode ? child.render() : document.createTextNode(child);el.appendChild(childEl);});return el;}
}// 3. 實現簡單的diff算法(找出最小差異)
function diff(oldVNode, newVNode) {// 標簽不同,直接替換if (oldVNode.tag !== newVNode.tag) {return { type: 'REPLACE', newVNode };}// 文本節點比較if (typeof oldVNode === 'string' && typeof newVNode === 'string') {if (oldVNode !== newVNode) {return { type: 'TEXT', content: newVNode };}return null;}// 屬性比較const propsDiff = {};const oldProps = oldVNode.props || {};const newProps = newVNode.props || {};// 查找屬性變化Object.keys(newProps).forEach(key => {if (oldProps[key] !== newProps[key]) {propsDiff[key] = newProps[key];}});// 查找被移除的屬性Object.keys(oldProps).forEach(key => {if (!newProps.hasOwnProperty(key)) {propsDiff[key] = undefined;}});// 子節點比較(簡化版)const childrenDiff = [];for (let i = 0; i < Math.max(oldVNode.children.length, newVNode.children.length); i++) {const childDiff = diff(oldVNode.children[i], newVNode.children[i]);if (childDiff) childrenDiff.push(childDiff);}return {type: 'UPDATE',props: propsDiff,children: childrenDiff};
}// 4. 使用虛擬DOM更新列表
function createTodoVNode(todo) {return new VNode('li', { class: todo.completed ? 'completed' : '' }, [new VNode('span', {}, [todo.text]),new VNode('button', { class: 'delete' }, ['刪除'])]);
}function updateTodoListOptimized(todos) {// 創建新的虛擬DOM樹const newVList = new VNode('ul', { id: 'todoList' }, todos.map(todo => createTodoVNode(todo)));// 與舊的虛擬DOM樹比較(實際應用中會保存上一次的vNode)const oldVList = window.lastVList; // 假設我們保存了上一次的虛擬DOMconst changes = diff(oldVList, newVList);// 只更新有變化的部分(實際應用中會有patch函數執行這些變化)applyChanges(document.getElementById('todoList'), changes);// 保存當前虛擬DOM供下次比較window.lastVList = newVList;
}

實戰建議

  • 小型項目:手動優化DOM操作可能比引入虛擬DOM更高效
  • 中大型項目:使用React、Vue等框架的虛擬DOM和diff算法,大幅減少DOM操作
  • 極端性能場景:結合Web Components或原生API做針對性優化

總結:DOM優化的"黃金法則"

  1. 減少操作次數:批量處理DOM變更,避免頻繁的增刪改
  2. 緩存查詢結果:DOM查詢代價高,復用查詢結果
  3. 遵循渲染節奏:用requestAnimationFrame同步視覺更新
  4. 避免布局抖動:分離讀寫操作,不強制同步布局
  5. 智能更新:使用虛擬DOM或手動計算最小變更集

????????記住:每次DOM操作都是"昂貴"的,優化的核心思想是減少實際DOM操作的數量和復雜度。在實際開發中,建議使用Chrome DevTools的Performance面板錄制操作過程,找到真正的性能瓶頸后再針對性優化。

????????最后送大家一句話:不是所有DOM操作都需要優化,但所有優化都應該基于測量。讓我們的頁面在性能與開發效率之間找到最佳平衡!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93699.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93699.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93699.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【力扣】面試經典150題總結02-雙指針、滑動窗口

1.驗證回文串&#xff08;簡單&#xff09;用toLowerCase()轉為小寫字母&#xff0c;然后前后指針向中間進行比對。2.判斷子序列&#xff08;簡單&#xff09;兩個指針一個指向長字符串&#xff0c;另一個指向短字符串。匹配就都1&#xff0c;不匹配就將長字符串指針1。長字符串…

MQ遷移方案

以下是完整的MQ遷移方案設計&#xff0c;涵蓋同構/異構遷移、零丟失保障、灰度切換等關鍵環節&#xff0c;適用于Kafka、RabbitMQ、RocketMQ等主流消息隊列&#xff1a;?一、遷移方案選型矩陣??場景??適用方案??技術實現??優缺點??同集群版本升級?滾動重啟 協議兼…

RAG 分塊中表格填補簡明示例:Markdown、HTML、Excel、Doc

表格填補是RAG分塊中常見的需求&#xff0c;但不同格式的表格處理方式有所不同。本文將對 Markdown、HTML、Excel 的合并單元格進行說明&#xff0c;并給出 Python 示例&#xff0c;演示如何解析和填補。1. Markdown 表格Markdown 只能用空值表示合并單元格。&#xff08;只有列…

IDEA創建一個VUE項目

由于新手學習VUE&#xff0c;所以使用手動初始化項目 步驟&#xff1a; 創建項目文件夾&#xff1a;在 IDEA 中點擊 File > New > Project&#xff0c;選擇 Empty Project&#xff0c;指定項目路徑。初始化 npm&#xff1a;在終端中&#xff1a;npm init -y安裝vue&#…

Chrome插件開發實戰:todoList 插件

以下是一個適合小團隊自用的 Chrome TodoList 插件開發示例&#xff0c;包含基礎功能&#xff08;增刪改查、本地存儲、統計&#xff09;和簡潔的交互設計。代碼結構清晰&#xff0c;適合新手學習或快速上手。 一、項目準備 創建插件項目目錄 todo-list-extension&#xff0c;…

【Redis數據庫開啟SSL加密】【小白指南】【生產環境可用】附帶Docker服務器配置和python連接Redis數據庫代碼(加密通訊版)

【Redis數據庫開啟SSL加密】【填坑指南】附帶服務器配置和python連接測試代碼 本教程轉為小白提供設置Redis安全訪問&#xff0c;自簽名證書進行安全訪問你的Redis數據庫&#xff0c;輕松實現安全訪問和保護數據庫不被非法入侵。 本文原創&#xff0c;轉載請注明出處&#xff0…

筆記本電腦鍵盤失靈【已解決】

配置環境硬件詳情筆記本電腦聯想拯救者y7000 2019 PG0&#xff08;已更新為win11&#xff09;外接鍵盤colorful ckb-p100問題今天筆記本開機后&#xff0c;進入登錄頁面輸入密碼&#xff0c;突然發現筆記本自帶鍵盤&#xff08;我通常不用外接鍵盤&#xff09;的鍵失靈了&#…

postgresql運維問題解決:PG集群備節點狀態異常告警處理

小亦平臺會持續給大家科普一些運維過程中常見的問題解決案例&#xff0c;運維朋友們可以在常見問題及解決方案專欄查看更多案例 問題概述&#xff1a; 故障&#xff1a; pg數據庫備節點狀態異常現象&#xff1a; 一般為集群間心跳超時導致,現象為集群有fail-count失敗數告警&…

Maven 開發實踐

文章目錄1. 搭建私服&#xff08;windows)2.上傳依賴3.多個遠程倉庫配置4.其它1. 搭建私服&#xff08;windows) 軟件下載 https://help.sonatype.com/en/download.html修改端口 etc/nexus-default.properties啟動程序 管理員身份進入進入bin目錄下執行.\nexus.exe /run創建Ma…

設計心得——如何架構選型

一、架構的作用 可能對于很多的公司&#xff0c;其實架構本身的重要性并不大。大家一定明白這回事&#xff0c;架構在實際的開發&#xff0c;在大多數的場景下其實用處并沒有書籍和資料中講的那樣重要&#xff0c;甚至是可有可無。這樣講是不有些可笑&#xff1f;是不是覺得挺意…

vba學習系列(12)--反射率通過率計算復雜度優化25/8/17

系列文章目錄 文章目錄系列文章目錄前言一、反射率通過率1.整體通過率2.整體通過率3.客戶工藝匹配4.機臺通過率分析5.鏡片通過率罩次分析分析1.1分析1.26.鏡片通過率圈數分析分析1.1分析1.28.鏡筒通過率圈數分析分析1.1分析1.29.鏡筒通過率罩次分析分析1.2總結前言 一、反射率通…

Microsoft WebView2

運行效果 代碼如下 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Form…

GitCode 疑難問題診療:全方位指南

一、問題診斷與解決框架1.1 通用問題排查流程在面對 GitCode 問題時&#xff0c;遵循一套科學的排查流程至關重要。首先&#xff0c;詳細記錄問題出現時的具體操作步驟與相關報錯信息&#xff0c;這有助于精準定位問題根源。例如&#xff0c;若在執行git push命令時出現錯誤&am…

AMD Ryzen AI Max+ 395四機并聯:大語言模型集群推理深度測試

本文介紹使用四塊Framework主板構建AI推理集群的完整過程&#xff0c;并對其在大語言模型推理任務中的性能表現進行了系統性評估。該集群基于AMD Ryzen AI Max 395處理器&#xff0c;采用mini ITX規格設計&#xff0c;可部署在10英寸標準機架中。 Jeff Geerling大佬還開發了名…

深度學習·GFSS

GFSS General Few-Shot Segmentation 任務實現方式與zero-shot有所不同本篇論文只涉及同一個模態(圖像)&#xff0c;訓練過程中&#xff0c;novel class有幾個圖像提供&#xff0c;提供k個就稱之為k-shot。先從圖像中提取class prototype&#xff0c;然后這個原型向量作為查詢&…

Transformer架構的數學本質:從注意力機制到大模型時代的技術內核

系列專欄推薦&#xff1a;零基礎學Python&#xff1a;Python從0到100最新最全教程 深入淺出講解神經網絡原理與實現&#xff0c;從基礎的多層感知機到前沿的Transformer架構。包含完整的數學推導、代碼實現和工程優化技巧。 寫在前面&#xff1a;為什么理解Transformer如此重要…

最新微信小程序一鍵獲取真實微信頭像和昵稱方法

使用公開免費插件&#xff0c;快速實現獲取用戶頭像和昵稱&#xff0c;已附uniapp、微信開發工具開發詳細教程。前言為了保護用戶隱私&#xff0c;wx.getUserInfo、wx.getUserProfile都沒法獲取到用戶頭像和昵稱了&#xff0c;只能通過設計用戶主動選擇/輸入形式&#xff0c;操…

路由器配置之模式

文章目錄配置路由器時&#xff0c;有一個模式選擇最佳實踐各個選項的區別11b only11g only11n only11bg mixed11bgn mixed配置路由器時&#xff0c;有一個模式選擇 最佳實踐 ? 追求速度&#xff1a;選 11n only&#xff08;需所有設備支持&#xff09;。 ? 兼容性優先&…

評測系統構建

合成數據更“科研驅動”&#xff0c;強調 controllability 和 generalization evaluation&#xff1a; 之前往往直接采用經典數據集如OGB和OGB-large提供的經典數據集和數據劃分思路 該思想從現有真實數據中學習參數&#xff0c;再構造類似但分布略異的數據集&#xff0c;驗證模…

【計算機網絡面試】TCP/IP網絡模型有哪幾層

參考&#xff1a; 2.1 TCP/IP 網絡模型有哪幾層&#xff1f; | 小林coding | Java面試學習 以下為自己做的筆記 應用層 專注于為用戶提供應用功能&#xff0c;如HTTP、FTP、Telnet、DNS、SMTP等。應用層不關心用戶是怎么傳輸的&#xff0c;當兩個設備間的應用需要通信時&…