前言
為了金三銀四的跳槽季做準(zhǔn)備,并且我是vue技術(shù)棧的,所以整理了若干個vue的面試題。
每次看別人的博客,都會不自主的去看答案,為了方便檢驗自己的掌握程度,我特意將答案折疊起來,大家可以先看題目,在腦海中想象一下如果你被問到會怎么回答,然后再展開答案看看和自己的答案有什么不同。
答案非官方,仁者見仁智者見智,僅供參考。
基礎(chǔ)使用
MVVM、MVC 有什么區(qū)別
MVC通過分離Model、View和Controller的方式來組織代碼結(jié)構(gòu)。
其中View負責(zé)頁面的顯示邏輯,
Model負責(zé)存儲頁面的業(yè)務(wù)數(shù)據(jù),以及對相應(yīng)數(shù)據(jù)的操作。
Controller層是View層和Model層的紐帶,它主要負責(zé)用戶與應(yīng)用的響應(yīng)操作,當(dāng)用戶與頁面產(chǎn)生交互的時候,Controller中的事件觸發(fā)器就開始工作了,通過調(diào)用Model層,來完成對Model的修改,然后Model層再去通知View層更新。
MVVM分為Model、View、ViewModel。
Model代表數(shù)據(jù)模型,數(shù)據(jù)和業(yè)務(wù)邏輯都在Model層中定義;
View代表UI視圖,負責(zé)數(shù)據(jù)的展示;
ViewMode負責(zé)監(jiān)聽Model中數(shù)據(jù)的改變并且控制視圖的更新,處理用戶交互操作;
Model和View并無直接關(guān)聯(lián),而是通過ViewModel來進行聯(lián)系的,Model和ViewModel之間有著雙向數(shù)據(jù)綁定的聯(lián)系。因此當(dāng)Model中的數(shù)據(jù)改變時會觸發(fā)View層的刷新,View中由于用戶交互操作而改變的數(shù)據(jù)也會在Model中同步。 這種模式實現(xiàn)了Model和View的數(shù)據(jù)自動同步,因此開發(fā)者只需要專注于數(shù)據(jù)的維護操作即可,而不需要自己操作DOM。
Vue并沒有完全遵循MVVM思想呢?
嚴(yán)格的MVVM要求View不能和Model直接通信,而Vue提供了$refs這個屬性,讓Model可以直接操作View,違反了這一規(guī)定,所以說Vue沒有完全遵循MVVM。
Vue 的優(yōu)點
漸進式框架:可以在任何項目中輕易的引入;
輕量級框架:只關(guān)注視圖層,是一個構(gòu)建數(shù)據(jù)的視圖集合,大小只有幾十kb;
簡單易學(xué):國人開發(fā),中文文檔,不存在語言障礙 ,易于理解和學(xué)習(xí);
雙向數(shù)據(jù)綁定:在數(shù)據(jù)操作方面更為簡單;
組件化:很大程度上實現(xiàn)了邏輯的封裝和重用,在構(gòu)建單頁面應(yīng)用方面有著獨特的優(yōu)勢;
視圖,數(shù)據(jù),結(jié)構(gòu)分離:使數(shù)據(jù)的更改更為簡單,不需要進行邏輯代碼的修改,只需要操作數(shù)據(jù)就能完成相關(guān)操作;
對 SPA 單頁面的理解,它的優(yōu)缺點分別是什么?
SPA僅在Web頁面初始化時加載相應(yīng)的HTML、JavaScript和CSS。一旦頁面加載完成,SPA不會因為用戶的操作而進行頁面的重新加載或跳轉(zhuǎn);取而代之的是利用路由機制實現(xiàn)HTML內(nèi)容的變換,UI與用戶的交互,避免頁面的重新加載。
優(yōu)點:
用戶體驗好、快,內(nèi)容的改變不需要重新加載整個頁面,避免了不必要的跳轉(zhuǎn)和重復(fù)渲染;
有利于前后端職責(zé)分離,架構(gòu)清晰,前端進行交互邏輯,后端負責(zé)數(shù)據(jù)處理;
缺點:
初次加載耗時多:為實現(xiàn)單頁Web應(yīng)用功能及顯示效果,需要在加載頁面的時候?qū)avaScript、CSS統(tǒng)一加載,部分頁面按需加載;
不利于SEO:由于所有的內(nèi)容都在一個頁面中動態(tài)替換顯示,所以在SEO上其有著天然的弱勢。
怎樣理解 Vue 的單向數(shù)據(jù)流?
父級prop的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態(tài),從而導(dǎo)致你的應(yīng)用的數(shù)據(jù)流向難以理解。
每次父級組件發(fā)生更新時,子組件中所有的prop都將會刷新為最新的值。在子組件內(nèi)部改變prop的時候 ,Vue會在瀏覽器的控制臺中發(fā)出警告。
子組件想修改時,只能通過$emit派發(fā)一個自定義事件,父組件接收到后,由父組件修改。
有兩種常見的試圖改變一個prop的情形 :
這個prop用來傳遞一個初始值;這個子組件接下來希望將其作為一個本地的prop數(shù)據(jù)來使用。?在這種情況下,最好定義一個本地的data屬性并將這個prop用作其初始值:
這個prop以一種原始的值傳入且需要進行轉(zhuǎn)換。?在這種情況下,最好使用這個prop的值來定義一個計算屬性
Data 為什么是一個函數(shù)?
因為組件是用來復(fù)用的,且JS里對象是引用關(guān)系,如果組件中data是一個對象,那么這樣作用域沒有隔離,子組件中的data屬性值會相互影響,如果組件中data選項是一個函數(shù),那么每個實例可以維護一份被返回對象的獨立的拷貝,組件實例之間的data屬性值不會互相影響;而new Vue的實例,是不會被復(fù)用的,因此不存在引用對象的問題。
Computed 和 Watch 有什么區(qū)別?
對于 Computed:
它支持緩存,只有依賴的數(shù)據(jù)發(fā)生了變化,才會重新計算
不支持異步,當(dāng)Computed中有異步操作時,無法監(jiān)聽數(shù)據(jù)的變化
如果一個屬性是由其他屬性計算而來的,這個屬性依賴其他的屬性,一般會使用 computed
如果computed屬性的屬性值是函數(shù),那么默認使用get方法,函數(shù)的返回值就是屬性的屬性值;在computed中,屬性有一個get方法和一個set方法,當(dāng)數(shù)據(jù)發(fā)生變化時,會調(diào)用set方法。
對于 Watch:
它不支持緩存,當(dāng)一個屬性發(fā)生變化時,它就會觸發(fā)相應(yīng)的操作
支持異步監(jiān)聽
監(jiān)聽的函數(shù)接收兩個參數(shù),第一個參數(shù)是最新的值,第二個是變化之前的值
監(jiān)聽數(shù)據(jù)必須是data中聲明的或者父組件傳遞過來的props中的數(shù)據(jù),當(dāng)發(fā)生變化時,會觸發(fā)其他操作
函數(shù)有兩個的參數(shù):immediate:組件加載立即觸發(fā)回調(diào)函數(shù)deep:深度監(jiān)聽,發(fā)現(xiàn)數(shù)據(jù)內(nèi)部的變化,在復(fù)雜數(shù)據(jù)類型中使用,例如數(shù)組中的對象發(fā)生變化。
Computed 和 Methods 的區(qū)別
共同點:
可以將同一函數(shù)定義為一個method或者一個計算屬性。對于最終的結(jié)果,兩種方式是相同的。
不同點:
computed: 計算屬性是基于它們的依賴進行緩存的,只有在它的相關(guān)依賴發(fā)生改變時才會重新求值;
method: 調(diào)用總會執(zhí)行該函數(shù)。
.Sync 的作用是什么?
vue修飾符sync的功能是:當(dāng)父組件提供了一個數(shù)據(jù),而子組件想要去更改這個數(shù)據(jù),但是Vue的規(guī)則不能讓子組件去修改父組件的數(shù)據(jù),就需要通過this.$emit和$event,來實現(xiàn)數(shù)據(jù)修改的目的。
:money.sync="total" // 等價于 :money="total" v-on:update:money="total = $event"復(fù)制代碼
復(fù)制代碼
綁定事件 @click=handler 和 @click=handler() 那個正確?有什么區(qū)別?
都可以,不帶括號會傳進來一個事件對象,帶括號的不會
事件有哪些修飾符?
「事件修飾符」
.stop阻止事件繼續(xù)傳播
.prevent阻止標(biāo)簽?zāi)J行為
.capture使用事件捕獲模式,即元素自身觸發(fā)的事件先在此處處理,然后才交由內(nèi)部元素進行處理
.self只當(dāng)在event.target是當(dāng)前元素自身時觸發(fā)處理函數(shù)
.once事件將只會觸發(fā)一次
.passive告訴瀏覽器你不想阻止事件的默認行為
「v-model 的修飾符」
.lazy通過這個修飾符,轉(zhuǎn)變?yōu)樵赾hange事件再同步
.number自動將用戶的輸入值轉(zhuǎn)化為數(shù)值類型
.trim自動過濾用戶輸入的首尾空格
「鍵盤事件的修飾符」
.enter
.tab
.delete(捕獲“刪除”和“退格”鍵)
.esc
.space
.up
.down
.left
.right
「系統(tǒng)修飾鍵」
.ctrl
.alt
.shift
.meta
「鼠標(biāo)按鈕修飾符」
.left
.right
.middle
什么是插槽?具名插槽?作用域插槽?原理是什么?
slot又名插槽,是Vue的內(nèi)容分發(fā)機制,插槽slot是子組件的一個模板標(biāo)簽元素,而這一個標(biāo)簽元素是否顯示,以及怎么顯示是由父組件決定的。slot又分三類,默認插槽,具名插槽和作用域插槽。
默認插槽:又名匿名插槽,當(dāng)slot沒有指定name屬性值的時候,默認顯示的插槽,一個組件內(nèi)只允許有一個匿名插槽。
具名插槽:帶有具體名字的插槽,也就是帶有name屬性的slot,一個組件可以出現(xiàn)多個具名插槽。
作用域插槽:可以是匿名插槽,也可以是具名插槽,該插槽在渲染時,父組件可以使用子組件內(nèi)部的數(shù)據(jù)。
實現(xiàn)原理:當(dāng)子組件vm實例化時,獲取到父組件傳入的slot標(biāo)簽的內(nèi)容,存放在vm.$slot中,默認插槽為vm.$slot.default,具名插槽為vm.$slot.xxx,xxx為插槽名,當(dāng)組件執(zhí)行渲染函數(shù)時候,遇到slot標(biāo)簽,使用$slot中的內(nèi)容進行替換,此時可以為插槽傳遞數(shù)據(jù),若存在數(shù)據(jù),則可稱該插槽為作用域插槽。
Vue 中如何實現(xiàn)過度效果?如何實現(xiàn)列表過度?
過渡效果,當(dāng)然只有dom從顯示到隱藏或隱藏到顯示才能用
Vue.js為我們提供了內(nèi)置的過渡組件transition和transition-group
Vue將元素的過渡分為四個階段,進入前,進入后,消失前,消失后
支持mode屬性,可選值:
in-out:要進入的先進入,然后要消失的再消失
out-in:要消失的先消失,然后要進入的再進入
多個元素需要加上過渡效果可以使用name屬性進行區(qū)分。
可以配合animate.css實現(xiàn)更多的動畫效果。
過濾器的作用,如何實現(xiàn)一個過濾器
過濾器是用來過濾數(shù)據(jù)的,在Vue選項中聲明filters來實現(xiàn)一個過濾器,filters不會修改數(shù)據(jù),而是處理數(shù)據(jù),改變用戶看到的輸出。
使用場景:
需要格式化數(shù)據(jù)的情況,比如需要處理時間、價格等數(shù)據(jù)格式的輸出 / 顯示。
比如后端返回一個年月日的日期字符串,前端需要展示為多少天前的數(shù)據(jù)格式,此時就可以用fliters過濾器來處理數(shù)據(jù)。
過濾器是一個函數(shù),它會把表達式中的值始終當(dāng)作函數(shù)的第一個參數(shù)。過濾器用在插值表達式{{ }}和v-bind表達式中,然后放在操作符|后面進行指示。
例如,在顯示金額,給商品價格添加單位:
<li>商品價格:{{item.price | filterPrice}}</li> filters: {? ? filterPrice (price) {? ? ? return price ? ('¥' + price) : '--'? ? }? }
復(fù)制代碼
assets 和 static 的區(qū)別
相同點:assets和static兩個都是存放靜態(tài)資源文件。項目中所需要的資源文件圖片,字體圖標(biāo),樣式文件等都可以放在這兩個文件下,這是相同點
不相同點:assets中存放的靜態(tài)資源文件在項目打包時,也就是運行npm run build時會將assets中放置的靜態(tài)資源文件進行打包上傳,所謂打包簡單點可以理解為壓縮體積,代碼格式化。而壓縮后的靜態(tài)資源文件最終也都會放置在static文件中跟著index.html一同上傳至服務(wù)器。static中放置的靜態(tài)資源文件就不會要走打包壓縮格式化等流程,而是直接進入打包好的目錄,直接上傳至服務(wù)器。因為避免了壓縮直接進行上傳,在打包時會提高一定的效率,但是static中的資源文件由于沒有進行壓縮等操作,所以文件的體積也就相對于assets中打包后的文件提交較大點。在服務(wù)器中就會占據(jù)更大的空間。
建議:將項目中template需要的樣式文件 js 文件等都可以放置在assets中,走打包這一流程。減少體積。而項目中引入的第三方的資源文件如iconfoont.css等文件可以放置在static中,因為這些引入的第三方文件已經(jīng)經(jīng)過處理,我們不再需要處理,直接上傳。
對 SSR 的理解
SSR大致的意思就是vue在客戶端將標(biāo)簽渲染成的整個html片段的工作在服務(wù)端完成,服務(wù)端形成的html片段直接返回給客戶端,這個過程就叫做服務(wù)端渲染。
(1)服務(wù)端渲染的優(yōu)點:
更好的SEO:SSR是直接由服務(wù)端返回已經(jīng)渲染好的頁面(數(shù)據(jù)已經(jīng)包含在頁面中),所以搜索引擎爬取工具可以抓取渲染好的頁面;
首屏加載更快:SPA會等待所有Vue編譯后的js文件都下載完成后,才開始進行頁面的渲染,文件下載等需要一定的時間等,所以首屏渲染需要一定的時間;SSR直接由服務(wù)端渲染好頁面直接返回顯示,無需等待下載js文件及再去渲染等,所以SSR有更快的內(nèi)容到達時間;
(2) 服務(wù)端渲染的缺點:
更多的開發(fā)條件限制:例如服務(wù)端渲染只支持beforCreate和created兩個鉤子函數(shù),
不能進行dom操作。
這會導(dǎo)致一些外部擴展庫需要特殊處理,才能在服務(wù)端渲染應(yīng)用程序中運行。
Vue 的性能優(yōu)化有哪些
(1)代碼層面的優(yōu)化
v-if和v-show區(qū)分使用場景
computed和watch區(qū)分使用場景
v-for遍歷必須為item添加key,且避免同時使用v-if
長列表性能優(yōu)化
事件的銷毀
圖片資源懶加載
路由懶加載
第三方插件的按需引入
優(yōu)化無限列表性能
服務(wù)端渲染
(2)Webpack 層面的優(yōu)化
Webpack對圖片進行壓縮
減少ES6轉(zhuǎn)為ES5的冗余代碼
提取公共代碼
模板預(yù)編譯
提取組件的CSS
優(yōu)化SourceMap
構(gòu)建結(jié)果輸出分析
Vue項目的編譯優(yōu)化
(3)基礎(chǔ)的 Web 技術(shù)的優(yōu)化
開啟gzip壓縮
瀏覽器緩存
CDN的使用
使用Chrome Performance查找性能瓶頸
Vue 的首屏加載性能優(yōu)化有哪些
優(yōu)化前的大小

