前端面試之前準備(Vue原理)

Vue原理

組件化和MVVM

數(shù)據(jù)驅動視圖

  1. 傳統(tǒng)組件,只是靜態(tài)渲染,更新還要依賴操作DOM
  2. 數(shù)據(jù)驅動視圖 - vue MVVM
  3. 數(shù)據(jù)驅動視圖 - react setState() useState();

MVVM

MVVM架構
  1. Model(模型)
    • Model 代表應用的數(shù)據(jù)和業(yè)務邏輯。它負責存儲和管理應用的狀態(tài)。Model 可以包含從服務器獲取的數(shù)據(jù),或應用程序中需要操作的數(shù)據(jù)
    • Model 不關心 UI,它只處理數(shù)據(jù)和與數(shù)據(jù)相關的操作
  2. View(視圖)
    • View 是應用的 UI 部分,負責顯示數(shù)據(jù)(即 UI 展現(xiàn))。它通常是由 HTML、CSS 等組成的界面。View 關注的是如何呈現(xiàn)數(shù)據(jù)。
    • View 被動地顯示數(shù)據(jù)變化,但它不能直接修改數(shù)據(jù)
  3. ViewModel(視圖模型)
    • ViewModel 是 View 和 Model 之間的橋梁,它負責將 Model 中的數(shù)據(jù)處理為 View 所需的格式,并且處理用戶交互(如點擊、輸入等)。ViewModel 監(jiān)聽用戶對 View 的操作,并更新 Model 數(shù)據(jù),然后再將更新后的數(shù)據(jù)展示給 View
    • ViewModel 不直接操作 DOM,而是通過數(shù)據(jù)綁定(如 Vue 中的雙向數(shù)據(jù)綁定)來驅動 UI 的更新

響應式原理

核心API

  1. Vue 2 主要采用 Object.defineProperty() 實現(xiàn)數(shù)據(jù)的劫持和依賴收集。
  2. Vue 3 使用 Proxy 替代了 Object.defineProperty()

響應式的核心機制

Vue2.0通過數(shù)據(jù)劫持(數(shù)據(jù)監(jiān)聽)+ 發(fā)布 - 訂閱模式來實現(xiàn)響應式

  1. Observer(數(shù)據(jù)監(jiān)聽): 使用Object.defineProperty()劫持對象的屬性,并在屬性被讀取或修改時觸發(fā)回調(diào)
  2. Dep(依賴收集):用于管理依賴(訂閱者),當數(shù)據(jù)變更,通知所有依賴更新
  3. Watcher(觀察者): 作為Vue組件與數(shù)據(jù)的橋梁,當庭數(shù)據(jù)變化后,觸發(fā)視圖更新

響應式流程

  1. 數(shù)據(jù)初始化
  2. 依賴收集
  3. 視圖更新

Object.definedProperty 缺點

  1. 深度監(jiān)聽,需要遞歸到底,一次性計算量大
  2. 無法監(jiān)聽新增刷新/刪除屬性(Vue.set Vue.delete)
    • Vue.set(obj, "name", "value");
    • Vue.delete(obj, key)
  3. 無法監(jiān)聽原生數(shù)組,通過重寫數(shù)組方法(如 push、pop、splice)來監(jiān)聽數(shù)組變更,但不能監(jiān)聽數(shù)組索引的直接修改

vDom和diff

  1. vdom是實現(xiàn)vue和react的重要基石
  2. diff算法是vdom中最核心、最關鍵的部分

虛擬DOM(Virtual DOM)

Virtual DOM(虛擬 DOM) 是 Vue、React 等前端框架中用于優(yōu)化 DOM 操作的一種技術。它的核心思想是用 JavaScript 對象模擬真實 DOM 結構,然后在數(shù)據(jù)變化時對比新舊 VDOM 生成最小的更新補丁,再應用到真實 DOM,從而提高渲染性能。

為什么要有vdom

  1. 頻繁操作DOM開銷大,每次操作都需要觸發(fā)回流和重繪
  2. 數(shù)據(jù)驅動UI復雜: 手動管理DOM變化需要寫大量的代碼,容易出錯,難為維護

優(yōu)勢

  1. 減少直接DOM操作,在js中先計算變更,再一次性更新DOM
  2. 優(yōu)化渲染性能:使用diff算法計算最小的dom更新,提高頁面性能
  3. 跨平臺能力,vdom不依賴瀏覽器,react native等移動端框架也基于vdom構建

VDOM的實現(xiàn)

