為什么 Vuex 的 mutation 中不能做異步操作?
- Vuex中所有的狀態更新的唯一途徑都是mutation,異步操作通過 Action 來提交 mutation實現,這樣可以方便地跟蹤每一個狀態的變化,從而能夠實現一些工具幫助更好地了解我們的應用。
- 每個mutation執行完成后都會對應到一個新的狀態變更,這樣devtools就可以打個快照存下來,然后就可以實現 time-travel 了。如果mutation支持異步操作,就沒有辦法知道狀態是何時更新的,無法很好的進行狀態的追蹤,給調試帶來困難。
Vue為什么沒有類似于React中shouldComponentUpdate的生命周期?
考點: Vue的變化偵測原理
前置知識: 依賴收集、虛擬DOM、響應式系統
根本原因是Vue與React的變化偵測方式有所不同
React是pull的方式偵測變化,當React知道發生變化后,會使用Virtual Dom Diff進行差異檢測,但是很多組件實際上是肯定不會發生變化的,這個時候需要用shouldComponentUpdate進行手動操作來減少diff,從而提高程序整體的性能.
Vue是pull+push的方式偵測變化的,在一開始就知道那個組件發生了變化,因此在push的階段并不需要手動控制diff,而組件內部采用的diff方式實際上是可以引入類似于shouldComponentUpdate相關生命周期的,但是通常合理大小的組件不會有過量的diff,手動優化的價值有限,因此目前Vue并沒有考慮引入shouldComponentUpdate這種手動優化的生命周期.
Vue.js的template編譯
簡而言之,就是先轉化成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 組件間通信有哪幾種方式?
Vue 組件間通信是面試常考的知識點之一,這題有點類似于開放題,你回答出越多方法當然越加分,表明你對 Vue 掌握的越熟練。Vue 組件間通信只要指以下 3 類通信:父子組件通信、隔代組件通信、兄弟組件通信,下面我們分別介紹每種通信方式且會說明此種方法可適用于哪類組件間通信。
(1)props / $emit
適用 父子組件通信 這種方法是 Vue 組件的基礎,相信大部分同學耳聞能詳,所以此處就不舉例展開介紹。
(2)ref 與 $parent / $children
適用 父子組件通信
-
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例 -
$parent / $children
:訪問父 / 子實例
(3)EventBus ($emit / $on)
適用于 父子、隔代、兄弟組件通信 這種方法通過一個空的 Vue 實例作為中央事件總線(事件中心),用它來觸發事件和監聽事件,從而實現任何組件間的通信,包括父子、隔代、兄弟組件。
(4)$attrs/$listeners
適用于 隔代組件通信
-
$attrs
:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 ( class 和 style 除外 )。當一個組件沒有聲明任何prop
時,這里會包含所有父作用域的綁定 ( class 和 style 除外 ),并且可以通過v-bind="$attrs"
傳入內部組件。通常配合inheritAttrs
選項一起使用。 -
$listeners
:包含了父作用域中的 (不含 .native 修飾器的)v-on
事件監聽器。它可以通過v-on="$listeners"
傳入內部組件
(5)provide / inject
適用于 隔代組件通信 祖先組件中通過 provider
來提供變量,然后在子孫組件中通過 inject
來注入變量。 provide / inject API
主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間建立了一種主動提供與依賴注入的關系。 (6)Vuex
適用于 父子、隔代、兄弟組件通信 Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。每一個 Vuex 應用的核心就是 store(倉庫)。“store” 基本上就是一個容器,它包含著你的應用中大部分的狀態 ( state )。
- Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
- 改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化。
v-model 的原理?
我們在 vue 項目中主要使用 v-model 指令在表單 input、textarea、select 等元素上創建雙向數據綁定,我們知道 v-model 本質上不過是語法糖,v-model 在內部為不同的輸入元素使用不同的屬性并拋出不同的事件:
- text 和 textarea 元素使用 value 屬性和 input 事件;
- checkbox 和 radio 使用 checked 屬性和 change 事件;
- select 字段將 value 作為 prop 并將 change 作為事件。
以 input 表單元素為例:
<input v-model='something'>
相當于
<input v-bind:value="something" v-on:input="something = $event.target.value">
復制代碼
如果在自定義組件中,v-model 默認會利用名為 value 的 prop 和名為 input 的事件,如下所示:
父組件:
<ModelChild v-model="message"></ModelChild>
子組件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小紅')
},
},
Vue 初始化頁面閃動問題如何解決?
出現該問題是因為在 Vue 代碼尚未被解析之前,尚無法控制頁面中 DOM 的顯示,所以會看見模板字符串等代碼。
解決方案是,在 css 代碼中添加 v-cloak 規則,同時在待編譯的標簽上添加 v-cloak 屬性:
[v-cloak] { display: none; }
<div v-cloak>
{{ message }}
</div>
Vue 組件間通信有哪幾種方式?
Vue 組件間通信是面試常考的知識點之一,這題有點類似于開放題,你回答出越多方法當然越加分,表明你對 Vue 掌握的越熟練。Vue 組件間通信只要指以下 3 類通信:父子組件通信、隔代組件通信、兄弟組件通信,下面我們分別介紹每種通信方式且會說明此種方法可適用于哪類組件間通信。
(1)props / $emit
適用 父子組件通信
這種方法是 Vue 組件的基礎,相信大部分同學耳聞能詳,所以此處就不舉例展開介紹。
(2)ref
與 $parent / $children
適用 父子組件通信
-
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例 -
$parent
/$children
:訪問父 / 子實例
(3)EventBus ($emit / $on)
適用于 父子、隔代、兄弟組件通信
這種方法通過一個空的 Vue 實例作為中央事件總線(事件中心),用它來觸發事件和監聽事件,從而實現任何組件間的通信,包括父子、隔代、兄弟組件。
(4)$attrs
/$listeners
適用于 隔代組件通信
-
$attrs
:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 ( class 和 style 除外 )。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 ( class 和 style 除外 ),并且可以通過v-bind="$attrs"
傳入內部組件。通常配合 inheritAttrs 選項一起使用。 -
$listeners
:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過v-on="$listeners"
傳入內部組件
(5)provide / inject
適用于 隔代組件通信
祖先組件中通過 provider 來提供變量,然后在子孫組件中通過 inject 來注入變量。 provide / inject API 主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間建立了一種主動提供與依賴注入的關系。
(6)Vuex 適用于 父子、隔代、兄弟組件通信
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。每一個 Vuex 應用的核心就是 store(倉庫)。“store” 基本上就是一個容器,它包含著你的應用中大部分的狀態 ( state )。
- Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
- 改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化。
簡述 mixin、extends 的覆蓋邏輯
(1)mixin 和 extends mixin 和 extends均是用于合并、拓展組件的,兩者均通過 mergeOptions 方法實現合并。
- mixins 接收一個混入對象的數組,其中混入對象可以像正常的實例對象一樣包含實例選項,這些選項會被合并到最終的選項中。Mixin 鉤子按照傳入順序依次調用,并在調用組件自身的鉤子之前被調用。
- extends 主要是為了便于擴展單文件組件,接收一個對象或構造函數。
(2)mergeOptions 的執行過程
- 規范化選項(normalizeProps、normalizelnject、normalizeDirectives)
- 對未合并的選項,進行判斷
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}
- 合并處理。根據一個通用 Vue 實例所包含的選項進行分類逐一判斷合并,如 props、data、 methods、watch、computed、生命周期等,將合并結果存儲在新定義的 options 對象里。
- 返回合并結果 options。
過濾器的作用,如何實現一個過濾器
根據過濾器的名稱,過濾器是用來過濾數據的,在Vue中使用filters
來過濾數據,filters
不會修改數據,而是過濾數據,改變用戶看到的輸出(計算屬性 computed
,方法 methods
都是通過修改數據來處理數據格式的輸出顯示)。
使用場景:
- 需要格式化數據的情況,比如需要處理時間、價格等數據格式的輸出 / 顯示。
- 比如后端返回一個 年月日的日期字符串,前端需要展示為 多少天前 的數據格式,此時就可以用
fliters
過濾器來處理數據。
過濾器是一個函數,它會把表達式中的值始終當作函數的第一個參數。過濾器用在插值表達式 {{ }}
和 v-bind
表達式 中,然后放在操作符“ |
”后面進行指示。
例如,在顯示金額,給商品價格添加單位:
<li>商品價格:{{item.price | filterPrice}}</li>
filters: {
filterPrice (price) {
return price ? ('¥' + price) : '--'
}
}
復制代碼
Vue-router 路由有哪些模式?
一般有兩種模式:
(1)hash 模式:后面的 hash 值的變化,瀏覽器既不會向服務器發出請求,瀏覽器也不會刷新,每次 hash 值的變化會觸發 hashchange 事件。
(2)history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。這兩個方法應用于瀏覽器的歷史記錄棧,在當前已有的 back、forward、go 的基礎之上,它們提供了對歷史記錄進行修改的功能。只是當它們執行修改時,雖然改變了當前的 URL,但瀏覽器不會立即向后端發送請求。
Vue 中的 key 到底有什么用?
key 是給每一個 vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準確、更快速 (對于簡單列表頁渲染來說 diff 節點也更快,但會產生一些隱藏的副作用,比如可能不會產生過渡效果,或者在某些節點有綁定數據(表單)狀態,會出現狀態錯位。)
diff 算法的過程中,先會進行新舊節點的首尾交叉對比,當無法匹配的時候會用新節點的 key 與舊節點進行比對,從而找到相應舊節點.
更準確 : 因為帶 key 就不是就地復用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地復用的情況。所以會更加準確,如果不加 key,會導致之前節點的狀態被保留下來,會產生一系列的 bug。
更快速 : key 的唯一性可以被 Map 數據結構充分利用,相比于遍歷查找的時間復雜度 O(n),Map 的時間復雜度僅僅為 O(1)
Vue模版編譯原理知道嗎,能簡單說一下嗎?
簡單說,Vue的編譯過程就是將template
轉化為render
函數的過程。會經歷以下階段:
- 生成AST樹
- 優化
- codegen
首先解析模版,生成AST語法樹
(一種用JavaScript對象的形式來描述整個模板)。 使用大量的正則表達式對模板進行解析,遇到標簽、文本的時候都會執行對應的鉤子進行相關處理。
Vue的數據是響應式的,但其實模板中并不是所有的數據都是響應式的。有一些數據首次渲染后就不會再變化,對應的DOM也不會變化。那么優化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)我們就可以跳過對它們的比對
,對運行時的模板起到很大的優化作用。
編譯的最后一步是將優化后的AST樹轉換為可執行的代碼
。
那vue中是如何檢測數組變化的呢?
數組就是使用object.defineProperty
重新定義數組的每一項,那能引起數組變化的方法我們都是知道的,pop
、push
、shift
、unshift
、splice
、sort
、reverse
這七種,只要這些方法執行改了數組內容,我就更新內容就好了,是不是很好理解。
- 是用來函數劫持的方式,重寫了數組方法,具體呢就是更改了數組的原型,更改成自己的,用戶調數組的一些方法的時候,走的就是自己的方法,然后通知視圖去更新。
- 數組里每一項可能是對象,那么我就是會對數組的每一項進行觀測,(且只有數組里的對象才能進行觀測,觀測過的也不會進行觀測)
vue3:改用proxy
,可直接監聽對象數組的變化。
寫過自定義指令嗎 原理是什么
指令本質上是裝飾器,是 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.當執行指令對應鉤子函數時,調用對應指令定義的方法
vue和react的區別
=> 相同點:
1. 數據驅動頁面,提供響應式的試圖組件
2. 都有virtual DOM,組件化的開發,通過props參數進行父子之間組件傳遞數據,都實現了webComponents規范
3. 數據流動單向,都支持服務器的渲染SSR
4. 都有支持native的方法,react有React native, vue有wexx
=> 不同點:
1.數據綁定:Vue實現了雙向的數據綁定,react數據流動是單向的
2.數據渲染:大規模的數據渲染,react更快
3.使用場景:React配合Redux架構適合大規模多人協作復雜項目,Vue適合小快的項目
4.開發風格:react推薦做法jsx + inline style把html和css都寫在js了
vue是采用webpack + vue-loader單文件組件格式,html, js, css同一個文件
描述下Vue自定義指令
在 Vue2.0 中,代碼復用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。
一般需要對DOM元素進行底層操作時使用,盡量只用來操作 DOM展示,不修改內部的值。當使用自定義指令直接修改 value 值時綁定v-model的值也不會同步更新;如必須修改可以在自定義指令中使用keydown事件,在vue組件中使用 change事件,回調中修改vue數據;
(1)自定義指令基本內容
全局定義:
Vue.directive("focus",{})
局部定義:
directives:{focus:{}}
-
鉤子函數:指令定義對象提供鉤子函數
o bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
o inSerted:被綁定元素插入父節點時調用(僅保證父節點存在,但不一定已被插入文檔中)。
o update:所在組件的VNode更新時調用,但是可能發生在其子VNode更新之前調用。指令的值可能發生了改變,也可能沒有。但是可以通過比較更新前后的值來忽略不必要的模板更新。
o ComponentUpdate:指令所在組件的 VNode及其子VNode全部更新后調用。
o unbind:只調用一次,指令與元素解綁時調用。
-
鉤子函數參數
o el:綁定元素o bing: 指令核心對象,描述指令全部信息屬性
o name
o value
o oldValue
o expression
o arg
o modifers
o vnode 虛擬節點
o oldVnode:上一個虛擬節點(更新鉤子函數中才有用)
(2)使用場景
普通DOM元素進行底層操作的時候,可以使用自定義指令
自定義指令是用來操作DOM的。盡管Vue推崇數據驅動視圖的理念,但并非所有情況都適合數據驅動。自定義指令就是一種有效的補充和擴展,不僅可用于定義任何的DOM操作,并且是可復用的。
(3)使用案例
初級應用:
- 鼠標聚焦
- 下拉菜單
- 相對時間轉換
- 滾動動畫
高級應用:
- 自定義指令實現圖片懶加載
- 自定義指令集成第三方插件
如何保存頁面的當前的狀態
既然是要保持頁面的狀態(其實也就是組件的狀態),那么會出現以下兩種情況:
- 前組件會被卸載
- 前組件不會被卸載
那么可以按照這兩種情況分別得到以下方法:
組件會被卸載:
(1)將狀態存儲在LocalStorage / SessionStorage
只需要在組件即將被銷毀的生命周期 componentWillUnmount
(react)中在 LocalStorage / SessionStorage 中把當前組件的 state 通過 JSON.stringify() 儲存下來就可以了。在這里面需要注意的是組件更新狀態的時機。
比如從 B 組件跳轉到 A 組件的時候,A 組件需要更新自身的狀態。但是如果從別的組件跳轉到 B 組件的時候,實際上是希望 B 組件重新渲染的,也就是不要從 Storage 中讀取信息。所以需要在 Storage 中的狀態加入一個 flag 屬性,用來控制 A 組件是否讀取 Storage 中的狀態。
優點:
- 兼容性好,不需要額外庫或工具。
- 簡單快捷,基本可以滿足大部分需求。
缺點:
- 狀態通過 JSON 方法儲存(相當于深拷貝),如果狀態中有特殊情況(比如 Date 對象、Regexp 對象等)的時候會得到字符串而不是原來的值。(具體參考用 JSON 深拷貝的缺點)
- 如果 B 組件后退或者下一頁跳轉并不是前組件,那么 flag 判斷會失效,導致從其他頁面進入 A 組件頁面時 A 組件會重新讀取 Storage,會造成很奇怪的現象
(2)路由傳值
通過 react-router 的 Link 組件的 prop —— to 可以實現路由間傳遞參數的效果。
在這里需要用到 state 參數,在 B 組件中通過 history.location.state 就可以拿到 state 值,保存它。返回 A 組件時再次攜帶 state 達到路由狀態保持的效果。
優點:
- 簡單快捷,不會污染 LocalStorage / SessionStorage。
- 可以傳遞 Date、RegExp 等特殊對象(不用擔心 JSON.stringify / parse 的不足)
缺點:
- 如果 A 組件可以跳轉至多個組件,那么在每一個跳轉組件內都要寫相同的邏輯。
組件不會被卸載:
(1)單頁面渲染
要切換的組件作為子組件全屏渲染,父組件中正常儲存頁面狀態。
優點:
- 代碼量少
- 不需要考慮狀態傳遞過程中的錯誤
缺點:
- 增加 A 組件維護成本
- 需要傳入額外的 prop 到 B 組件
- 無法利用路由定位頁面
除此之外,在Vue中,還可以是用keep-alive來緩存頁面,當組件在keep-alive內被切換時組件的activated、deactivated這兩個生命周期鉤子函數會被執行
被包裹在keep-alive中的組件的狀態將會被保留:
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>
復制代碼
router.js
{
path: '/',
name: 'xxx',
component: ()=>import('../src/views/xxx.vue'),
meta:{
keepAlive: true // 需要被緩存
}
},
復制代碼
MVC 和 MVVM 區別
MVC
MVC 全名是 Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典范
- Model(模型):是應用程序中用于處理應用程序數據邏輯的部分。通常模型對象負責在數據庫中存取數據
- View(視圖):是應用程序中處理數據顯示的部分。通常視圖是依據模型數據創建的
- Controller(控制器):是應用程序中處理用戶交互的部分。通常控制器負責從視圖讀取數據,控制用戶輸入,并向模型發送數據
MVC 的思想:一句話描述就是 Controller 負責將 Model 的數據用 View 顯示出來,換句話說就是在 Controller 里面把 Model 的數據賦值給 View。
MVVM
MVVM 新增了 VM 類
- ViewModel 層:做了兩件事達到了數據的雙向綁定 一是將【模型】轉化成【視圖】,即將后端傳遞的數據轉化成所看到的頁面。實現的方式是:數據綁定。二是將【視圖】轉化成【模型】,即將所看到的頁面轉化成后端的數據。實現的方式是:DOM 事件監聽。
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。