1.圖片優(yōu)化
之前為了方便開法, 背景圖片直接在assets里面扔了一個jpg, 導(dǎo)致加載這張圖片的時候就用了十幾秒, 于是乎我就把圖片上傳空間了, 然后改用網(wǎng)絡(luò)地址。
2.禁止生成.map 文件
build出來的dist文件夾里面有很多的.map文件,這些文件主要是幫助線上調(diào)試代碼,禁止生成這些文件.
在vue.config.js里面加上這句。

3.路由懶加載

4.cdn 引入公共庫
? ?<link rel="stylesheet" >??
? <script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>??
? <script src="https://unpkg.com/element-ui/lib/index.js"></script>??
? <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>??
? <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>? ?
?<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
//cdn引入
? ? configureWebpack: {
? ? ? ? externals: {
? ? ? ? ? ? 'vue': 'Vue',
? ? ? ? ? ? 'element-ui': 'ELEMENT',
? ? ? ? ? ? 'vue-router': 'VueRouter',
? ? ? ? ? ? 'vuex': 'Vuex',
? ? ? ? ? ? 'axios': 'axios'
? ? ? ? }
? ? }
復(fù)制代碼
網(wǎng)上說可以把import注釋掉,親自操作會報錯,也有資料說不用注釋也不會打包。
一頓操作最后的文件,效果顯著,app.js 還是很大

