最全vue面試題+詳解答案
1、MVC 和 MVVM 區別
MVC全名是 Model View Controller,時模型 - 視圖 - 控制器的縮寫,一種軟件設計典范。
- Model(模型):是用於處理應用程序數據邏輯部分。通常模型對象負責在數據庫中存取數據。
- View(視圖):是應用程序中處理數據顯示的本分。通常視圖是依據模型數據創建的。
- Controller(控制器):是應用程序處理用戶交互的部分。通常控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據。

MVVM新增了VM類。
-
ViewModel層:做了兩件事達到了數據的雙向綁定,一是將【模型】轉化成【視圖】,即將后端傳遞的數據轉化成所看到的頁面。 實現的方式時:數據綁定。二是將【視圖】轉化成【模型】,即將所看到的頁面轉換成后端的數據。實現的方式是:DOM事件監聽。


嚴格的MVVVM要求View不能和Model直接通信,而Vue提供了$refs這個屬性,讓Model可以直接操作View,違反了這一規定,所以是Vue沒有完全遵循MVVM。
2、為什么data是一個函數
組件的data寫成一個函數,數據以函數返回值形式定義,這樣每復用一次組件,就會返回一分新的data,類似於給每個組件實例創建一個私有的數據空間,讓各個組件實例維護各自的數據。而單純的寫成對象形式,就使得所有組件實例共用了一份data,就會造成一個變了全都會變的結果。
3、Vue組件通訊有哪些方式?
1、props 和 $emit。父組件向子組件傳遞數據是通過props傳遞的,子組件傳遞給父組件是通過$emit觸發事件來做到的。
2、$parent 和 $children 獲取單簽組件的父組件和當前組件的子組件。
3、$attrs 和 $listeners A -> B -> C。Vue2.4開始提供了$attrs和$listeners來解決這個問題。
4、父組件中通過 provide 來提供變量,然后在子組件中通過 inject 來注入變量。(官方不推薦在實際業務中適用,但是寫組件庫時很常用。)
5、$refs 獲取組件實例。
6、envetBus 兄弟組件數據傳遞,這種情況下可以使用事件總線的方式。
7、vuex 狀態管理。
4、Vue的生命周期方法有哪些?一般在哪一步發送請求?
beforeCreate 在實例初始化之后,數據觀測(data observe)和 event/watcher 事件配置之前被調用。在當前階段 data、methods、computed 以及 watch 上的數據和方法都不能被訪問。
created 實例已經創建完成之后被調用。在這一步,實例已經完成以下的配置:數據觀測(data observe ),屬性和方法的運算,watch/event 事件回調。這里沒有 $el,如果非要想與 DOM 進行交互,可以通過vm.$nextTick 來訪問 DOM。
beforeMount 在掛載開始之前被調用:相關的 render 函數首次被調用。
mounted 在掛載完成后發生,在當前階段,真實的 Dom 掛載完畢,數據完成雙向綁定,可以訪問到 Dom節點。
beforeUpdate 數據更新時調用,發生在虛擬 DOM 重新渲染和打補丁 (patch)之前。可以在這個鈎子中進一步地更改狀態,這不會觸發附加的重渲染過程。
updated 發生在更新完成之后,當前階段組件 Dom 已經完成更新。要注意的是避免在此期間更新數據,因為這個可能導致無限循環的更新,該鈎子在服務器渲染期間不被調用。
beforeDestroy 實例銷毀之前調用。在這一步,實力仍然完全可用。我們可以在這時進行 善后收尾工作,比如清除定時器。
destroy Vue實例銷毀后調用。調用后,Vue實例指示的東西都會解綁定,所有的事件監聽器會被移除,左右的子實例也會被銷毀,該鈎子在服務器端渲染不被調用。
activated keep-alive 專屬,組件被激活時調用
deactivated keep-alive 專屬,組件被銷毀時調用
可以在鈎子函數 created、beforeMount、mounted 中進行異步請求,因為在這三個鈎子函數中,data已經創建,可以將服務器端返回的數據進行賦值。
如果異步請求不需要依賴 DOM 推薦加載 created 鈎子函數中調用異步請求,因為在 created 鈎子函數中調用異步請求有以下優點:
- 能更快獲取到服務端數據,減少頁面loading時間;
- ssr 不支持 beforeMount、mounted 鈎子函數,所以放在 created 中有助於一致性。
5、v-if 和 v-show 的區別
v-if 在編譯過程中會被轉化成三元表達式,條件不滿足時不渲染此節點。
v-show 會被編譯成指令,條件不滿足時控制樣式將此節點隱藏(display:none)
v-if 適用於在運行時很少改變條件,不需要頻繁切換條件的場景。
v-show 適用於需要非常頻繁切換條件的場景。
擴展補充:display:none 、 visibility:hidden 和 opacity:0 之間的區別?
- 一、是否占據空間。
display:none,隱藏之后不占位置;visibility:hidden、opacity:0,隱藏后任然占據位置。 - 二、子元素是否繼承。
display:none --- 不會被子元素繼承,父元素都不存在了,子元素也不會顯示出來。
visibility:hidden --- 會被子元素繼承,通過設置子元素 visibility:visible 來顯示子元素。
opacity:0 --- 會被子元素繼承,但是不能設置子元素 opacity:0 來先重新顯示。 - 三、事件綁定。
display:none 的元素都已經不存在了,因此無法觸發他綁定的事件。
visibility:hidden 不會觸發他上面綁定的事件。
opacity:0 元素上面綁定的事件時可以觸發的。 - 四、過度動畫。
transition對於display是無效的。
transition對於visibility是無效的。
transition對於opacity是有效的。
6、說說 vue 內置指令

