(學習視訊分享:)
Object.defineProperty 本身有一定的監控到陣列下標變化的能力,但是在 Vue 中,從效能/體驗的價效比考慮,尤大大就棄用了這個特性(Vue 為什麼不能檢測陣列變動 )。為了解決這個問題,經過 vue 內部處理後可以使用以下幾種方法來監聽陣列
push(); pop(); shift(); unshift(); splice(); sort(); reverse();
由於只針對了以上 7 種方法進行了 hack 處理,所以其他陣列的屬性也是檢測不到的,還是具有一定的侷限性。
Object.defineProperty 只能劫持物件的屬性,因此我們需要對每個物件的每個屬性進行遍歷。Vue 2.x 裡,是通過 遞迴 + 遍歷 data 物件來實現對資料的監控的,如果屬性值也是物件那麼需要深度遍歷,顯然如果能劫持一個完整的物件是才是更好的選擇。
Proxy 可以劫持整個物件,並返回一個新的物件。Proxy 不僅可以代理物件,還可以代理陣列。還可以代理動態增加的屬性。
如果不使用 key,Vue 會使用一種最大限度減少動態元素並且儘可能的嘗試就地修改/複用相同型別元素的演演算法。key 是為 Vue 中 vnode 的唯一標記,通過這個 key,我們的 diff 操作可以更準確、更快速
更準確:因為帶 key 就不是就地複用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地複用的情況。所以會更加準確。
更快速:利用 key 的唯一性生成 map 物件來獲取對應節點,比遍歷方式更快
涉及到Vue中的模板編譯原理,主要過程:
將模板轉換成 ast
樹, ast
用物件來描述真實的JS語法(將真實DOM轉換成虛擬DOM)
優化樹
將 ast
樹生成程式碼
Vue
是元件級更新,如果不採用非同步更新,那麼每次更新資料都會對當前元件進行重新渲染,所以為了效能, Vue
會在本輪資料更新後,在非同步更新檢視。核心思想 nextTick
。
dep.notify()
通知 watcher進行更新, subs[i].update
依次呼叫 watcher 的 update
, queueWatcher
將watcher 去重放入佇列, nextTick( flushSchedulerQueue
)在下一tick中重新整理watcher佇列(非同步)。
物件為參照型別,當複用元件時,由於資料物件都指向同一個data物件,當在一個元件中修改data時,其他重用的元件中的data會同時被修改;而使用返回物件的函數,由於每次返回的都是一個新物件(Object的範例),參照地址不同,則不會出現這個問題。
MVC
MVC 全名是 Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範
MVC 的思想:一句話描述就是 Controller 負責將 Model 的資料用 View 顯示出來,換句話說就是在 Controller 裡面把 Model 的資料賦值給 View。
MVVM
MVVM 新增了 VM 類
MVVM 與 MVC 最大的區別就是:它實現了 View 和 Model 的自動同步,也就是當 Model 的屬性改變時,我們不用再自己手動操作 Dom 元素,來改變 View 的顯示,而是改變屬性後該屬性對應 View 層顯示會自動改變(對應Vue資料驅動的思想)
整體看來,MVVM 比 MVC 精簡很多,不僅簡化了業務與介面的依賴,還解決了資料頻繁更新的問題,不用再用選擇器操作 DOM 元素。因為在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也觀察不到 View,這種低耦合模式提高程式碼的可重用性
注意:Vue 並沒有完全遵循 MVVM 的思想 這一點官網自己也有說明
那麼問題來了 為什麼官方要說 Vue 沒有完全遵循 MVVM 思想呢?
- 嚴格的 MVVM 要求 View 不能和 Model 直接通訊,而 Vue 提供了$refs 這個屬性,讓 Model 可以直接操作 View,違反了這一規定,所以說 Vue 沒有完全遵循 MVVM。
1)Vue為什麼要用vm.$set() 解決物件新增屬性不能響應的問題
Vue使用了Object.defineProperty實現雙向資料繫結
在初始化範例時對屬性執行 getter/setter 轉化
屬性必須在data物件上存在才能讓Vue將它轉換為響應式的(這也就造成了Vue無法檢測到物件屬性的新增或刪除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下來我們看看框架本身是如何實現的呢?
Vue 原始碼位置:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any { // target 為陣列 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改陣列的長度, 避免索引>陣列長度導致splcie()執行有誤 target.length = Math.max(target.length, key) // 利用陣列的splice變異方法觸發響應式 target.splice(key, 1, val) return val } // key 已經存在,直接修改屬性值 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // target 本身就不是響應式資料, 直接賦值 if (!ob) { target[key] = val return val } // 對屬性進行響應式處理 defineReactive(ob.value, key, val) ob.dep.notify() return val }
我們閱讀以上原始碼可知,vm.$set 的實現原理是:
如果目標是陣列,直接使用陣列的 splice 方法觸發相應式;
如果目標是物件,會先判讀屬性是否存在、物件是否是響應式,
最終如果要對屬性進行響應式處理,則是通過呼叫 defineReactive 方法進行響應式處理
defineReactive 方法就是 Vue 在初始化物件時,給物件屬性採用 Object.defineProperty 動態新增 getter 和 setter 的功能所呼叫的方法
Vue3.x 改用 Proxy 替代 Object.defineProperty。因為 Proxy 可以直接監聽物件和陣列的變化,並且有多達 13 種攔截方法。
相關程式碼如下
import { mutableHandlers } from "./baseHandlers"; // 代理相關邏輯 import { isObject } from "./util"; // 工具方法 export function reactive(target) { // 根據不同引數建立不同響應式物件 return createReactiveObject(target, mutableHandlers); } function createReactiveObject(target, baseHandler) { if (!isObject(target)) { return target; } const observed = new Proxy(target, baseHandler); return observed; } const get = createGetter(); const set = createSetter(); function createGetter() { return function get(target, key, receiver) { // 對獲取的值進行放射 const res = Reflect.get(target, key, receiver); console.log("屬性獲取", key); if (isObject(res)) { // 如果獲取的值是物件型別,則返回當前物件的代理物件 return reactive(res); } return res; }; } function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { console.log("屬性新增", key, value); } else if (hasChanged(value, oldValue)) { console.log("屬性值被修改", key, value); } return result; }; } export const mutableHandlers = { get, // 當獲取屬性時呼叫此方法 set, // 當修改屬性時呼叫此方法 };
簡單說,Vue的編譯過程就是將template
轉化為render
函數的過程。會經歷以下階段:
首先解析模版,生成AST語法樹
(一種用JavaScript物件的形式來描述整個模板)。 使用大量的正規表示式對模板進行解析,遇到標籤、文字的時候都會執行對應的勾點進行相關處理。
Vue的資料是響應式的,但其實模板中並不是所有的資料都是響應式的。有一些資料首次渲染後就不會再變化,對應的DOM也不會變化。那麼優化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)我們就可以跳過對它們的比對
,對執行時的模板起到很大的優化作用。
編譯的最後一步是將優化後的AST樹轉換為可執行的程式碼
。
優點:
缺點:
不會立即同步執行重新渲染。Vue 實現響應式並不是資料發生變化之後 DOM 立即變化,而是按一定的策略進行 DOM 的更新。Vue 在更新 DOM 時是非同步執行的。只要偵聽到資料變化, Vue 將開啟一個佇列,並緩衝在同一事件迴圈中發生的所有資料變更。
如果同一個watcher被多次觸發,只會被推入到佇列中一次。這種在緩衝時去除重複資料對於避免不必要的計算和 DOM 操作是非常重要的。然後,在下一個的事件迴圈tick中,Vue 重新整理佇列並執行實際(已去重的)工作。
時間複雜度: 個樹的完全 diff
演演算法是一個時間複雜度為 O(n*3)
,vue進行優化轉化成 O(n)
。
理解:
最小量更新, key
很重要。這個可以是這個節點的唯一標識,告訴 diff
演演算法,在更改前後它們是同一個DOM節點
v-for
為什麼要有 key
,沒有 key
會暴力複用,舉例子的話隨便說一個比如移動節點或者增加節點(修改DOM),加 key
只會移動減少操作DOM。diff演演算法的優化策略:四種命中查詢,四個指標
--- 問完上面這些如果都能很清楚的話,基本O了 ---
以下的這些簡單的概念,你肯定也是沒有問題的啦?
kb
;angular
的特點,在資料操作方面更為簡單;react
的優點,實現了 html
的封裝和重用,在構建單頁面應用方面有著獨特的優勢;dom
操作是非常耗費效能的,不再使用原生的 dom
操作節點,極大解放 dom
操作,但具體操作的還是 dom
不過是換了另一種方式;react
而言,同樣是操作虛擬 dom
,就效能而言, vue
存在很大的優勢。路由勾點的執行流程, 勾點函數種類有:全域性守衛、路由守衛、元件守衛
完整的導航解析流程:
導航被觸發。
在失活的元件裡呼叫 beforeRouteLeave 守衛。
呼叫全域性的 beforeEach 守衛。
在重用的元件裡呼叫 beforeRouteUpdate 守衛 (2.2+)。
在路由設定裡呼叫 beforeEnter。
解析非同步路由元件。
在被啟用的元件裡呼叫 beforeRouteEnter。
呼叫全域性的 beforeResolve 守衛 (2.5+)。
導航被確認。
呼叫全域性的 afterEach 勾點。
觸發 DOM 更新。
呼叫 beforeRouteEnter 守衛中傳給 next 的回撥函數,建立好的元件範例會作為回撥函數的引數傳入。
簡而言之,就是先轉化成AST樹,再得到的render函數返回VNode(Vue的虛擬DOM節點),詳細步驟如下:
首先,通過compile編譯器把template編譯成AST語法樹(abstract syntax tree 即 原始碼的抽象語法結構的樹狀表現形式),compile是createCompiler的返回值,createCompiler是用以建立編譯器的。另外compile還負責合併option。
然後,AST會經過generate(將AST語法樹轉化成render funtion字串的過程)得到render函數,render的返回值是VNode,VNode是Vue的虛擬DOM節點,裡面有(標籤名、子節點、文字等等)
Vue 實現響應式並不是在資料發生後立即更新 DOM,使用 vm.$nextTick
是在下次 DOM 更新迴圈結束之後立即執行延遲迴撥。在修改資料之後使用,則可以在回撥中獲取更新後的 DOM。
什麼時候被呼叫?
watch/event
事件回撥。無 $el
. render
函數首次被呼叫vm.$el
替換,並掛載到範例上去之後呼叫改勾點。每個生命週期內部可以做什麼?
ajax放在哪個生命週期?:一般放在 mounted
中,保證邏輯統一性,因為生命週期是同步執行的, ajax
是非同步執行的。單數伺服器端渲染 ssr
同一放在 created
中,因為伺服器端渲染不支援 mounted
方法。 什麼時候使用beforeDestroy?:當前頁面使用 $on
,需要解綁事件。清楚定時器。解除事件繫結, scroll mousemove
。
受現代 JavaScript 的限制 ,Vue 無法檢測到物件屬性的新增或刪除。由於 Vue 會在初始化範例時對屬性執行 getter/setter 轉化,所以屬性必須在 data 物件上存在才能讓 Vue 將它轉換為響應式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
來實現為物件新增響應式屬性,那框架本身是如何實現的呢?
我們檢視對應的 Vue 原始碼:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any { // target 為陣列 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改陣列的長度, 避免索引>陣列長度導致splcie()執行有誤 target.length = Math.max(target.length, key) // 利用陣列的splice變異方法觸發響應式 target.splice(key, 1, val) return val } // key 已經存在,直接修改屬性值 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // target 本身就不是響應式資料, 直接賦值 if (!ob) { target[key] = val return val } // 對屬性進行響應式處理 defineReactive(ob.value, key, val) ob.dep.notify() return val }
我們閱讀以上原始碼可知,vm.$set 的實現原理是:
(學習視訊分享:、)
以上就是2022年vue高頻面試題分享(附答案分析)的詳細內容,更多請關注TW511.COM其它相關文章!