5.終極法寶 GZIP 壓縮
做完這個感覺前四步都是小菜一碟,直接把1.4m的app.js干成一百多kb,其他的都不足掛齒了。
configureWebpack: config => {
? ? ? ? return {
? ? ? ? ? ? //配置cdn
? ? ? ? ? ? externals: {
? ? ? ? ? ? ? ? 'vue': 'Vue',
? ? ? ? ? ? ? ? 'element-ui': 'ELEMENT',
? ? ? ? ? ? ? ? 'vue-router': 'VueRouter',
? ? ? ? ? ? ? ? 'vuex': 'Vuex',
? ? ? ? ? ? ? ? 'axios': 'axios'
? ? ? ? ? ? },
? ? ? ? ? ? //配置gzip壓縮
? ? ? ? ? ? plugins: [
? ? ? ? ? ? ? ? new CompressionWebpackPlugin({
? ? ? ? ? ? ? ? ? ? test: new RegExp('\.(js|css)$'),
? ? ? ? ? ? ? ? ? ? threshold: 10240,
? ? ? ? ? ? ? ? ? ? minRatio: 0.8
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ],
? ? ? ? }
? ? }
復(fù)制代碼
服務(wù)端也要配,不然不認識GZIP文件。
//配置GZIP壓縮模塊
const compression = require('compression');
//在所有中間件之前引入
app.use(compression());
復(fù)制代碼
最垃圾的服務(wù)器通過以上幾個優(yōu)化,一樣飛起來了!!!

vue 初始化頁面閃動問題
使用vue開發(fā)時,在vue初始化之前,由于div是不歸vue管的,所以我們寫的代碼在還沒有解析的情況下會容易出現(xiàn)花屏現(xiàn)象,看到類似于{{message}}的字樣,雖然一般情況下這個時間很短暫,但是我們還是有必要讓解決這個問題的。
首先:在css里加上[v-cloak] { display: none; }。如果沒有徹底解決問題,則在根元素加上style="display: none;" :style="{display: ?block }"
Class 與 Style 如何動態(tài)綁定?
Class可以通過對象語法和數(shù)組語法進行動態(tài)綁定:
對象語法:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
? isActive: true,
? hasError: false
}
復(fù)制代碼
數(shù)組語法:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
? activeClass: 'active',
? errorClass: 'text-danger'
}
復(fù)制代碼
Style也可以通過對象語法和數(shù)組語法進行動態(tài)綁定:
對象語法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
? activeColor: 'red',
? fontSize: 30
}
復(fù)制代碼
數(shù)組語法:
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
? styleColor: {
? ? color: 'red'
? },
? styleSize:{
? ? fontSize:'23px'
? }
}
復(fù)制代碼
如何讓 CSS 只在當(dāng)前組件中起作用?
在組件中的style標(biāo)簽中加上scoped
如何獲取 dom
ref="domName"用法:this.$refs.domName
vue-loader 是什么?使用它的用途有哪些?
vue文件的一個加載器,把template/js/style轉(zhuǎn)換成js模塊。
生命周期
Vue 有哪些生命周期鉤子?
Vue的生命周期鉤子核心實現(xiàn)是利用發(fā)布訂閱模式先把用戶傳入的的生命周期鉤子訂閱好(內(nèi)部采用數(shù)組的方式存儲)然后在創(chuàng)建組件實例的過程中會一次執(zhí)行對應(yīng)的鉤子方法(發(fā)布)。
beforeCreate:是new Vue()之后觸發(fā)的第一個鉤子,在當(dāng)前階段data、methods、computed以及watch上的數(shù)據(jù)和方法都不能被訪問。
created:在實例創(chuàng)建完成后發(fā)生,當(dāng)前階段已經(jīng)完成了數(shù)據(jù)觀測,也就是可以使用數(shù)據(jù),更改數(shù)據(jù),在這里更改數(shù)據(jù)不會觸發(fā)updated函數(shù)。可以做一些初始數(shù)據(jù)的獲取,在當(dāng)前階段無法與Dom進行交互,如果非要想,可以通過vm.$nextTick來訪問Dom。
beforeMount:發(fā)生在掛載之前,在這之前template模板已導(dǎo)入渲染函數(shù)編譯。而當(dāng)前階段虛擬Dom已經(jīng)創(chuàng)建完成,即將開始渲染。在此時也可以對數(shù)據(jù)進行更改,不會觸發(fā)updated。
mounted:在掛載完成后發(fā)生,在當(dāng)前階段,真實的Dom掛載完畢,數(shù)據(jù)完成雙向綁定,可以訪問到Dom節(jié)點,使用$refs屬性對Dom進行操作。
beforeUpdate:發(fā)生在更新之前,也就是響應(yīng)式數(shù)據(jù)發(fā)生更新,虛擬dom重新渲染之前被觸發(fā),你可以在當(dāng)前階段進行更改數(shù)據(jù),不會造成重渲染。
updated:發(fā)生在更新完成之后,當(dāng)前階段組件Dom已完成更新。要注意的是避免在此期間更改數(shù)據(jù),因為這可能會導(dǎo)致無限循環(huán)的更新。
beforeDestroy:發(fā)生在實例銷毀之前,在當(dāng)前階段實例完全可以被使用,我們可以在這時進行善后收尾工作,比如清除計時器。
destroyed:發(fā)生在實例銷毀之后,這個時候只剩下了dom空殼。組件已被拆解,數(shù)據(jù)綁定被卸除,監(jiān)聽被移出,子實例也統(tǒng)統(tǒng)被銷毀。
如果需要發(fā)送異步請求,最好放在哪個鉤子內(nèi)?
可以在鉤子函數(shù)created、beforeMount、mounted中進行調(diào)用,因為在這三個鉤子函數(shù)中,data已經(jīng)創(chuàng)建,可以將服務(wù)端端返回的數(shù)據(jù)進行賦值。
推薦在created鉤子函數(shù)中調(diào)用異步請求,有以下優(yōu)點:
能更快獲取到服務(wù)端數(shù)據(jù),減少頁面loading時間;
ssr不支持beforeMount、mounted鉤子函數(shù),所以放在created中有助于一致性;
第一次頁面加載會觸發(fā)哪幾個鉤子?
beforeCreate,created,beforeMount,mounted
哪個鉤子可以進行 dom 操作?
在鉤子函數(shù)mounted被調(diào)用前,Vue已經(jīng)將編譯好的模板掛載到頁面上,所以在mounted中可以訪問操作DOM。
父子組件嵌套時,父組件和子組件生命周期鉤子執(zhí)行順序是什么?
Vue的父組件和子組件生命周期鉤子函數(shù)執(zhí)行順序可以歸類為以下4部分:
加載渲染過程父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
子組件更新過程父beforeUpdate->子beforeUpdate->子updated->父updated
父組件更新過程父beforeUpdate->父updated
銷毀過程父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
父子組件嵌套時,父組件視圖和子組件視圖誰先完成渲染?
加載渲染過程父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
可知子組件先完成渲染
keep-alive 中的生命周期哪些
對應(yīng)兩個鉤子函數(shù)activated和deactivated,當(dāng)組件被激活時,觸發(fā)鉤子函數(shù)activated,當(dāng)組件被移除時,觸發(fā)鉤子函數(shù)deactivated。
指令相關(guān)
說說 vue 內(nèi)置指令