Vue2參考的是Snabbdom作為Virtual DOM庫,它的實現(xiàn)主要包括:

  1. VNode(虛擬節(jié)點):用javascript對象表示DOM結構
    const vnode = {
        tag: "div",
        data: { id: "app", class: "container" },
        children: [
            { tag: "p", data: {}, text: "Hello Virtual DOM" }
        ]
    };
    
    相當于
    <div id="app" class="container">
        <p>Hello Virtual DOM</p>
    </div>
    
  2. Diff算法: 新舊VNode進行對比,計算最小修改
    • 同級比較:只比較同一層的節(jié)點,不進行夸層對比,減少計算量
    • Key機制: 通過key標識節(jié)點,提高Diff速度
    • 雙端對比(雙指針優(yōu)化):減少無用遍歷,優(yōu)化列表更新
    • 四種節(jié)點變更情況:
      • 新增節(jié)點 -> 直接插入
      • 刪除節(jié)點 -> 直接移除
      • 修改節(jié)點 -> 更新屬性、文本
      • 移動節(jié)點 -> 調(diào)整順序,減少dom操作
  3. Patch過程:把差異更新到真實DOM

通過snabbdom學習VDOM

  1. h函數(shù)
  2. vdom數(shù)據(jù)結構
  3. patch函數(shù)

VDOM總結

  1. 用js模擬DOM結構
  2. 新舊vnode對比,得出最小的更新范圍,最后更新DOM
  3. 數(shù)據(jù)驅動視圖的模式下,有效控制DOM操作

diff算法

概覽

Diff(差異對比算法)用于比較新舊 Virtual DOM(VNode),找出最小變化,并將變更高效地應用到真實 DOM。

  1. 新舊VNode是否是同一個節(jié)點
  2. 對比屬性和文本內(nèi)容
  3. 對比子節(jié)點(雙端對比 + key機制優(yōu)化)

背景

  1. 樹diff的時間 復雜度O(n^3)
    • 遍歷tree1
    • 遍歷tree2
    • 排序
  2. 優(yōu)化時間復雜度到O(n)
    • 只比較統(tǒng)一層級,不跨級比較
    • tag不相同,則直接刪除重建,不再深度比較
    • tag和key,兩者都相同,則認為是相同節(jié)點,不再深度比較

diff算法詳細過程

  1. 判斷是否是同一節(jié)點
    • 相同:直接對比屬性&子節(jié)點
    • 不相同:直接刪除舊節(jié)點,創(chuàng)建新節(jié)點
    • 判斷規(guī)則:
      • 標簽相同
      • key相同
      • 都是文本節(jié)點
      const oldVNode = { tag: "p", text: "Hello" };
      const newVNode = { tag: "p", text: "Hello Vue" };
      
      // 只修改文本,不刪除 p 標簽
      patch(oldVNode, newVNode);
      
  2. 對比屬性和文本
    • 屬性不同 - 更新屬性
    • 文本不同 - 更新textContent
    // 舊 VNode
    const oldVNode = { tag: "p", data: { class: "text" }, text: "Hello" };
    
    // 新 VNode
    const newVNode = { tag: "p", data: { class: "text updated" }, text: "Hello Vue" };
    
    // Vue Patch 過程:
    // 1. class 變更 → 僅修改 class
    // 2. 文本變更 → 僅修改 textContent
    patch(oldVNode, newVNode);
    
    最終更新:
    <p class="text">Hello</p>
    ↓
    <p class="text updated">Hello Vue</p>
    
  3. 對比子節(jié)點
    如果節(jié)點相同,vue遞歸比較chidren:
    • 舊節(jié)點有,新節(jié)點沒有 -> 刪除舊節(jié)點
    • 新節(jié)點有,舊節(jié)點沒有 -> 直接新增
    • 新舊都有 -> 進行雙端對比+key機制優(yōu)化
  4. 雙端diff優(yōu)化
    • 從頭開始比較(左側)
    • 從尾部開始比較(右側)
    • 移動節(jié)點:使用key識別相同節(jié)點,避免刪除+重建

vdom和diff算法總結

  1. patchVNode
  2. addVNodes removeVNodes
  3. updateChildren(key的重要性)
  4. 細節(jié)不重要,updateChildren的過程不重要,不要深究
  5. vdom核心概念很重要:h、vnode、patch、diff、key
  6. vdom存在的價值更加重要:數(shù)據(jù)驅動視圖,控制DOM

LIS算法

在 Vue 2 的 Diff 過程中,列表比較采用雙端比較(雙指針優(yōu)化),但如果有大量元素位置變化,Vue 2 可能會執(zhí)行多次插入和刪除,導致不必要的 DOM 操作。

Vue 3 進一步優(yōu)化了 Diff 算法,引入了 LIS(最長遞增子序列,Longest Increasing Subsequence),減少 DOM 移動次數(shù),使列表更新更高效。

LIS 是指在一個數(shù)組中,找到最長的遞增子序列(不要求連續(xù)), Vue 3 的 Diff 算法中,LIS 用來找到可以復用的元素,避免不必要的移動。

輸入: [10, 9, 2, 5, 3, 7, 101, 18]
最長遞增子序列: [2, 3, 7, 18](長度 4)

vue3Diff過程

  1. 從前往后找到相同前綴(頭部相同不變)
  2. 從后往前找到相同后綴(尾部相同不變)
  3. 對比中間不同部分
    • 查找最長遞增子序列
    • 只移動非LIS部分
    • LIS部分直接復用,避免多余操作