- v-once - 定義它的元素或組件只渲染一次,包括元素或組件的所有節點,首次渲染后,不再隨數據的變化重新渲染,將被視為靜態內容。
- v-cloak - 這個指令保持在元素上直到關聯實例結束編譯 -- 解決初始化慢到頁面閃動的最佳實踐。
- v-bind - 綁定屬性,動態更新HTML元素上的屬性。例如 v-bind:class。
- v-on - 用於監聽DOM事件。例如 v-on:click v-on:keyup
- v-html - 賦值就是變量的innerHTML -- 注意防止xss攻擊
- v-text - 更新元素的textContent
- v-model - 1、在普通標簽。變成value和input的語法糖,並且會處理拼音輸入法的問題。2、再組件上。也是處理value和input語法糖。
- v-if / v-else / v-else-if。可以配合template使用;在render函數里面就是三元表達式。
- v-show - 使用指令來實現 -- 最終會通過display來進行顯示隱藏
- v-for - 循環指令編譯出來的結果是 -L 代表渲染列表。優先級比v-if高最好不要一起使用,盡量使用計算屬性去解決。注意增加唯一key值,不要使用index作為key。
- v-pre - 跳過這個元素以及子元素的編譯過程,以此來加快整個項目的編譯速度。
7、怎樣理解 Vue 的單項數據流
數據總是從父組件傳到子組件,子組件沒有權利修改父組件傳過來的數據,只能請求父組件對原始數據進行修改。這樣會防止從子組件意外改變父組件的狀態,從而導致你的應用的數據流向難以理解。
注意:在子組件直接用 v-model 綁定父組件傳過來的 props 這樣是不規范的寫法,開發環境會報警告。
如果實在要改變父組件的 props 值可以再data里面定義一個變量,並用 prop 的值初始化它,之后用$emit 通知父組件去修改。
8、computed 和 watch 的區別和運用的場景。
computed 是計算屬性,依賴其它屬性計算值,並且 computed 的值有緩存,職友集當計算值變化才會返回內容,他可以設置getter和setter。
watch 監聽到值的變化就會執行回調,在回調中可以進行一系列的操作。
計算屬性一般用在模板渲染中,某個值是依賴其它響應對象甚至是計算屬性而來;而偵聽屬性適用於觀測某個值的變化去完成一段復雜的業務邏輯。
9、v-if 和 v-for 為什么不建議一起使用
v-for和v-if不要在同一標簽中使用,因為解析時先解析v-for在解析v-if。如果遇到需要同時使用時可以考慮寫成計算屬性的方式。
10、Vue 2.0 響應式數據的原理
整體思路是數據劫持 + 觀察者模式
對象內部通過 defineReactive 方法,使用 Object.defineProperty 將屬性進行劫持(只會劫持已存在的屬性),數組則是通過重寫數組來實現。當頁面使用對應屬性時,每個屬性都擁有自己的 dep 屬性,存在它所依賴的 watcher (依賴收集)get,當屬性變化后會通知自己對應的 watcher 去更新(派發更新)set。
1、Object.defineProperty 數據劫持
2、使用 getter 收集依賴 ,setter 通知 watcher派發更新。
3、watcher 發布訂閱模式。
11、Vue 如何檢測數組變化
數組考慮性能原因沒有用 defineProperty 對數組的每一項進行攔截,而是選擇對7種數組(push,shift,pop,splice,unshift,sort,reverse)方法進行重寫(AOP 切片思想)。
所以在 Vue 中修改數組的索引和長度無法監控到。需要通過以上7種變異方法修改數組才會觸發數組對應的watcher進行更新。
12、Vue3.0 用過嗎?了解多少?
- 響應式原理的改變 Vue3.x 使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty。
- 組件選項聲明方式 Vue3.x 使用 Composition API setup是Vue3.x新增的一個選項,他是組件內使用Composition API 的入口。
- 模板語法變化 slot 具名插槽語法,自定義指令v-model升級。
- 其他方面的更改 Suspense支持Fragment(多個根節點)和 Protal(在dom其他部分渲染組件內容)組件,針對一些特殊的場景做了處理。基於 treeShaking 優化,提供了更多的內置功能。
13、Vue3.0 和 2.0 的響應式原理區別
Vue的父子組件生命周期鈎子函數執行順序
- 加載渲染過程 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
- 子組件更新過程 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
- 父組件更新過程 父beforeUpdate -> 父updated
- 銷毀過程 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
15、虛擬DOM是什么?有什么優缺點?
1、保證性能下限:框架的虛擬DOM需要適配任何上層API可能產生的操作,他的一些DOM操作的實現必須是普適的,所以它的性能並不是最優的;但是比起粗暴的DOM操作性能要好很多,因此框架的虛擬DOM至少可以保證在你不需要手動優化的情況下,依然可以提供還不錯的性能,既保證性能的下限。
2、無需手動操作DOM:我們不需手動去操作DOM,只需要寫好 View-Model的 代碼邏輯,框架會根據虛擬DOM和數據雙向綁定,幫我們以可預期的方式更新視圖,極大提高我們的開發效率。
3、跨平台:虛擬DOM本質上是JavaScript對象,而DOM與平台強相關,相比之下虛擬DOM可以進行更方便地跨平台操作,例如服務器端渲染、weex開發等等。
缺點:
1、無法進行極致優化:雖然虛擬DOM + 合理的優化,足以應對大部分應用的性能需要,但在一些性能要求極高的應用中虛擬DOM無法進行針對性的極致優化。
2、首次渲染大量DOM時,由於多了一層DOM計算,會比innerHTML插入慢。
16、v-model 原理
v-model 在內部為不同的輸入元素使用不同的 property 並拋出不同的事件。
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change事件;
- select 字段將 value 作為 prop 並將 change 作為事件。
input v-model='sth'
input v-bind:value='sth' v-on:input='sth = $event.target.value'