什么是自定義指令?有哪些生命周期?
是vue對HTML元素的擴展,給HTML元素增加自定義功能。vue編譯DOM時,會找到指令對象,執(zhí)行指令的相關(guān)方法。
自定義指令有五個生命周期
bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用。在這里可以進行一次性的初始化設(shè)置。
inserted:被綁定元素插入父節(jié)點時調(diào)用?(僅保證父節(jié)點存在,但不一定已被插入文檔中)。
update:被綁定于元素所在的模板更新時調(diào)用,而無論綁定值是否變化。通過比較更新前后的綁定值,可以忽略不必要的模板更新。
componentUpdated:被綁定元素所在模板完成一次更新周期時調(diào)用。
unbind:只調(diào)用一次,指令與元素解綁時調(diào)用。
v-text 和 v-html 有什么區(qū)別?
v-text和{{}}表達式渲染數(shù)據(jù),不解析標(biāo)簽。
v-html不僅可以渲染數(shù)據(jù),而且可以解析標(biāo)簽。
v-if 和 v-for 的優(yōu)先級
當(dāng)v-if與v-for一起使用時,v-for具有比v-if更高的優(yōu)先級,這意味著v-if將分別重復(fù)運行于每個v-for循環(huán)中。所以,不推薦v-if和v-for同時使用。如果v-if和v-for一起用的話,vue中的的會自動提示v-if應(yīng)該放到外層去。
V-if 和 v-show 有什么區(qū)別?
手段:v-if是動態(tài)的向DOM樹內(nèi)添加或者刪除DOM元素;v-show是通過設(shè)置DOM元素的display樣式屬性控制顯隱;
編譯過程:v-if切換有一個局部編譯/卸載的過程,切換過程中合適地銷毀和重建內(nèi)部的事件監(jiān)聽和子組件;v-show只是簡單的基于css切換;
編譯條件:v-if是惰性的,如果初始條件為假,則什么也不做;只有在條件第一次變?yōu)檎鏁r才開始局部編譯;v-show是在任何條件下,無論首次條件是否為真,都被編譯,然后被緩存,而且DOM元素保留;
性能消耗:v-if有更高的切換消耗;v-show有更高的初始渲染消耗;
使用場景:v-if適合運營條件不大可能改變;v-show適合頻繁切換。
組件的 v-model 是如何實現(xiàn)的?
我們在vue項目中主要使用v-model指令在表單input、textarea、select等元素上創(chuàng)建雙向數(shù)據(jù)綁定,我們知道v-model本質(zhì)上不過是語法糖,v-model在內(nèi)部為不同的輸入元素使用不同的屬性并拋出不同的事件:
text和textarea元素使用value屬性和input事件;
checkbox和radio使用checked屬性和change事件;
select字段將value作為prop并將change作為事件。
以input表單元素為例:
<input?v-model='something'>
// 相當(dāng)于
<input?v-bind:value="something"?v-on:input="something?=?$event.target.value">
復(fù)制代碼
v-model 可以被用在自定義組件上嗎?如果可以,如何使用?
如果在自定義組件中,v-model默認會利用名為value的prop和名為input的事件,如下所示:
父組件:<ModelChild v-model="message"></ModelChild>復(fù)制代碼
復(fù)制代碼
子組件:
<div>{{value}}</div>
props:{
? ? value: String
},
methods: {
? test1(){
? ? this.$emit('input', '小紅')
? },
},
復(fù)制代碼
v-on 可以監(jiān)聽多個方法嗎?
可以
<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">
復(fù)制代碼
組件相關(guān)
組件通信的 N 種方式
(1)props / $emit適用 父子組件通信
(2)ref適用 父子組件通信
ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子組件上,引用就指向組件實例
(3)$parent/$children/$root:訪問父 / 子實例 / 根實例
(4)EventBus ($emit / $on)適用于 父子、隔代、兄弟組件通信
這種方法通過一個空的Vue實例作為中央事件總線(事件中心),用它來觸發(fā)事件和監(jiān)聽事件,從而實現(xiàn)任何組件間的通信,包括父子、隔代、兄弟組件。
(5)$attrs/$listeners適用于 隔代組件通信
$attrs:包含了父作用域中不被prop所識別 (且獲取) 的特性綁定 (class和style除外 )。當(dāng)一個組件沒有聲明任何prop時,這里會包含所有父作用域的綁定 (class和style除外 ),并且可以通過v-bind="$attrs"傳入內(nèi)部組件。通常配合inheritAttrs選項一起使用。
$listeners:包含了父作用域中的 (不含.native修飾器的)v-on事件監(jiān)聽器。它可以通過v-on="$listeners"傳入內(nèi)部組件
(6)provide / inject適用于 隔代組件通信
祖先組件中通過provide來提供變量,然后在子孫組件中通過inject來注入變量。provide / inject API主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態(tài),跨級組件間建立了一種主動提供與依賴注入的關(guān)系。
(7)Vuex適用于 父子、隔代、兄弟組件通信
Vuex是一個專為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式。每一個Vuex應(yīng)用的核心就是store(倉庫)。store基本上就是一個容器,它包含著你的應(yīng)用中大部分的狀態(tài) (state)。
Vuex的狀態(tài)存儲是響應(yīng)式的。當(dāng)Vue組件從store中讀取狀態(tài)的時候,若store中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會相應(yīng)地得到高效更新。
改變store中的狀態(tài)的唯一途徑就是顯式地提交(commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化。
(8)插槽
Vue3可以通過usesolt獲取插槽數(shù)據(jù)。
(9)mitt.js適用于任意組件通信
Vue3中移除了$on,$off等方法,所以EventBus不再使用,相應(yīng)的替換方案就是mitt.js
Vue3 和 vue2 全局組件和局部組件注冊的方式?
Vue2:Vue.component()
Vue3:app.component()
什么是動態(tài)組件?動態(tài)組件的鉤子如何執(zhí)行?
讓多個組件使用同一個掛載點,并動態(tài)切換,這就是動態(tài)組件
簡單的說,動態(tài)組件就是將幾個組件放在一個掛載點下,這個掛載點就是標(biāo)簽,其需要綁定is屬性,屬性值為父組件中的變量,變量對應(yīng)的值為要掛載的組件的組件名,然后根據(jù)父組件里某個變量來動態(tài)顯示哪個,也可以都不顯示。
緩存<keep-alive>
包裹動態(tài)組件時,會緩存不活動的組件實例,而不是銷毀它們。
可以將動態(tài)組件放到組件內(nèi)對動態(tài)組件進行緩存,這樣動態(tài)組件進行切換的時候,就不會每次都重新創(chuàng)建了。
Keep-alive 的作用?使用 keep-alive 的組件如何監(jiān)控組件切換?
keep-alive是Vue內(nèi)置的一個組件,可以使被包含的組件保留狀態(tài),避免重新渲染 ,其有以下特性:
一般結(jié)合路由和動態(tài)組件一起使用,用于緩存組件;
提供include和exclude屬性,兩者都支持字符串或正則表達式,include表示只有名稱匹配的組件會被緩存,exclude表示任何名稱匹配的組件都不會被緩存 ,其中exclude的優(yōu)先級比include高;
對應(yīng)兩個鉤子函數(shù)activated和deactivated,當(dāng)組件被激活時,觸發(fā)鉤子函數(shù)activated,當(dāng)組件被移除時,觸發(fā)鉤子函數(shù)deactivated。
父組件如何監(jiān)聽子組件的生命周期?
比如有父組件Parent和子組件Child,如果父組件監(jiān)聽到子組件掛載mounted就做一些邏輯處理,可以通過以下寫法實現(xiàn):
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
? this.$emit("mounted");
}
復(fù)制代碼
以上需要手動通過$emit觸發(fā)父組件的事件,更簡單的方式可以在父組件引用子組件時通過@hook來監(jiān)聽即可,如下所示:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
? console.log('父組件監(jiān)聽到 mounted 鉤子函數(shù) ...');
},
//? Child.vue
mounted(){
? console.log('子組件觸發(fā) mounted 鉤子函數(shù) ...');
},? ?
// 以上輸出順序為:
// 子組件觸發(fā) mounted 鉤子函數(shù) ...
// 父組件監(jiān)聽到 mounted 鉤子函數(shù) ...? ?
復(fù)制代碼
當(dāng)然@hook方法不僅僅是可以監(jiān)聽mounted,其它的生命周期事件,例如:created,updated等都可以監(jiān)聽。
原理相關(guān)
Vue 初始化時都做了什么?
第一部分
? 每個vue實例都有一個_uid,并且是依次遞增的,確保唯一性。
?vue實例不應(yīng)該是一個響應(yīng)式的,做個標(biāo)記。
第二部分
? 如果是子組件,將組件配置對象上的一些深層次屬性放到?vm.$options選項中,以提高代碼的執(zhí)行效率。
? 如果是根組件,對options進行合并,vue會將相關(guān)的屬性和方法都統(tǒng)一放到vm.$options中。vm.$options的屬性來自兩個方面,一個是Vue的構(gòu)造函數(shù)vm.constructor預(yù)先定義的,一個是new Vue時傳入的入?yún)ο蟆?/p>
第三部分
?initProxy / vm._renderProxy在非生產(chǎn)環(huán)境下執(zhí)行了initProxy函數(shù),參數(shù)是實例;在生產(chǎn)環(huán)境下設(shè)置了實例的_renderProxy屬性為實例自身。
? 設(shè)置了實例的_self屬性為實例自身。
?initLifecycle初始化組件實例關(guān)系屬性 , 比如$parent、$children、$root、$refs等 (不是組件生命周期mounted,created...)
?initEvents初始化自定義事件。
?initRender初始化插槽 , 獲取this.slots, 定義this._c, 也就是createElement方法 , 平時使用的h函數(shù)。
?callHook執(zhí)行beforeCreate生命周期函數(shù)。
?initInjections初始化inject選項
?initState響應(yīng)式原理的核心 , 處理props、methods、computed、data、watch等。
?initProvide解析組件配置項上的provide對象,將其掛載到vm._provided屬性上。
?callHook執(zhí)行created生命周期函數(shù)。
第四部分
? 如果有el屬性,則調(diào)用vm.$mount方法掛載vm,掛載的目標(biāo)就是把模板渲染成最終的DOM。
? 不存在el的時候不掛載 , 需要手動掛載。
數(shù)據(jù)響應(yīng)式的原理
Vue.js是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。主要分為以下幾個步驟:
使用observe對需要響應(yīng)式的數(shù)據(jù)進行遞歸,將對像的所有屬性及其子屬性,都加上setter和getter這樣的話,給這個對象的某個屬性賦值的時候,就會觸發(fā)setter,那么就能監(jiān)聽到了數(shù)據(jù)變化。
compile解析模板指令,將模板中的變量替換成數(shù)據(jù),然后初始化渲染頁面視圖,并將每個指令對應(yīng)的節(jié)點綁定更新函數(shù),添加監(jiān)聽數(shù)據(jù)的訂閱者,一旦數(shù)據(jù)有變動,收到通知,更新視圖。
Watcher訂閱者是Observer和Compile之間通信的橋梁,主要做的事情是:
在自身實例化時往屬性訂閱器(dep)里面添加自己
自身必須有一個update()方法
待屬性變動觸發(fā)dep.notice()通知時,能調(diào)用自身的update()方法,并觸發(fā)Compile中綁定的回調(diào),完成視圖更新。
總結(jié):通過Observer來監(jiān)聽自己的model數(shù)據(jù)變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到一個數(shù)據(jù)響應(yīng)式的效果。