vue2和vu3 diff更新對比

<!-- 舊列表 -->
<ul>
  <li key="a">A</li>
  <li key="b">B</li>
  <li key="c">C</li>
  <li key="d">D</li>
</ul>

<!-- 新列表(調(diào)整順序) -->
<ul>
  <li key="a">A</li>
  <li key="c">C</li>
  <li key="d">D</li>
  <li key="b">B</li>
</ul>
  1. vue2處理方式:
    • 發(fā)現(xiàn) A 沒變
    • 發(fā)現(xiàn) B 位置變了(誤認為B被刪除,再新增到最后)
    • C 和 D 位置不變
    • 最終:刪除B,創(chuàng)建B,導致額外的dom操作
  2. vue3處理方式
    • A 位置不變
    • C->D 不一定能夠
    • B(需要調(diào)整)
    • 最終:只移動B,而不是先刪除+新增

模版編譯

js的with語法

with 語句用于擴展作用域鏈,讓代碼在一個對象的作用域內(nèi)執(zhí)行,避免重復書寫對象名。

const person = {
  name: "Alice",
  age: 25,
  job: "Engineer"
};

with (person) {
  console.log(name);  // 直接訪問 person.name
  console.log(age);   // 直接訪問 person.age
  console.log(job);   // 直接訪問 person.job
}

with 會影響作用域鏈的解析,導致代碼難以閱讀和調(diào)試,因此被認為是不安全的,在嚴格模式(strict mode)下被禁止。

vue template complier將模版編譯為render函數(shù)

具體代碼見vue原理 - 模版編譯 - vue-template-compiler-demo

背景

  1. 模版不是html,有指令、插值、js表達式、能實現(xiàn)判斷、循環(huán)
  2. html是標簽語言,只有js才能實現(xiàn)判斷、虛幻(圖靈完備的)
  3. 因此,模版一定是轉換為某種js代碼,即編譯模版

過程

  1. 解析,將vue模版解析為AST(抽象語法樹),即一個樹型結構數(shù)據(jù)表示
  2. 轉換,在ast的基礎上進行轉換處理
    • 指令解析(v-if, v-for)
    • 表達式解析({{message}})
    • 靜態(tài)優(yōu)化(標記靜態(tài)節(jié)點,提高更新性能)
  3. 代碼生成,將AST轉換為可執(zhí)行的函數(shù)(render function),即h函數(shù)(vNode)
  4. 最終 render() 函數(shù)在組件渲染時執(zhí)行,生成 Virtual DOM(虛擬 DOM),再通過 Diff 算法 更新真實 DOM。

總結

  1. 模版編譯為render函數(shù),執(zhí)行render函數(shù)會返回vnode
  2. 基于vnode再執(zhí)行patch和diff
  3. 使用webpack vue-loader 會在開發(fā)環(huán)境下編譯模版(重要),屬于優(yōu)化手段

執(zhí)行render函數(shù)生成vnode

vue組件中使用render代替了template

react一直都用render

組件更新渲染過程

組件是異步渲染的,匯總數(shù)據(jù)data修改,一次性更新
[圖片上傳失敗...(image-441047-1741339501073)]

  1. 初次渲染過程
    • 創(chuàng)建vue實例
    • 解析template為render函數(shù)
    • 觸發(fā)響應式,監(jiān)聽data屬性的getter 和 setter
    • 執(zhí)行render函數(shù),生成vNode
    • patch(elem, newVNode),將vNode轉換為真實的DOM并掛載到頁面上:
  2. 更新過程
    • 修改data,觸發(fā)setter(此前在getter中已被監(jiān)聽)
    • 重新生成render函數(shù),生成新的vNode
    • patch(oldVNode, newVNode),通過diff,找到變更的節(jié)點,渲染到頁面上

前端路由

前端路由的原理

前端路由的本質是在不刷新頁面的情況下,更新URL并改變頁面內(nèi)容,他主要依賴History API 或 Hash模式來監(jiān)聽URL變化,并根據(jù)不同的url渲染組件

原因

  1. 傳統(tǒng)的后端路由方式下,每次訪問新的頁面都會向服務器請求新的html,每次頁面跳轉都會重新加載,影響用戶體驗
  2. 前端路由讓頁面切換更快:
    • 避免整頁刷新
    • 只更新部分視圖
    • 更適合單頁面應用

前端路由的兩種模式

hash路由

  1. hash變化會觸發(fā)網(wǎng)頁跳轉,即瀏覽器的前進,后退
  2. hash變化不會刷新頁面,spa必需的特點
  3. hash永遠不會提交到server端

history路由

  1. 用url規(guī)范的路由,但跳轉是不刷新頁面
  2. history.pushState
  3. window.onpopstate
  4. 需要服務端支持

如何選擇

  1. to b系統(tǒng)推薦用hash,簡單易用,對url規(guī)范不敏感
  2. to c系統(tǒng),可以考慮h5 history,但需要服務端支持
  3. 能選擇簡單的,就別用復雜的,要考慮成本和收益
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容