17、v-for為什么要加key
更准確:因為帶key就不是就地復用了,在sameNode函數 a.key === b.key 對比中可以避免就地復用的情況。所以更加准確。
更快速:利用key的唯一性生成map對象來獲取對應節點,比遍歷方式塊。

18、Vue事件綁定原理
$on、$emit 是基於發布訂閱模式的,維護一個事件中心,on的時候將事件按名稱存在事件中心里,稱之為訂閱者,然后emit將對應的事件進行發布,去執行事件中心里的對應的監聽器。
19、vue-router 路由鈎子函數是什么?執行順序是什么?
1、導航被觸發。
2、在失活的組件里調用 beforeRouterLeave 守衛。
3、調用全局的 beforeEach 守衛。
4、在重用的組件調用 beforeRouterUpdate 守衛(2.2+)。
5、在路由配置里面 beforeEnter。
6、解析異步路由組件。
7、在被激活的組件里調用 beforeRouterEnter。
8、調用全局的 beforeResolve 守衛(2.5+)。
9、導航被確認。
10、調用全局的 afterEach 鈎子。
11、觸發 DOM 更新。
12、調用 beforeRouterEnter 守衛中傳給next的回調函數,創建好的組件實例會作為回調函數的參數傳入。
20、vue-router 動態路由是什么?有什么問題。
template: "
const router = new VueRouter({
routes: [
// 動態路徑參數 以冒號開頭
{ path: "/user/:id", component: User },
],
});
問題:vue-router 組件復用導致路由參數失效怎么辦?
解決方案:
1、通過watch監聽路由參數再發請求
watch:{
"router":function(){
this.getData(this.$router.params.xxx)
}
}
2、用 :key來阻止復用
router-view :key="$route.fullPath"
21、談一下對 vuex 的個人理解
vuex 是專門為 vue 提供的全局狀態管理系統,用於多個組件中數據共享、數據緩存等。(無法持久化、內部內心原理是通過創造一個全局實例 new Vue)
主要包括以下幾個模塊:
- State:定義了應用狀態的數據結構,可以在這里設置默認的初始化狀態。
- Getter:允許組件從Store中獲取數據,mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性。
- Mutation:是唯一更改 store 中狀態的方法,且必須是同步函數。
- Action:用於提交 mutation,而不是直接變更狀態,可以包含任意異步請求。
- Module:允許將單一的 Store 拆分更多個 store 且同時保存在單一的狀態樹中。
22、Vuex 頁面刷新數據丟失怎么解決?
需要做 vuex 數據持久化,一般使用本地儲存的方案來保存數據,可以自己設計存儲方案,也可以使用第三方插件。
推薦使用 vuex-persist (脯肉賽斯特)插件,它是為 Vuex 持久化儲存而生的一個插件。不需要你手動存取 storage,而是直接將狀態保存至 cookie 或者 localStorage中。
23、Vuex 為什么要分模塊並且加命名空間?
模塊: 由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常復雜時,store 對象就有可能會變得相當臃腫。為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊。
命名空間: 默認情況下,模塊內部的 action、mutation、getter是注冊在全局命名空間的 --- 這樣使得多個模塊能夠對同一 mutation 或 action 做出響應。如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced:true 的方式使其成為帶命名的模塊。當模塊被注冊后,他所有 getter、action、及 mutation 都會自動根據模塊注冊的路徑調整命名。
24、使用過 Vue SSR 嗎?說說 SSR
SSR 也就是服務端渲染,也就是將 Vue 在客戶端把標簽渲染成 HTML 的工作放在服務端完成,然后再把 html 直接返回給客戶端。
優點:
SSR 有着更好的 SEO、並且首屏加載速度更快。
缺點:
開發條件會受限制,服務器端渲染只支持 beforeCreate 和 created 兩個鈎子,當我們需要一些外部擴展庫時需要特殊處理,服務端渲染應用程序也需要處於 Node.js 的運行環境。
服務器會有更大的負載需求。
25、vue 中使用了哪些設計模式?
1、工廠模式 - 傳入參數即可創建實例
虛擬 DOM 根據參數的不同返回基礎標簽的 Vnode 和組件 Vnode。
2、單例模式 - 整個程序有且僅有一個實例
vuex 和 vue-router 的插件注冊方法 install 判斷如果系統存在實例就直接返回掉。
3、發布-訂閱模式。(vue 事件機制)
4、觀察者模式。(響應式數據原理)
5、裝飾器模式(@裝飾器的用法)
6、策略模式,策略模式指對象有某個行為,但是在不同的場景中,該行為有不同的實現方案 - 比如選項的合並策略。
26、你都做過哪些 Vue 的性能優化?
這里只列舉針對 Vue 的性能優化,整個項目的性能優化是一個大工程。
- 對象層級不要過深,否則性能就會差。
- 不需要響應式的數據不要放在 data 中(可以使用 Object.freeze() 凍結數據)
- v-if 和 v-show 區分使用場景
- computed 和 watch 區分場景使用
- v-for 遍歷必須加 key,key最好是id值,且避免同時使用 v-if
- 大數據列表和表格性能優化 - 虛擬列表 / 虛擬表格
- 防止內部泄露,組件銷毀后把全局變量和時間銷毀
- 圖片懶加載
- 路由懶加載
- 異步路由
- 第三方插件的按需加載
- 適當采用 keep-alive 緩存組件
- 防抖、節流的運用
- 服務端渲染 SSR or 預渲染
27、Vue.mixin 的使用場景和原理
在日常開發中,我們經常會遇到在不同組件中經常用到一些相同或者相似的代碼,這些代碼的功能相對獨立,可以通過vue 的 mixin 功能抽離公共的業務邏輯,原理類似“對象的繼承”,當組件初始化時會調用 mergeOptions 方法進行合並,采用策略模式針對不同的屬性進行合並。當組件和混入對象含有相同名選項時,這些選項將以恰當的方式進行“合並”。
28、nextTick 使用場景和原理
nextTick 中的回調是在下次 DOM 更新循環結束之后執行的延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM。主要思路就是采用微任務優先的方式調用異步方法去執行 nextTick 包裝的方法。
29、keep-alive 使用場景和原理
keep-alive 是 Vue 內置的一個組件,可以實現組件緩存,當組件切換時不會對當前組件進行卸載。
- 常用的兩個屬性 include/exclude,允許組件有條件的進行緩存。
- 兩個生命周期 activated/deactivated,用來得知當前組件是否處理活躍狀態。
- keep-alive 運用了 LRU 算法,選擇最近最久未使用的組件予以淘汰。
擴展補充:LRU 算法是什么?
30、Vue.set 方法原理
了解 Vue 響應式原理的同學都知道在兩種情況下修改 Vue 是不會觸發視圖更新的。
1、在實例創建之后添加新的屬性到實例上(給響應式對象新增屬性)
2、直接更改數組下標來修改數組的值。
因為響應式數據 我們給對象和數組本身新增了__ob__屬性,代表的是 Observer 實例。當給對象新增不存在的屬性,首先會把新的屬性進行響應式跟蹤 然后會觸發對象 __ob__ 的dep收集到的 watcher 去更新,當修改數組索引時我們調用數組本身的 splice 方法去更新數組。
31、Vue.extend 作用和原理
官方解釋:Vue.extend 使用基礎 Vue 構造器,創建一個“子類”。參數是一個包含組件選項的對象。
其實就是一個子類構造器,是Vue組件的核心api。實現思路就是使用原型繼承的方法返回了 vue 的子類,並且利用 mergeOptions 把傳入組件的 options 就和父類的 options 進行了合並。
32、寫過自定義指令嗎?原理是什么?
指令本質上是裝飾器,是 vue 對 HTML 元素的擴展,給 HTML 元素添加自定義功能。vue 編譯 DOM 時,會找到指令對象,執行指令的相關方法。
自定義指令有五個生命周期(也叫鈎子函數),分別是 bind、inserted、update、componentUpdated、unbind
- 1、bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
- 2、inserted:被綁定元素插入父節點時調用。
- 3、update:被綁定元素所在的模板更新時調用,而不論綁定值是否變化。通過比較前后的綁定值。
- 4、componentUpdated:被綁定元素所在模板完成一次更新周期時調用。
- 5、unbind:只調用一次,指令與元素解綁時調用。
原理:
1、在生成 ast 語法樹時,遇到指令會給當前元素添加 directives 屬性
2、通過 genDirectives 生成指令代碼
3、在 patch 前將指令的鈎子提取到 cbs 中,在 patch 過程中調用對應的鈎子。
4、當執行指令對應鈎子函數時,調用對應指令定義方法。
33、Vue 修飾符有哪些?
- .stop 阻止事件繼續傳播
- .prevent 阻止標簽默認行為
- .capture 使用事件捕獲模式,即元素自身觸發的事件先在此處處理,然后才交由內部元素進行處理
- .self 只當在 event.target 是當前元素自身時觸發處理函數
- .once 事件只會觸發一次
- .passive 告訴瀏覽器你不想阻止事件的默認行為
- .lazy 通過這個修飾符,轉變為在 change 事件再同步
- .number 自動將用戶輸入值轉化為數值類型
- .trim 自動過濾用戶輸入的收尾空格
- .enter
- .tab
- .delete (捕獲“刪除”和“退格”鍵)
- .esc
- .space
- .up
- .down
- .left
- .right
- .ctrl
- .alt
- .shift
- .meta
- .left
- .right
- .middle
34、Vue 模板編譯原理
第一步是將 模板字符串轉換成 element ASTs(解析器)
第二步是對 AST 進行靜態節點標記,主要用來做虛擬 DOM 的渲染優化(優化器)
第三步是 使用element ASTs 生成 render 函數代碼字符串(代碼生成器)
35、生命周期鈎子是如何實現的
Vue 的生命周期鈎子核心實現是利用發布訂閱模式先把用戶傳入的生命周期鈎子訂閱好(內部采用數組的方法存儲)然后在創建組件實例的過程中會一次執行對應的鈎子方法(發布)
36、函數式組件使用場景和原理
函數式組件與普通組件的區別
- 1、函數式組件需要在聲明組件時指定 functional:true
- 2、不需要實例化,所以沒有this,this通過render函數的第二個參數context代替
- 3、沒有生命周期鈎子函數,不能使用計算屬性,watch
- 4、不能通過$emit對外暴露事件,調用事件只能通過context.listeners.click的方式調用外部傳入的事件
- 5、因為函數組件時沒有實例化的,所以在外部通過ref去引用組件時,實際引用的是HTMLElement
- 6、函數式組件的props可以不用顯示聲明,所以沒有在props里面聲明的屬性都會被自動隱式解析為prop,而普通的組件所有未聲明的屬性都解析到$attrs里面,並自動掛載到組件根元素上(可以通過inheritAttrs屬性禁止)
優點:1.由於函數組件不需要實例化,無狀態,沒有生命周期,所以渲染性要好於普通組件2.函數組件結構比較簡單,代碼結構更清晰
使用場景:
一個簡單的展示組件,作為容器組件使用 比如 router-view 就是一個函數式組件。 “高階組件”---用於接受一個組件為參數,返回一個被包裝過的組件。
相關代碼如下:
if (isTrue(Ctor.options.functional)) { // 帶有functional的屬性的就是函數式組件 return createFunctionalComponent(Ctor, propsData, data, context, children); } const listeners = data.on; data.on = data.nativeOn; installComponentHooks(data); // 安裝組件相關鈎子 (函數式組件沒有調用此方法,從而性能高於普通組件)
37、能說下 vue-router 中常用的路由模式和實現原理嗎?
1、location.has 的值實際就是 URL 中 # 后面的東西。它的特點在於:hash雖然出現 URL 中,但不會被包含在 HTTP 請求中,對后端完全沒有影響,因此改變 hash 不會重新加載頁面。
2、可以為 hash 的改變添加監聽事件
window.addEventListener("hashchange",funcRef,false)
每一次改變 hash (window.location.hash),都會在瀏覽器的訪問歷史中增加一個記錄,利用hash的以上特點,就可以實現前端路由“更新視圖但不重新請求頁面”的功能了
特點:兼容性好但是不美觀
利用 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
這兩個方法應用於瀏覽器的歷史記錄站,在當前已有的 back、forward、go 的基礎上,他們提供了對歷史記錄進行修改的功能。這兩個方法有個共同點:當調用他們修改瀏覽器歷史記錄棧后,雖然當前 URL 改變了,但瀏覽器不會刷新頁面,這就為單頁面應用前端路由“更新視圖但不重新請求頁面”提供了基礎
特點:雖然美觀,但是刷新會出現 404 需要后端進行配置。
38、diff 算法了解嗎?

- 1、tag 標簽不一致直接新節點替換舊節點。
- 2、tag 標簽一樣。
先替換屬性對比子元素
- 新老都有子元素,采用雙指針方式進行對比
sameVnode 判斷tag和key完全相同為同一節點,進行節點復用
頭和頭相等對比
尾和尾相等對比
頭和尾相等對比
sameVnode 的時候傳入兩個新老子節點patch(oldChild,newChild)
亂序情況 -- 上面的都不符合,先遍歷舊子節點數組形成 key值映射的map對象。
然后根據新子節點數組循環 按照key值和位置關系移動以及新增節點 最后刪除多余的舊子節點 如果移動舊節點同樣需要patch(oldChild,newChild) - 新的有子元素,老的沒有子元素。-- 直接將子元素虛擬節點轉化成真實節點插入即可。
- 新的沒有子元素,老的有子元素。 -- 直接清空 innerHtml
- 新老都有子元素,采用雙指針方式進行對比
- 3、無 tag 標簽 -- 文本節點直接比較內容是否一致
雙向綁定
什么是雙向綁定?
雙向綁定的原理?
如何實現雙向綁定?
我們先從單向綁定切入
單向綁定非常簡單,就是把 Model 綁定到 View,當我們用 JavaScript 代碼更新 Model 時,View 就會自動更新
雙向綁定就很容易聯想到了,在單向綁定的基礎上,用戶更新了 View,Model 的數據也自動被更新了,這種情況就是雙向綁定

當用戶填寫表單時,View 的狀態就被更新了,如果此時可以自動更新 Model 的狀態,那就相當於我們把 Model 和 View 做了雙向綁定
關系圖如下

- 數據層(Model):應用的數據及業務邏輯
- 視圖層(View):應用的展示效果,各類 UI 組件
- 業務邏輯層(ViewModel):框架封裝的核心,它負責將數據與視圖關聯起來
而上面的這個分層的架構方案,可以用一個專業術語進行稱呼:MVVM
這里的控制層的核心功能便是 “數據雙向綁定” 。自然,我們只需弄懂它是什么,便可以進一步了解數據綁定的原理
理解 ViewModel它的主要職責就是:
- 數據變化后更新視圖
- 視圖變化后更新數據
當然,它還有兩個主要部分組成
- 監聽器(Observer):對所有數據的屬性進行監聽
- 解析器(Compiler):對每個元素節點的指令進行掃描跟解析,根據指令模板替換數據,以及綁定相應的更新函數
- new Vue()首先執行初始化,對 data 執行響應化處理,這個過程發生 Observe 中;defineReactive 時為每⼀個 key 創建⼀個 Dep 實例
- 同時對模板執行編譯,找到其中動態綁定的數據,從 data 中獲取並初始化視圖,這個過程發生在 Compile 中;初始化視圖時讀取某個 key,例如 name1,創建⼀個 watcher1
- 同時定義⼀個更新函數和 Watcher,將來對應數據變化時 Watcher 會調用更新函數
- 由於 data 的某個 key 在⼀個視圖中可能出現多次,所以每個 key 都需要⼀個管家 Dep 來管理多個 Watcher;由於觸發 name1 的 getter 方法,便將 watcher1 添加到 name1 對應的 Dep 中
- 將來 data 中數據⼀旦發生變化,會首先找到對應的 Dep,通知所有 Watcher 執行更新函數;當 name1 更新,setter 觸發時,便可通過對應 Dep 通知其管理所有 Watcher 更新
流程圖如下:

實現思路
- defineReactive 時為每⼀個 key 創建⼀個 Dep 實例
- 初始化視圖時讀取某個 key,例如 name1,創建⼀個 watcher1
- 由於觸發 name1 的 getter 方法,便將 watcher1 添加到 name1 對應的 Dep 中
- 當 name1 更新,setter 觸發時,便可通過對應 Dep 通知其管理所有 Watcher 更新