使用 Object.defineProperty() 來進行數(shù)據(jù)劫持有什么缺點?
無法劫持以下操作
給對象新增屬性
給對象刪除屬性
大部分的操作數(shù)組
Vue 框架怎么實現(xiàn)對象和數(shù)組的監(jiān)聽?
Vue框架是通過遍歷數(shù)組和遞歸遍歷對象,從而達到利用Object.defineProperty()對對象和數(shù)組的部分方法的操作進行監(jiān)聽。
Vue 中給 data 中的對象屬性添加一個新的屬性或刪除一個屬性時會發(fā)生什么?如何解決?
什么都不會發(fā)生,因為Object.defineProperty()監(jiān)聽不到這類變化。
可以使用vm.$set和Vue.set方法去添加一個屬性。
可以使用vm.$delete和Vue.delete方法去刪除一個屬性。
如何解決索引賦值或者修改數(shù)組長度無法改變視圖?
由于Vue只改寫了 7 種修改數(shù)組的方法,所以Vue不能檢測到以下數(shù)組的變動:
當(dāng)你利用索引直接設(shè)置一個數(shù)組項時,例如:vm.items[indexOfItem] = newValue
當(dāng)你修改數(shù)組的長度時,例如: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)
復(fù)制代碼
為了解決第二個問題,Vue提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
復(fù)制代碼
數(shù)組的響應(yīng)式是怎么實現(xiàn)的?
擇對7種數(shù)組(push,shift,pop,splice,unshift,sort,reverse)方法進行重寫
所以在Vue中修改數(shù)組的索引和長度是無法監(jiān)控到的。需要通過以上7種變異方法修改數(shù)組才會觸發(fā)數(shù)組對應(yīng)的watcher進行更新
// src/obserber/array.js\
// 先保留數(shù)組原型\
const arrayProto = Array.prototype;\
// 然后將arrayMethods繼承自數(shù)組原型\
// 這里是面向切片編程思想(AOP)--不破壞封裝的前提下,動態(tài)的擴展功能\
export const arrayMethods = Object.create(arrayProto);\
let methodsToPatch = [\
? "push",\
? "pop",\
? "shift",\
? "unshift",\
? "splice",\
? "reverse",\
? "sort",\
];\
methodsToPatch.forEach((method) => {\
? arrayMethods[method] = function (...args) {\
? ? //? 這里保留原型方法的執(zhí)行結(jié)果\
? ? const result = arrayProto[method].apply(this, args);\
? ? // 這句話是關(guān)鍵\
? ? // this代表的就是數(shù)據(jù)本身 比如數(shù)據(jù)是{a:[1,2,3]} 那么我們使用a.push(4)? this就是a? ob就是a.__ob__ 這個屬性就是上段代碼增加的 代表的是該數(shù)據(jù)已經(jīng)被響應(yīng)式觀察過了指向Observer實例\
? ? const ob = this.__ob__;\
\
? ? // 這里的標(biāo)志就是代表數(shù)組有新增操作\
? ? let inserted;\
? ? switch (method) {\
? ? ? case "push":\
? ? ? case "unshift":\
? ? ? ? inserted = args;\
? ? ? ? break;\
? ? ? case "splice":\
? ? ? ? inserted = args.slice(2);\
? ? ? default:\
? ? ? ? break;\
? ? }\
? ? // 如果有新增的元素 inserted是一個數(shù)組 調(diào)用Observer實例的observeArray對數(shù)組每一項進行觀測\
? ? if (inserted) ob.observeArray(inserted);\
? ? // 之后咱們還可以在這里檢測到數(shù)組改變了之后從而觸發(fā)視圖更新的操作--后續(xù)源碼會揭曉\
? ? return result;\
? };\
});
復(fù)制代碼
$nextTick 的原理是什么?nextTick 中的回調(diào)是在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行的延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的 DOM。主要思路就是采用微任務(wù)優(yōu)先的方式調(diào)用異步方法去執(zhí)行 nextTick 包裝的方法。
簡單的理解是:當(dāng)數(shù)據(jù)更新了,在 dom 中渲染后, 自動執(zhí)行該函數(shù)。Vue 實現(xiàn)響應(yīng)式并不是數(shù)據(jù)發(fā)生變化之后 DOM 立即變化,Vue 是異步執(zhí)行 DOM 更新的。created 鉤子函數(shù)進行的 DOM 操作一定要放在 Vue.nextTick() 的回調(diào)函數(shù)中,原因是在函數(shù)執(zhí)行的時候 DOM 其實并未進行任何渲染。常用的場景是在進行獲取數(shù)據(jù)后,需要對新視圖進行下一步操作或者其他操作時,發(fā)現(xiàn)獲取不到 dom。因為賦值操作只完成了數(shù)據(jù)模型的改變并沒有完成視圖更新。
有一個 timerFunc 這個函數(shù)用來執(zhí)行 callbacks 里存儲的所有回調(diào)函數(shù)
先判斷是否原生支持 promise,如果支持,則利用 promise 來觸發(fā)執(zhí)行回調(diào)函數(shù);
否則,如果支持 MutationObserver,則實例化一個觀察者對象,觀察文本節(jié)點發(fā)生變化時,觸發(fā)執(zhí)行所有回調(diào)函數(shù)。? ? 如果都不支持,則利用 setTimeout 設(shè)置延時為 0。
列表循環(huán)時 key 有什么作用?key 是為 Vue 中 vnode 的唯一標(biāo)記,通過這個 key,我們的 diff 操作可以更準(zhǔn)確、更快速。Vue 的 diff 過程可以概括為:oldCh 和 newCh 各有兩個頭尾的變量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它們會新節(jié)點和舊節(jié)點會進行兩兩對比,即一共有 4 種比較方式:newStartIndex 和 oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 種比較都沒匹配,如果設(shè)置了 key,就會用 key 再進行比較,在比較的過程中,遍歷會往中間靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一個已經(jīng)遍歷完了,就會結(jié)束比較。
所以 Vue 中 key 的作用是:key 是為 Vue 中 vnode 的唯一標(biāo)記,通過這個 key,我們的 diff 操作可以更準(zhǔn)確、更快速,因為帶 key 就不是就地復(fù)用了,在 sameNode 函數(shù) a.key === b.key 對比中可以避免就地復(fù)用的情況。利用 key 的唯一性生成 map 對象來獲取對應(yīng)節(jié)點,比遍歷方式更快,源碼如下:
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
}
復(fù)制代碼
為什么不建議用 index 作為 key?
使用index作為key和沒寫基本上沒區(qū)別,因為不管數(shù)組的順序怎么顛倒,index都是?0, 1, 2...這樣排列,導(dǎo)致Vue會復(fù)用錯誤的舊子節(jié)點,做很多額外的工作。
v-if、v-show、v-html 的原理
v-if會調(diào)用addIfCondition方法,生成vnode的時候會忽略對應(yīng)節(jié)點,render的時候就不會渲染;
v-show會生成vnode,render的時候也會渲染成真實節(jié)點,只是在render過程中會在節(jié)點的屬性中修改show屬性值,也就是常說的display;
v-html會先移除節(jié)點下的所有節(jié)點,設(shè)置innerHTML為v-html的值。
Vue 中封裝的數(shù)組方法有哪些,其如何實現(xiàn)頁面更新
數(shù)組就是使用object.defineProperty重新定義數(shù)組的每一項,那能引起數(shù)組變化的方法我們都是知道的,
pop、
push、
shift、
unshift、
splice、
sort、
reverse
這七種,只要這些方法執(zhí)行改了數(shù)組內(nèi)容,我就更新內(nèi)容就好了,是不是很好理解。
是用來函數(shù)劫持的方式,重寫了數(shù)組方法,具體呢就是更改了數(shù)組的原型,更改成自己的,用戶調(diào)數(shù)組的一些方法的時候,走的就是自己的方法,然后通知視圖去更新。
數(shù)組里每一項可能是對象,那么我就是會對數(shù)組的每一項進行觀測,(且只有數(shù)組里的對象才能進行觀測,觀測過的也不會進行觀測)
vue3:改用proxy,可直接監(jiān)聽對象數(shù)組的變化。
Vue 模板渲染的原理是什么?
vue中的模板template無法被瀏覽器解析并渲染,因為這不屬于瀏覽器的標(biāo)準(zhǔn),不是正確的HTML語法,所有需要將template轉(zhuǎn)化成一個JavaScript函數(shù),這樣瀏覽器就可以執(zhí)行這一個函數(shù)并渲染出對應(yīng)的HTML元素,就可以讓視圖跑起來了,這一個轉(zhuǎn)化的過程,就成為模板編譯。
模板編譯又分三個階段,解析parse,優(yōu)化optimize,生成generate,最終生成可執(zhí)行函數(shù)render。
parse 階段:使用大量的正則表達式對template字符串進行解析,將標(biāo)簽、指令、屬性等轉(zhuǎn)化為抽象語法樹AST。
optimize 階段:遍歷AST,找到其中的一些靜態(tài)節(jié)點并進行標(biāo)記,方便在頁面重渲染的時候進行diff比較時,直接跳過這一些靜態(tài)節(jié)點,優(yōu)化runtime的性能。
generate 階段:將最終的AST轉(zhuǎn)化為render函數(shù)字符串。
說一下什么是 Virtual DOM
Virtual DOM是DOM節(jié)點在JavaScript中的一種抽象數(shù)據(jù)結(jié)構(gòu),之所以需要虛擬DOM,是因為瀏覽器中操作DOM的代價比較昂貴,頻繁操作DOM會產(chǎn)生性能問題。虛擬DOM的作用是在每一次響應(yīng)式數(shù)據(jù)發(fā)生變化引起頁面重渲染時,Vue對比更新前后的虛擬DOM,匹配找出盡可能少的需要更新的真實DOM,從而達到提升性能的目的。
Vue data 中某一個屬性的值發(fā)生改變后,視圖會立即同步執(zhí)行重新渲染嗎?
Vue是異步執(zhí)行DOM更新。
只要觀察到數(shù)據(jù)變化,Vue將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。
如果同一個watcher被多次觸發(fā),只會被推入到隊列中一次。這種在緩沖時去除重復(fù)數(shù)據(jù)對于避免不必要的計算和DOM操作上非常重要。
然后,在下一個的事件循環(huán)tick中,Vue刷新隊列并執(zhí)行實際 (已去重的) 工作。Vue在內(nèi)部嘗試對異步隊列使用原生的Promise.then和MessageChannel,如果執(zhí)行環(huán)境不支持,會采用setTimeout(fn, 0)代替。
例如,當(dāng)你設(shè)置vm.someData = 'new value',該組件不會立即重新渲染。
當(dāng)刷新隊列時,組件會在事件循環(huán)隊列清空時的下一個tick更新。
多數(shù)情況我們不需要關(guān)心這個過程,但是如果你想在DOM狀態(tài)更新后做點什么,這就可能會有些棘手。
雖然Vue.js通常鼓勵開發(fā)人員沿著 “數(shù)據(jù)驅(qū)動” 的方式思考,避免直接接觸DOM,但是有時我們確實要這么做。為了在數(shù)據(jù)變化之后等待Vue完成更新DOM,可以在數(shù)據(jù)變化之后立即使用Vue.nextTick(callback)?。這樣回調(diào)函數(shù)在DOM更新完成后就會調(diào)用。
Vue.mixin 的使用場景和原理
在日常的開發(fā)中,我們經(jīng)常會遇到在不同的組件中經(jīng)常會需要用到一些相同或者相似的代碼,這些代碼的功能相對獨立,可以通過Vue的mixin功能抽離公共的業(yè)務(wù)邏輯,原理類似“對象的繼承”,當(dāng)組件初始化時會調(diào)用mergeOptions方法進行合并,采用策略模式針對不同的屬性進行合并。當(dāng)組件和混入對象含有同名選項時,這些選項將以恰當(dāng)?shù)姆绞竭M行“合并”。
Vue.extend 作用和原理
其實就是一個子類構(gòu)造器 ,是Vue組件的核心api實現(xiàn)思路就是使用原型繼承的方法返回了Vue的子類 并且利用mergeOptions把傳入組件的options和父類的options進行了合并。
Vue 事件綁定原理
原生事件綁定是通過addEventListener綁定給真實元素的,組件事件綁定是通過Vue自定義的$on實現(xiàn)的。如果要在組件上使用原生事件,需要加.native修飾符,這樣就相當(dāng)于在父組件中把子組件當(dāng)做普通html標(biāo)簽,然后加上原生事件。
on、emit是基于發(fā)布訂閱模式的,維護一個事件中心,on的時候?qū)⑹录疵Q存在事件中心里,稱之為訂閱者,然后emit將對應(yīng)的事件進行發(fā)布,去執(zhí)行事件中心里的對應(yīng)的監(jiān)聽器。
虛擬 DOM 實現(xiàn)原理?
虛擬DOM的實現(xiàn)原理主要包括以下3部分:
用JavaScript對象模擬真實DOM樹,對真實DOM進行抽象;
diff算法 — 比較兩棵虛擬DOM樹的差異;
pach算法 — 將兩個虛擬DOM對象的差異應(yīng)用到真正的DOM樹。
虛擬 dom 和真實 dom 的區(qū)別
虛擬DOM不會進行排版與重繪操作
虛擬DOM就是把真實DOM轉(zhuǎn)換為Javascript代碼
虛擬DOM進行頻繁修改,然后一次性比較并修改真實DOM中需要改的部分,最后并在真實DOM中進行排版與重繪,減少過多DOM節(jié)點排版與重繪損耗
生態(tài)相關(guān)
vue-router 路由模式有幾種?
vue-router有 3 種路由模式:hash、history、abstract:
hash: 使用URL hash值來作路由。支持所有瀏覽器,包括不支持HTML5 History Api的瀏覽器;
history: 依賴HTML5 History API和服務(wù)器配置。
abstract: 支持所有JavaScript運行環(huán)境,如Node.js服務(wù)器端。如果發(fā)現(xiàn)沒有瀏覽器的API,路由會自動強制進入這個模式.
路由的 hash 和 history 模式的區(qū)別
(1)hash 模式的實現(xiàn)原理
早期的前端路由的實現(xiàn)就是基于location.hash來實現(xiàn)的。其實現(xiàn)原理很簡單,location.hash的值就是URL中#后面的內(nèi)容。比如下面這個網(wǎng)站,它的location.hash的值為#search:
https://www.word.com#search
復(fù)制代碼
hash路由模式的實現(xiàn)主要是基于下面幾個特性:
URL中hash值只是客戶端的一種狀態(tài),也就是說當(dāng)向服務(wù)器端發(fā)出請求時,hash部分不會被發(fā)送;
hash值的改變,都會在瀏覽器的訪問歷史中增加一個記錄。因此我們能通過瀏覽器的回退、前進按鈕控制hash的切換;
可以通過a標(biāo)簽,并設(shè)置href屬性,當(dāng)用戶點擊這個標(biāo)簽后,URL的hash值會發(fā)生改變;或者使用JavaScript來對loaction.hash進行賦值,改變URL的hash值;
我們可以使用hashchange事件來監(jiān)聽hash值的變化,從而對頁面進行跳轉(zhuǎn)(渲染)。
(2)history 模式的實現(xiàn)原理
HTML5提供了History API來實現(xiàn)URL的變化。其中做最主要的API有以下兩個:history.pushState()和history.repalceState()。這兩個API可以在不進行刷新的情況下,操作瀏覽器的歷史紀(jì)錄。唯一不同的是,前者是新增一個歷史記錄,后者是直接替換當(dāng)前的歷史記錄,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
復(fù)制代碼
history路由模式的實現(xiàn)主要基于存在下面幾個特性:
pushState和repalceState兩個API來操作實現(xiàn)URL的變化 ;
我們可以使用popstate事件來監(jiān)聽url的變化,從而對頁面進行跳轉(zhuǎn)(渲染);
history.pushState()或history.replaceState()不會觸發(fā)popstate事件,這時我們需要手動觸發(fā)頁面跳轉(zhuǎn)(渲染)。
如何獲取頁面的 hash 變化
監(jiān)聽$route對象
// 監(jiān)聽,當(dāng)路由發(fā)生變化的時候執(zhí)行
watch: {
$route: {
handler: function(val, oldVal){
? console.log(val);
},
? // 深度觀察監(jiān)聽
deep: true
}
復(fù)制代碼
route 和 router?的區(qū)別
$router是VueRouter的實例,在script標(biāo)簽中想要導(dǎo)航到不同的URL,使用$router.push方法。返回上一個歷史history用$router.to(-1)
$route為當(dāng)前router跳轉(zhuǎn)對象。里面可以獲取當(dāng)前路由的name,path,query,parmas等。
如何定義動態(tài)路由?如何獲取傳過來的動態(tài)參數(shù)?
可以通過query,param兩種方式
區(qū)別:query通過url傳參,刷新頁面還在;params屬性頁面不在
params的類型:
配置路由格式:/router/:id
傳遞的方式:在path后面跟上對應(yīng)的值
傳遞后形成的路徑:/router/123
通過$route.params.id獲取傳遞的值
query的類類型
配置路由格式:/router也就是普通配置
傳遞的方式:對象中使用query的key作為傳遞方式
傳遞后形成的路徑:/route?id=123
通過$route.query獲取傳遞的值
Vue-router 導(dǎo)航守衛(wèi)有哪些
Vue-router 跳轉(zhuǎn)和 location.href 有什么區(qū)別
使用location.href= /url?來跳轉(zhuǎn),簡單方便,但是刷新了頁面;
使用history.pushState( /url ),無刷新頁面,靜態(tài)跳轉(zhuǎn);
引進router,然后使用router.push( /url )來跳轉(zhuǎn),使用了diff算法,實現(xiàn)了按需加載,減少了dom的消耗。
其實使用router跳轉(zhuǎn)和使用history.pushState()沒什么差別的,因為vue-router就是用了history.pushState(),尤其是在history模式下。
params 和 query 的區(qū)別
用法:query要用path來引入,params要用name來引入,接收參數(shù)都是類似的,分別是this.$route.query.name和this.$route.params.name。
url 地址顯示:query更加類似于我們ajax中g(shù)et傳參,params則類似于post,說的再簡單一點,前者在瀏覽器地址欄中顯示參數(shù),后者則不顯示。
注意點:query刷新不會丟失query里面的數(shù)據(jù),params刷新會丟失params里面的數(shù)據(jù)。
Vuex 的原理
Vue組件會觸發(fā)(dispatch)一些事件或動作,也就是Actions;
在組件中發(fā)出的動作,肯定是想獲取或者改變數(shù)據(jù)的,但是在vuex中,數(shù)據(jù)是集中管理的,不能直接去更改數(shù)據(jù),所以會把這個動作提交(Commit)到Mutations中;
然后Mutations就去改變State中的數(shù)據(jù);
當(dāng)State中的數(shù)據(jù)被改變之后,就會重新渲染(Render)到Vue Components中去,組件展示更新后的數(shù)據(jù),完成一個流程。
Vuex 有哪幾種屬性
有五種,分別
State:定義了應(yīng)用狀態(tài)的數(shù)據(jù)結(jié)構(gòu),可以在這里設(shè)置默認的初始狀態(tài)。
Getter:允許組件從Store中獲取數(shù)據(jù),mapGetters輔助函數(shù)僅僅是將store中的getter映射到局部計算屬性。
Mutation:是唯一更改store中狀態(tài)的方法,且必須是同步函數(shù)。
Action:用于提交mutation,而不是直接變更狀態(tài),可以包含任意異步操作。
Module:允許將單一的Store拆分為多個store且同時保存在單一的狀態(tài)樹中。
Vuex 和單純的全局對象有什么區(qū)別?
Vuex的狀態(tài)存儲是響應(yīng)式的。當(dāng)Vue組件從store中讀取狀態(tài)的時候,若store中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會相應(yīng)地得到高效更新。
不能直接改變store中的狀態(tài)。改變store中的狀態(tài)的唯一途徑就是顯式地提交mutation。
Vuex 中 action 和 mutation 的區(qū)別
Mutation專注于修改State,理論上是修改State的唯一途徑;Action業(yè)務(wù)代碼、異步請求。
Mutation:必須同步執(zhí)行;Action:可以異步,但不能直接操作State。
在視圖更新時,先觸發(fā)actions,actions再觸發(fā)mutation
mutation的參數(shù)是state,它包含store中的數(shù)據(jù);action的參數(shù)是context,它是state的父級,包含state、getters等。
為什么 Vuex 的 mutation 中不能做異步操作?
Vuex中所有的狀態(tài)更新的唯一途徑都是mutation,異步操作通過Action來提交mutation實現(xiàn),這樣可以方便地跟蹤每一個狀態(tài)的變化,從而能夠?qū)崿F(xiàn)一些工具幫助更好地了解我們的應(yīng)用。
每個mutation執(zhí)行完成后都會對應(yīng)到一個新的狀態(tài)變更,這樣devtools就可以打個快照存下來。如果mutation支持異步操作,就沒有辦法知道狀態(tài)是何時更新的,無法很好的進行狀態(tài)的追蹤,給調(diào)試帶來困難。
Vuex 和 localStorage 的區(qū)別
(1)最重要的區(qū)別
vuex存儲在內(nèi)存中
localstorage則以文件的方式存儲在本地,只能存儲字符串類型的數(shù)據(jù),存儲對象需要JSON的stringify和parse方法進行處理。 讀取內(nèi)存比讀取硬盤速度要快
(2)應(yīng)用場景
Vuex是一個專為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。vuex用于組件之間的傳值。
localstorage是本地存儲,是將數(shù)據(jù)存儲到瀏覽器的方法,一般是在跨頁面?zhèn)鬟f數(shù)據(jù)時使用 。
Vuex能做到數(shù)據(jù)的響應(yīng)式,localstorage不能
(3)永久性
刷新頁面時vuex存儲的值會丟失,localstorage不會,對于不變的數(shù)據(jù)可以用localstorage可以代替vuex。
Vuex 的嚴(yán)格模式是什么,有什么作用,如何開啟?
在嚴(yán)格模式下,無論何時發(fā)生了狀態(tài)變更且不是由mutation函數(shù)引起的,將會拋出錯誤。這能保證所有的狀態(tài)變更都能被調(diào)試工具跟蹤到。
在Vuex.Store構(gòu)造器選項中開啟,如下
const store = new Vuex.Store({
? ? strict:true,
})
復(fù)制代碼
如何在組件中批量使用 Vuex 的 getter 屬性
使用mapGetters輔助函數(shù), 利用對象展開運算符將getter混入computed對象中
import {mapGetters} from 'vuex'
export default{
? ? computed:{
? ? ? ? ...mapGetters(['total','discountTotal'])
? ? }
}
復(fù)制代碼
如何在組件中重復(fù)使用 Vuex 的 mutation
使用mapMutations輔助函數(shù),在組件中這么使用
import { mapMutations } from 'vuex'
methods:{
? ? ...mapMutations({
? ? ? ? setNumber:'SET_NUMBER',
? ? })
}
復(fù)制代碼
Vuex 頁面刷新數(shù)據(jù)丟失怎么解決
在created周期中讀取sessionstorage中的數(shù)據(jù)存儲在store中,此時用vuex.store的replaceState方法,替換store的根狀態(tài)
在beforeunload方法中將store.state存儲到sessionstorage中。
export default {
? name: 'App',
? created() {
? ? //在頁面加載時讀取sessionStorage里的狀態(tài)信息
? ? if (sessionStorage.getItem("store")) {
? ? ? this.$store.replaceState(Object.assign({},
? ? ? ? this.$store.state, JSON.parse(sessionStorage.getItem("store"))))
? ? }
? ? //在頁面刷新時將vuex里的信息保存到sessionStorage里
? ? window.addEventListener("beforeunload", () => {
? ? ? sessionStorage.setItem("store", JSON.stringify(this.$store.state))
? ? })
? }
}
復(fù)制代碼
3.0 相關(guān)
Vue3.0 有什么更新
Vue3.0 defineProperty 和 proxy 的區(qū)別
Vue3.x改用Proxy替代Object.defineProperty。因為Proxy可以直接監(jiān)聽對象和數(shù)組的變化,并且有多達13種攔截方法。
Proxy 與 Object.defineProperty 優(yōu)劣對比
Proxy 的優(yōu)勢如下:
Proxy可以直接監(jiān)聽對象而非屬性;
Proxy可以直接監(jiān)聽數(shù)組的變化;
Proxy返回的是一個新對象,我們可以只操作新的對象達到目的,而Object.defineProperty只能遍歷對象屬性直接修改;
Proxy作為新標(biāo)準(zhǔn)將受到瀏覽器廠商重點持續(xù)的性能優(yōu)化,也就是傳說中的新標(biāo)準(zhǔn)的性能紅利;
Object.defineProperty 的優(yōu)勢如下:
兼容性好,支持IE9。
Vue 3.0 生命周期有哪些變化?

