vue 面試題總結
1. 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同一個文件
2. 介紹下MVVM(數據的雙向綁定
- M: model數據模型
- V: view 界面
- MV:作爲橋樑負責溝通view跟model
-
只關心數據的流傳,減少強耦合性。最關鍵的就是數據的雙向綁定
關鍵步驟: 1.實現數據監聽器Observer,用object.defineProperty()重寫數據的get/set。 值更新就在set中通知訂閱者更新數據 2.實現模板編譯compile,深度遍歷dom樹,對每個元素節點的指令模板替換數據以及訂閱數據 3.實現watch用於連接Observer和compile,能夠訂閱並接受每一個屬性的變動的通知, 執行指令綁定的相應的回調函數,從而更新數據 4.實現一個訂閱器 Dep:訂閱器採用 發佈-訂閱 設計模式, 用來收集訂閱者 Watcher,對監聽器 Observer 和 訂閱者 Watcher 進行統一管理。
- mvc和mvvm其實區別並不大。都是一種設計思想。主要就是mvc中Controller演變成mvvm中的viewModel。mvvm主要解決了mvc中大量的DOM 操作使頁面渲染性能降低,
加載速度變慢,影響用戶體驗。和當 Model 頻繁發生變化,開發者需要主動更新到View。
3. 生命週期函數
new Vue(創建一個Vue對象)--> beforeCreate --> observer Data(開始監控data對象數據變化) -->
init event(vue內部初始化事件)
--> created() --> compile(編譯模板,把data裏面的數據和模板生成html) -->
beforeMount(還沒有生成HTML到元素上) -->
mounted(掛載完成,也就是模板中的html渲染到了html頁面中) --> beforeUpdate (Vritual Dom) -->
updated --> beforeDestroy --> destroyed
- 1.ajax請求最好放在created裏面,頁面可以訪問到this了
- 2.關於dom的操作要放在mounted裏面,在mounted前面還沒有生成dom
- 3.每次進入/離開組件都要做一些事情,用什麼鉤子函數:
- 不緩存:進入的時候可以用created和mounted鉤子,離開的時候可以使用beforedDestory(可以訪問this)和destoryed
- 緩存:緩存了組件之後,在次進入組件不會觸發beforeCreate,created, beforeMount,mounted
如果你想每次進入組件都做一些事情的話,你可以放在activated進入緩存組件的鉤子中
4. 說說你對 SPA 單頁面的理解,它的優缺點分別是什麼?
-
SPA( single-page application )僅在 Web 頁面初始化時加載相應的 HTML、JavaScript 和 CSS。一旦頁面加載完成,SPA 不會因爲用戶的操作而進行頁面的重新加載或跳轉;取而代之的是利用路由機制實現 HTML 內容的變換,UI 與用戶的交互,避免頁面的重新加載。
-
優點:
- 用戶體驗好、快,內容的改變不需要重新加載整個頁面,避免了不必要的跳轉和重複渲染;
基於上面一點,SPA 相對對服務器壓力小;
前後端職責分離,架構清晰,前端進行交互邏輯,後端負責數據處理;
- 用戶體驗好、快,內容的改變不需要重新加載整個頁面,避免了不必要的跳轉和重複渲染;
-
缺點:
- 初次加載耗時多:爲實現單頁 Web 應用功能及顯示效果,需要在加載頁面的時候將 JavaScript、CSS 統一加載,部分頁面按需加載;
前進後退路由管理:由於單頁應用在一個頁面中顯示所有的內容,所以不能使用瀏覽器的前進後退功能,所有的頁面切換需要自己建立堆棧管理;
SEO 難度較大:由於所有的內容都在一個頁面中動態替換顯示,所以在 SEO 上其有着天然的弱勢。
- 初次加載耗時多:爲實現單頁 Web 應用功能及顯示效果,需要在加載頁面的時候將 JavaScript、CSS 統一加載,部分頁面按需加載;
5. Vue的SPA 如何優化加載速度
1.減少入口文件體積
2.靜態資源本地緩存
3.開啓Gzip壓縮
4.使用SSR,nuxt.js
6. v-show 與 v-if 有什麼區別?
- v-if 是真正的條件渲染,因爲它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷燬和重建;也是惰性的:如果在初始渲染時條件爲假,則什麼也不做——直到條件第一次變爲真時,纔會開始渲染條件塊。
- v-show 就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 的 “display” 屬性進行切換。
- 所以,v-if 適用於在運行時很少改變條件,不需要頻繁切換條件的場景;v-show 則適用於需要非常頻繁切換條件的場景。
7. 怎樣理解 Vue 的單向數據流?
所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新爲最新的值。這意味着你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。子組件想修改時,只能通過 $emit 派發一個自定義事件,父組件接收到後,由父組件修改。
有兩種常見的試圖改變一個 prop 的情形 :
- 這個 prop 用來傳遞一個初始值;這個子組件接下來希望將其作爲一個本地的 prop 數據來使用。 在這種情況下,最好定義一個本地的 data 屬性並將這個 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 這個 prop 以一種原始的值傳入且需要進行轉換。 在這種情況下,最好使用這個 prop 的值來定義一個計算屬性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
8. computed 和 watch 的區別和運用的場景?
- computed: 是計算屬性,依賴其它屬性值,並且 computed 的值有緩存,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時纔會重新計算 computed 的值;
- watch: 更多的是「觀察」的作用,類似於某些數據的監聽回調 ,每當監聽的數據變化時都會執行回調進行後續操作;
運用場景:
當我們需要進行數值計算,並且依賴於其它數據時,應該使用 computed,因爲可以利用 computed 的緩存特性,避免每次獲取值時,都要重新計算;
當我們需要在數據變化時執行異步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行異步操作 ( 訪問一個 API ),限制我們執行該操作的頻率,並在我們得到最終結果前,設置中間狀態。這些都是計算屬性無法做到的。
9. 直接給一個數組項賦值,Vue 能檢測到變化嗎?
由於 JavaScript 的限制,Vue 不能檢測到以下數組的變動:
- 當你利用索引直接設置一個數組項時,例如:
vm.items[indexOfItem] = newValue
- 當你修改數組的長度時,例如:
vm.items.length = newLength
爲了解決第一個問題,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一個別名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
爲了解決第二個問題,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
10. Vue 的父組件和子組件生命週期鉤子函數執行順序?
Vue 的父組件和子組件生命週期鉤子函數執行順序可以歸類爲以下 4 部分:
-
加載渲染過程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted -
子組件更新過程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated -
父組件更新過程
父 beforeUpdate -> 父 updated -
銷燬過程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
11. 在哪個生命週期內調用異步請求?
可以在鉤子函數 created、beforeMount、mounted 中進行調用,因爲在這三個鉤子函數中,data 已經創建,可以將服務端端返回的數據進行賦值。但是本人推薦在 created 鉤子函數中調用異步請求,因爲在 created 鉤子函數中調用異步請求有以下優點:
- 能更快獲取到服務端數據,減少頁面 loading 時間;
- ssr 不支持 beforeMount 、mounted 鉤子函數,所以放在 created 中有助於一致性;
12. 在什麼階段才能訪問操作DOM?
在鉤子函數 mounted 被調用前,Vue 已經將編譯好的模板掛載到頁面上,所以在 mounted 中可以訪問操作 DOM。vue 具體的生命週期示意圖可以參見如下,理解了整個生命週期各個階段的操作,關於生命週期相關的面試題就難不倒你了。
13. 父組件可以監聽到子組件的生命週期嗎?
比如有父組件 Parent 和子組件 Child,如果父組件監聽到子組件掛載 mounted 就做一些邏輯處理,可以通過以下寫法實現:
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
以上需要手動通過 $emit 觸發父組件的事件,更簡單的方式可以在父組件引用子組件時通過 @hook 來監聽即可,如下所示:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父組件監聽到 mounted 鉤子函數 ...');
},
// Child.vue
mounted(){
console.log('子組件觸發 mounted 鉤子函數 ...');
},
// 以上輸出順序爲:
// 子組件觸發 mounted 鉤子函數 ...
// 父組件監聽到 mounted 鉤子函數 ...
當然 @hook 方法不僅僅是可以監聽 mounted,其它的生命週期事件,例如:created,updated 等都可以監聽。
14. 爲什麼組件中的 data 必須是一個函數,然後 return 一個對象,而 new Vue 實例裏,data 可以直接是一個對象?
// data
data() {
return {
message: "子組件",
childName:this.name
}
}
// new Vue
new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})
因爲組件是用來複用的,且 JS 裏對象是引用關係,如果組件中 data 是一個對象,那麼這樣作用域沒有隔離,子組件中的 data 屬性值會相互影響,如果組件中 data 選項是一個函數,那麼每個實例可以維護一份被返回對象的獨立的拷貝,組件實例之間的 data 屬性值不會互相影響;而 new Vue 的實例,是不會被複用的,因此不存在引用對象的問題。
15. 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', '小紅')
},
},
16. 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。這樣使得我們可以方便地跟蹤每一個狀態的變化。
17. 你使用過 Vuex 嗎?
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。每一個 Vuex 應用的核心就是 store(倉庫)。“store” 基本上就是一個容器,它包含着你的應用中大部分的狀態 ( state )。
(1)Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地得到高效更新。
(2)改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化。
主要包括以下幾個模塊:
- State:定義了應用狀態的數據結構,可以在這裏設置默認的初始狀態。
- Getter:允許組件從 Store 中獲取數據,mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性。
- Mutation:是唯一更改 store 中狀態的方法,且必須是同步函數。
- Action:用於提交 mutation,而不是直接變更狀態,可以包含任意異步操作。
- Module:允許將單一的 Store 拆分爲多個 store 且同時保存在單一的狀態樹中。
18. 使用過 Vue SSR 嗎?說說 SSR?
Vue.js 是構建客戶端應用程序的框架。默認情況下,可以在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染爲服務端的 HTML 字符串,將它們直接發送到瀏覽器,最後將這些靜態標記"激活"爲客戶端上完全可交互的應用程序。
即:SSR大致的意思就是vue在客戶端將標籤渲染成的整個 html 片段的工作在服務端完成,服務端形成的html 片段直接返回給客戶端這個過程就叫做服務端渲染。
服務端渲染 SSR 的優缺點如下:
(1)服務端渲染的優點:
- 更好的 SEO: 因爲 SPA 頁面的內容是通過 Ajax 獲取,而搜索引擎爬取工具並不會等待 Ajax 異步完成後再抓取頁面內容,所以在 SPA 中是抓取不到頁面通過 Ajax 獲取到的內容;而 SSR 是直接由服務端返回已經渲染好的頁面(數據已經包含在頁面中),所以搜索引擎爬取工具可以抓取渲染好的頁面;
- 更快的內容到達時間(首屏加載更快): SPA 會等待所有 Vue 編譯後的 js 文件都下載完成後,纔開始進行頁面的渲染,文件下載等需要一定的時間等,所以首屏渲染需要一定的時間;SSR 直接由服務端渲染好頁面直接返回顯示,無需等待下載 js 文件及再去渲染等,所以 SSR 有更快的內容到達時間;
(2) 服務端渲染的缺點:
- 更多的開發條件限制: 例如服務端渲染只支持 beforCreate 和 created 兩個鉤子函數,這會導致一些外部擴展庫需要特殊處理,才能在服務端渲染應用程序中運行;並且與可以部署在任何靜態文件服務器上的完全靜態單頁面應用程序 SPA 不同,服務端渲染應用程序,需要處於 Node.js server 運行環境;
- 更多的服務器負載:在 Node.js 中渲染完整的應用程序,顯然會比僅僅提供靜態文件的 server 更加大量佔用CPU 資源 (CPU-intensive - CPU 密集),因此如果你預料在高流量環境 ( high traffic ) 下使用,請準備相應的服務器負載,並明智地採用緩存策略。
19. vue-router 路由模式有幾種?
vue-router 有 3 種路由模式:hash、history、abstract,對應的源碼如下所示:
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
其中,3 種路由模式的說明如下:
-
hash: 使用 URL hash 值來作路由。支持所有瀏覽器,包括不支持 HTML5 History Api 的瀏覽器;
-
history : 依賴 HTML5 History API 和服務器配置。具體可以查看 HTML5 History 模式;
-
abstract : 支持所有 JavaScript 運行環境,如 Node.js 服務器端。如果發現沒有瀏覽器的 API,路由會自動強制進入這個模式.
20. 能說下 vue-router 中常用的 hash 和 history 路由模式實現原理嗎?
(1)hash 模式的實現原理
早期的前端路由的實現就是基於 location.hash 來實現的。其實現原理很簡單,location.hash 的值就是 URL 中 # 後面的內容。比如下面這個網站,它的 location.hash 的值爲 ‘#search’:
https://www.word.com#search
hash 路由模式的實現主要是基於下面幾個特性:
- URL 中 hash 值只是客戶端的一種狀態,也就是說當向服務器端發出請求時,hash 部分不會被髮送;
hash 值的改變,都會在瀏覽器的訪問歷史中增加一個記錄。因此我們能通過瀏覽器的回退、前進按鈕控制hash 的切換; - 可以通過 a 標籤,並設置 href 屬性,當用戶點擊這個標籤後,URL 的 hash 值會發生改變;或者使用 JavaScript 來對 loaction.hash 進行賦值,改變 URL 的 hash 值;
- 我們可以使用 hashchange 事件來監聽 hash 值的變化,從而對頁面進行跳轉(渲染)。
(2)history 模式的實現原理
HTML5 提供了 History API 來實現 URL 的變化。其中做最主要的 API 有以下兩個:history.pushState() 和 history.repalceState()。這兩個 API 可以在不進行刷新的情況下,操作瀏覽器的歷史紀錄。唯一不同的是,前者是新增一個歷史記錄,後者是直接替換當前的歷史記錄,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的實現主要基於存在下面幾個特性:
- pushState 和 repalceState 兩個 API 來操作實現 URL 的變化 ;
- 我們可以使用 popstate 事件來監聽 url 的變化,從而對頁面進行跳轉(渲染);
- history.pushState() 或 history.replaceState() 不會觸發 popstate 事件,這時我們需要手動觸發頁面跳轉(渲染)。
21. Vue 框架怎麼實現對象和數組的監聽?
如果被問到 Vue 怎麼實現數據雙向綁定,大家肯定都會回答 通過 Object.defineProperty() 對數據進行劫持,但是 Object.defineProperty() 只能對屬性進行數據劫持,不能對整個對象進行劫持,同理無法對數組進行劫持,但是我們在使用 Vue 框架中都知道,Vue 能檢測到對象和數組(部分方法的操作)的變化,那它是怎麼實現的呢?我們查看相關代碼如下:
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // observe 功能爲監測數據的變化
}
}
/**
* 對屬性進行遞歸遍歷
*/
let childOb = !shallow && observe(val) // observe 功能爲監測數據的變化
通過以上 Vue 源碼部分查看,我們就能知道 Vue 框架是通過遍歷數組 和遞歸遍歷對象,從而達到利用 Object.defineProperty() 也能對對象和數組(部分方法的操作)進行監聽。
22. Proxy 與 Object.defineProperty 優劣對比
Proxy 的優勢如下:
- Proxy 可以直接監聽對象而非屬性;
- Proxy 可以直接監聽數組的變化;
- Proxy 有多達 13 種攔截方法,不限於 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具備的;
- Proxy 返回的是一個新對象,我們可以只操作新的對象達到目的,而 Object.defineProperty 只能遍歷對象屬性直接修改;
Proxy 作爲新標準將受到瀏覽器廠商重點持續的性能優化,也就是傳說中的新標準的性能紅利;
Object.defineProperty 的優勢如下:
- 兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,而且無法用 polyfill 磨平,因此 Vue 的作者才聲明需要等到下個大版本( 3.0 )才能用 Proxy 重寫。
23. Vue 怎麼用 vm.$set() 解決對象新增屬性不能響應的問題 ?
受現代 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 的實現原理是:
- 如果目標是數組,直接使用數組的 splice 方法觸發相應式;
- 如果目標是對象,會先判讀屬性是否存在、對象是否是響應式,最終如果要對屬性進行響應式處理,則是通過調用 defineReactive 方法進行響應式處理( defineReactive 方法就是 Vue 在初始化對象時,給對象屬性採用 Object.defineProperty 動態添加 getter 和 setter 的功能所調用的方法)
24. 虛擬 DOM 的優缺點?
優點:
- 保證性能下限: 框架的虛擬 DOM 需要適配任何上層 API 可能產生的操作,它的一些 DOM 操作的實現必須是普適的,所以它的性能並不是最優的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虛擬 DOM 至少可以保證在你不需要手動優化的情況下,依然可以提供還不錯的性能,即保證性能的下限;
- 無需手動操作 DOM: 我們不再需要手動去操作 DOM,只需要寫好 View-Model 的代碼邏輯,框架會根據虛擬 DOM 和 數據雙向綁定,幫我們以可預期的方式更新視圖,極大提高我們的開發效率;
- 跨平臺: 虛擬 DOM 本質上是 JavaScript 對象,而 DOM 與平臺強相關,相比之下虛擬 DOM 可以進行更方便地跨平臺操作,例如服務器渲染、weex 開發等等。
缺點:
- 無法進行極致優化: 雖然虛擬 DOM + 合理的優化,足以應對絕大部分應用的性能需求,但在一些性能要求極高的應用中虛擬 DOM 無法進行針對性的極致優化。
25. 虛擬 DOM 實現原理?
虛擬 DOM 的實現原理主要包括以下 3 部分:
-
用 JavaScript 對象模擬真實 DOM 樹,對真實 DOM 進行抽象;
-
diff 算法 — 比較兩棵虛擬 DOM 樹的差異;
-
pach 算法 — 將兩個虛擬 DOM 對象的差異應用到真正的 DOM 樹。
26. v-html相關
27. Vue 中的 key 有什麼作用?
key 是爲 Vue 中 vnode 的唯一標記,通過這個 key,我們的 diff 操作可以更準確、更快速。Vue 的 diff 過程可以概括爲:oldCh 和 newCh 各有兩個頭尾的變量
oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex
,它們會新節點和舊節點會進行兩兩對比,即一共有4種比較方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex
,如果以上 4 種比較都沒匹配,如果設置了key,就會用 key 再進行比較,在比較的過程中,遍歷會往中間靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一個已經遍歷完了,就會結束比較。
所以 Vue 中 key 的作用是:key 是爲 Vue 中 vnode 的唯一標記,通過這個 key,我們的 diff 操作可以更準確、更快速
更準確:因爲帶 key 就不是就地複用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地複用的情況。所以會更加準確。
更快速:利用 key 的唯一性生成 map 對象來獲取對應節點,比遍歷方式更快,源碼如下:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
28.你有對 Vue 項目進行哪些優化?
如果沒有對 Vue 項目沒有進行過優化總結的同學,可以參考另一篇文章《 Vue 項目性能優化 — 實踐指南 》,文章主要介紹從 3 個大方面,22 個小方面詳細講解如何進行 Vue 項目的優化。
(1)代碼層面的優化
- v-if 和 v-show 區分使用場景
- computed 和 watch 區分使用場景
- v-for 遍歷必須爲 item 添加 key,且避免同時使用 v-if
- 長列表性能優化
- 事件的銷燬
- 圖片資源懶加載
- 路由懶加載
- 第三方插件的按需引入
- 優化無限列表性能
- 服務端渲染 SSR or 預渲染
(2)Webpack 層面的優化
- Webpack 對圖片進行壓縮
- 減少 ES6 轉爲 ES5 的冗餘代碼
- 提取公共代碼
- 模板預編譯
- 提取組件的 CSS
- 優化 SourceMap
- 構建結果輸出分析
- Vue 項目的編譯優化
(3)基礎的 Web 技術的優化
- 開啓 gzip 壓縮
- 瀏覽器緩存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶頸
29. 模塊化
- 基本概念:
-
在js中,一個模塊就是實現特定功能的文件(js文件)
-
遵循模塊的機制,想要什麼就加載什麼模塊
-
模塊化開發需要遵循規範
-
js實現模塊化規範
1.AMD 瀏覽器 requirejs 模塊被異步加載, 模塊加載不影響後面語句的運行 默認使用baseURL+ paths的路經解析方式 2.CommonJS nodejs 3.ES6的import/export 4.CMD 瀏覽器端
-
解決的問題:1.命名衝突 2.文件依賴 3.模塊的複用 4.統一規範和開發方式
30. 談談Vue和React組件化的思想
- 1.我們在各個頁面開發的時候,會產生很多重複的功能,比如element中的xxxx。像這種純粹非頁面的UI,便成爲我們常用的UI組件,最初的前端組件也就僅僅指的是UI組件
- 2.隨着業務邏輯變得越來多是,我們就想要我們的組件可以處理很多事,這就是我們常說的組件化,這個組件就不是UI組件了,而是包具體業務的業務組件
- 3.這種開發思想就是分而治之。最大程度的降低開發難度和維護成本的效果。並且可以多人協作,每個人寫不同的組件,最後像撘積木一樣的把它構成一個頁面
31. 對於即將到來的 vue3.0 特性你有什麼瞭解的嗎?
Vue 3.0 正走在發佈的路上,Vue 3.0 的目標是讓 Vue 核心變得更小、更快、更強大,因此 Vue 3.0 增加以下這些新特性:
(1)監測機制的改變
3.0 將帶來基於代理 Proxy 的 observer 實現,提供全語言覆蓋的反應性跟蹤。這消除了 Vue 2 當中基於 Object.defineProperty 的實現所存在的很多限制:
-
只能監測屬性,不能監測對象
-
檢測屬性的添加和刪除;
-
檢測數組索引和長度的變更;
-
支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 還提供了以下特性:
- 用於創建 observable 的公開 API。這爲中小規模場景提供了簡單輕量級的跨組件狀態管理解決方案。
- 默認採用惰性觀察。在 2.x 中,不管反應式數據有多大,都會在啓動時被觀察到。如果你的數據集很大,這可能會在應用啓動時帶來明顯的開銷。在 3.x 中,只觀察用於渲染應用程序最初可見部分的數據。
- 更精確的變更通知。在 2.x 中,通過 Vue.set 強制添加新屬性將導致依賴於該對象的 watcher 收到變更通知。在 3.x 中,只有依賴於特定屬性的 watcher 纔會收到通知。
- 不可變的 observable:我們可以創建值的“不可變”版本(即使是嵌套屬性),除非系統在內部暫時將其“解禁”。這個機制可用於凍結 prop 傳遞或 Vuex 狀態樹以外的變化。
- 更好的調試功能:我們可以使用新的 renderTracked 和 renderTriggered 鉤子精確地跟蹤組件在什麼時候以及爲什麼重新渲染。
(2)模板
模板方面沒有大的變更,只改了作用域插槽,2.x 的機制導致作用域插槽變了,父組件會重新渲染,而 3.0 把作用域插槽改成了函數的方式,這樣只會影響子組件的重新渲染,提升了渲染的性能。
同時,對於 render 函數的方面,vue3.0 也會進行一系列更改來方便習慣直接使用 api 來生成 vdom 。
(3)對象式的組件聲明方式
vue2.x 中的組件是通過聲明的方式傳入一系列 option,和 TypeScript 的結合需要通過一些裝飾器的方式來做,雖然能實現功能,但是比較麻煩。3.0 修改了組件的聲明方式,改成了類式的寫法,這樣使得和 TypeScript 的結合變得很容易。
此外,vue 的源碼也改用了 TypeScript 來寫。其實當代碼的功能複雜之後,必須有一個靜態類型系統來做一些輔助管理。現在 vue3.0 也全面改用 TypeScript 來重寫了,更是使得對外暴露的 api 更容易結合 TypeScript。靜態類型系統對於複雜代碼的維護確實很有必要。
(4)其它方面的更改
vue3.0 的改變是全面的,上面只涉及到主要的 3 個方面,還有一些其他的更改:
- 支持自定義渲染器,從而使得 weex 可以通過自定義渲染器的方式來擴展,而不是直接 fork 源碼來改的方式。
- 支持 Fragment(多個根節點)和 Protal(在 dom 其他部分渲染組建內容)組件,針對一些特殊的場景做了處理。
- 基於 treeshaking 優化,提供了更多的內置功能。
32. 你知道vue的模板語法用的是哪個web模板引擎的嗎?說說你對這模板引擎的理解
Vue使用了Mustache語法,即雙大括號的語法。
模板引擎:
負責組裝數據,以另外一種形式或外觀展現數據。
優點:
- 可維護性(後期改起來方便);
- 可擴展性(想要增加功能,增加需求方便);
- 開發效率提高(程序邏輯組織更好,調試方便);
- 看起來舒服(不容易寫錯)
32. 你知道vue的模板語法用的是哪個web模板引擎的嗎?說說你對這模板引擎的理解
Vue使用了Mustache語法,即雙大括號的語法。
模板引擎:
負責組裝數據,以另外一種形式或外觀展現數據。
優點:
- 可維護性(後期改起來方便);
- 可擴展性(想要增加功能,增加需求方便);
- 開發效率提高(程序邏輯組織更好,調試方便);
- 看起來舒服(不容易寫錯)
33. 怎麼給vue定義全局的方法?
第一種:掛載到Vue的prototype上。把全局方法寫到一個文件裏面,然後for循環掛載到Vue的prototype上,缺點是調用這個方法的時候沒有提示
Object.keys(tools).forEach(key => {
Vue.prototype[key] = tools[key]
})
第二種:利用全局混入mixin,因爲mixin裏面的methods會和創建的每個單文件組件合併。這樣做的優點是調用這個方法的時候有提示
Vue.mixin(mixin)
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
import tools from "./tools"
import filters from "./filters"
import Config from '../config'
import CONSTANT from './const_var'
export default {
data() {
return {
CONFIG: Config,
CONSTANT: CONSTANT
}
},
methods: {
// //將tools裏面的方法掛載到vue上,以方便調用,直接this.$xxx方法名就可以了
// Object.keys(tools).forEach(key => {
// Vue.prototype[key] = tools[key]
// })
//將tools裏面的方法用對象展開符混入到mixin上,以方便調用,直接this.$xxx方法名就可以了
...tools
},
filters: {
// //將filter裏面的方法添加了vue的篩選器上
// Object.keys(filters).forEach(key => {
// Vue.filter(key, filters[key])
// })
...filters
}
}
34. vue2.0不再支持v-html中使用過濾器了怎麼辦?
- 在method中定義方法
htmlFilter(htmlString){
return htmlString.replace(/+s/g,'')
}
- 在vue中 v-html="htmlFilter(htmlString)"即可
35. 怎麼解決vue打包後靜態資源圖片失效的問題?
設置assetsPublicPath將 assetsPublicPath: ‘/’ 改爲 assetsPublicPath: ‘./’
最新的vue-cli 需要在根目錄下建一個vue.config.js 在裏面配置publicPath即可