注意:3.0中的生命周期鉤子要比2.X中相同生命周期的鉤子要快
Composition API還新增了以下調(diào)試鉤子函數(shù):但是不怎么常用
onRenderTracked
onRenderTriggered
Vue 3.0 自定義指令有哪些變化?
先看看Vue2自定義指令的鉤子
bind:當(dāng)指令綁定在對應(yīng)元素時觸發(fā)。只會觸發(fā)一次。
inserted:當(dāng)對應(yīng)元素被插入到DOM的父元素時觸發(fā)。
update:當(dāng)元素更新時,這個鉤子會被觸發(fā)(此時元素的后代元素還沒有觸發(fā)更新)。
componentUpdated:當(dāng)整個組件(包括子組件)完成更新后,這個鉤子觸發(fā)。
unbind:當(dāng)指令被從元素上移除時,這個鉤子會被觸發(fā)。也只觸發(fā)一次。
在?Vue3中,官方為了更有助于代碼的可讀性和風(fēng)格統(tǒng)一,把自定義指令的鉤子名稱改的更像是組件生命周期,盡管他們是兩回事
bind=>beforeMount
inserted=>mounted
beforeUpdate:新的鉤子,會在元素自身更新前觸發(fā)
update=>移除!
componentUpdated=>updated
beforeUnmount:新的鉤子,當(dāng)元素自身被卸載前觸發(fā)
unbind=>unmounted
后語
最后祝大家在新的一年里,都能找到滿意的工作,升職加薪,賺的盆滿缽滿!
最后
如果你覺得此文對你有一丁點幫助,點個贊?;蛘呖梢约尤胛业拈_發(fā)交流群:780179818 相互學(xué)習(xí),我們會有專業(yè)的技術(shù)答疑解惑
如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源項目點點 star:http://github.crmeb.net/u/lsq不勝感激 !
PHP 學(xué)習(xí)手冊:https://doc.crmeb.com
技術(shù)交流論壇:https://q.crmeb.com