前端高階面試題之Vue

.什么是vue生命周期

Vue 實(shí)例從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模板、掛載Dom→渲染、更新→渲染、銷毀等一系列過程,稱之為 Vue 的生命周期。
作用: 生命周期中有多個(gè)事件鉤子,在控制整個(gè)Vue實(shí)例的過程時(shí)更容易形成好的邏輯。
beforeCreate: 完成實(shí)例初始化,this指向被創(chuàng)建的實(shí)例,data,computed,watch,mothods方法和數(shù)據(jù)都不可以訪問,數(shù)據(jù)觀測之前(data observer)被調(diào)用。
created: 實(shí)例創(chuàng)建完成,data,computed,watch,methods 可被訪問,未掛載dom,可對 data 進(jìn)行操作,操作 dom 需放到nextTick
beforeMount: 有了el,找到對應(yīng)的 template 編譯成 render 函數(shù)
mounted: 完成掛載dom和渲染,可對dom進(jìn)行操作,并獲取dom節(jié)點(diǎn),可發(fā)起后端請求拿到數(shù)據(jù)
beforeUpdate: 數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬Dom重新渲染和打補(bǔ)丁之前之調(diào)用
updated: 組件 dom 已完成更新,可執(zhí)行依賴的 dom 操作,不要操作數(shù)據(jù)會陷入死循環(huán)
beforeDestroy: 實(shí)例銷毀之前調(diào)用,可進(jìn)行優(yōu)化操作,如銷毀定時(shí)器,解除綁定事件
destroyed: 組件已經(jīng)被銷毀,事件監(jiān)聽器和子實(shí)例都會被移除銷毀

第一次頁面加載會觸發(fā): beforeCreate, created, beforeMount, mounted
并且`` 渲染在 mounted 中就已經(jīng)完成了。

可以使用 $on('hook:')$once('hook:') 來簡化生命周期的注冊

.請說一下 Vue 響應(yīng)式數(shù)據(jù)的原理是什么?

在 Vue 初始化數(shù)據(jù)時(shí), 使用 Object.defineProperty 重新定義 data 中所有屬性,增加了數(shù)據(jù) 獲取(getter) / 設(shè)置(setter) 的攔截功能。在 獲取 / 設(shè)置 時(shí)可增加一些邏輯,這個(gè)邏輯交叫作 依賴收集。當(dāng)頁面取到對應(yīng)屬性時(shí)會進(jìn)行依賴收集, 如果屬性發(fā)生變化, 則會通知收集的依賴進(jìn)行更新,而負(fù)責(zé)收集的就是 watcher。
如負(fù)責(zé)渲染的 watcher 會在頁面渲染的時(shí)候?qū)?shù)據(jù)進(jìn)行取值,并把當(dāng)前 watcher 先存起來對應(yīng)到數(shù)據(jù)上,當(dāng)更新數(shù)據(jù)的時(shí)候告訴對應(yīng)的 watcher 去更新, 從而實(shí)現(xiàn)了數(shù)據(jù)響應(yīng)式。

data 一般分為兩大類: 對象類型 和 數(shù)組:

對象:

在 Vue 初始化的時(shí)候,會調(diào)用 initData 方法初始化 data,它會拿到當(dāng)前用戶傳入的數(shù)據(jù)。判斷如果已經(jīng)被觀測過則不在觀測,如果沒有觀測過則利用 new Observer 創(chuàng)建一個(gè)實(shí)例用來觀測數(shù)據(jù)。如果數(shù)據(jù)是對象類型非數(shù)組的話會調(diào)用 this.walk(value) 方法把數(shù)據(jù)進(jìn)行遍歷,在內(nèi)部使用 definReactive 方法重新定義( definReactive 是比較核心的方法: 定義響應(yīng)式 ),而重新定義采用的就是 Object.defineProperty 。如當(dāng)前對象的值還是個(gè)對象,會自動(dòng)調(diào)用遞歸觀測。當(dāng)用戶取值的時(shí)候會調(diào)用 get 方法并收集當(dāng)前的 wacther 。在 set 方法里,數(shù)據(jù)變化時(shí)會調(diào)用 notify 方法觸發(fā)數(shù)據(jù)對應(yīng)的依賴進(jìn)行更新。

數(shù)組:

使用函數(shù)劫持的方式重寫了數(shù)組的方法,并進(jìn)行了原型鏈重寫。使 data 中的數(shù)組指向了自己定義的數(shù)組原型方法。這樣的話,當(dāng)調(diào)用數(shù)組 API 時(shí),可以通知依賴更新。如果數(shù)組中包含著引用類型,則會對數(shù)組中的引用類型進(jìn)行再次監(jiān)控。
也就是當(dāng)創(chuàng)建了 Observer 觀測實(shí)例后,如果數(shù)據(jù)是數(shù)組的話,判斷是否支持自己原型鏈,如果不支持則調(diào)用 protoAugment 方法使目標(biāo)指向 arrayMethods 方法。arrayMethods 就是重寫的數(shù)組方法,包括 push、popshift 、unshift、splice、sortreverse 共七個(gè)可以改變數(shù)組的方法,內(nèi)部采用函數(shù)劫持的方式。在數(shù)組調(diào)用重寫的方法之后,還是會調(diào)用原數(shù)組方法去更新數(shù)組。只不過重寫的方法會通知視圖更新。如果使用 push、unshiftsplice 等方法新增數(shù)據(jù),會調(diào)用 observeArray 方法對插入的數(shù)據(jù)再次進(jìn)行觀測。
如果數(shù)組中有引用類型,則繼續(xù)調(diào)用 observeArray 方法循環(huán)遍歷每一項(xiàng),繼續(xù)深度觀測。前提是每一項(xiàng)必須是對象類型, 否則 observe 方法會直接 return

.為何Vue采用異步渲染?

如不采用異步更新, 則每次更新數(shù)據(jù)都會對當(dāng)前組件進(jìn)行重新渲染, 因此為了性能考慮 Vue 在本輪數(shù)據(jù)更新結(jié)束后,再去異步更新視圖。
當(dāng)數(shù)據(jù)變化之后, 會調(diào)用 notify 方法去通知 watcher 進(jìn)行數(shù)據(jù)更新。而 watcher 會調(diào)用 update 方法進(jìn)行更新(這里就是發(fā)布訂閱模式)。更新時(shí)并不是讓 wathcer 立即執(zhí)行, 而是放在一個(gè)隊(duì)列里進(jìn)行過濾,相同的 watcher 只存一個(gè), 這個(gè)隊(duì)列就是 queueWatcher 方法。最后在調(diào)用 nextTick 方法通過 flushSchedulerQueue 異步清空 watcher 隊(duì)列。

.nextTick 實(shí)現(xiàn)原理?

nextTick 方法主要是使用了宏任務(wù)微任務(wù)定義了一個(gè)異步方法。多次調(diào)用 nextTick 會將方法存入隊(duì)列中, 通過這個(gè)異步方法清空當(dāng)前隊(duì)列。所以 nextTick 方法就是異步方法。
默認(rèn)在內(nèi)部調(diào)用 nextTick 時(shí)會傳入 flushSchedulerQueue 方法, 存在一個(gè)數(shù)組里并讓它執(zhí)行。用戶有時(shí)也會調(diào)用 nextTick , 調(diào)用時(shí)把用戶傳過來的 cb 也放在數(shù)組里 , 都是同一個(gè)數(shù)組 callbacks 。多次調(diào)用 nextTick 只會執(zhí)行一次, 等到代碼都執(zhí)行完畢后,會調(diào)用 timerFunc 這個(gè)異步方法。在方法里會依次進(jìn)行判斷所支持的類型:

  1. 如支持 Promise 則把 timerFunc 包裹在了 Promise 中并把 flushCallbacks 放在了 then 中, 相當(dāng)于異步執(zhí)行了 flushCallBacksflushCallBacks 函數(shù)作用就是讓傳過來的方法依次執(zhí)行。
  2. 如不是 IE 、支持 Mutationobserve 并且 是原生的 Mutationobserve。首先聲明一個(gè)變量并創(chuàng)建一個(gè)文本節(jié)點(diǎn)。接著創(chuàng)建 Mutationobserve 實(shí)例并把 flushCallBacks 傳入, 調(diào)用 observe 方法去觀測每一個(gè)節(jié)點(diǎn)。如果節(jié)點(diǎn)變化會異步執(zhí)行 flushCallBacks方法。
  3. 如果支持 setImmediate , 則調(diào)用 setImmediate 傳入flushCallBacks 異步執(zhí)行。
  4. 以上都不支持就只能調(diào)用 setTimeout 傳入 flushCallBacks 。

作用:$nextTick 是在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),在修改數(shù)據(jù)之后使用 $nextTick,則可以在回調(diào)中獲取更新后的 DOM。

.請說一下 Vue 中 Computed 和 watch ?

默認(rèn) computedwatch 內(nèi)部都是用一個(gè) watcher 實(shí)現(xiàn)的 。
computed有緩存功能, 不會先執(zhí)行, 只有當(dāng)依賴的屬性發(fā)生變化才會通知視圖跟新。
watcher 沒有緩存,默認(rèn)會先執(zhí)行,只要監(jiān)聽的屬性發(fā)生變化就會更新視圖。

computed

調(diào)用 initComputed 方法初始化計(jì)算屬性時(shí),會獲取到用戶定義的方法,并創(chuàng)建一個(gè) watcher 把用戶定義傳進(jìn)去, 這個(gè) watcher 有個(gè)標(biāo)識: lazy = true,默認(rèn)不會執(zhí)行用戶定義的函數(shù)。還有個(gè)標(biāo)識 dirty = true 默認(rèn)去求值 。watcher 內(nèi)部調(diào)用 defineComputed 方法將計(jì)算屬性定義在實(shí)例上, 其底層也是用的 Object.defineProperty。并且傳入了 createComputedGetter 方法定義一個(gè)計(jì)算屬性。在用戶取值時(shí),調(diào)用的是createComputedGetter返回函數(shù) computedGetter。判斷當(dāng)前的 watcher.dirty 是否為 true。如果為 true 則調(diào)用 watcher.evaluate 方法求值。在求值時(shí)是調(diào)用的 this.get() 方法.其實(shí) this.get() 就是用戶傳入的方法,執(zhí)行時(shí)會把方法里的屬性依次取值。而在取值前調(diào)用了 pushTarget 方法將 watcher 放在了全局上,當(dāng)取值時(shí)會進(jìn)行依賴收集,把當(dāng)前的計(jì)算屬性的 watcher 收集起來。等數(shù)據(jù)變化則通知 watcher 重新執(zhí)行,也就是進(jìn)入到了 update 方法中。update 并沒有直接讓 watcher 執(zhí)行,而是將 dirty = true。這樣的好處就是,如果 dirty = true,就進(jìn)行求值,否則就返回上次計(jì)算后的值,從而實(shí)現(xiàn)了緩存的機(jī)制。

watch

調(diào)用 initWatch 方法初始化 watch 的時(shí)候, 內(nèi)部傳入用戶定義的方法調(diào)用了 createWatcher 方法。在 createWatcher 方法中比較核心的就是 $watch 方法內(nèi)部調(diào)用了 new Watcher 并傳入了 expOrFn 和 回調(diào)函數(shù)。expOrFn 如果是個(gè)字符串的話, 會包裝成一個(gè)函數(shù)并返回這個(gè)字符串。這時(shí) lazy = false 了, 則直接調(diào)用了 this.get() 方法, 直接取屬性的值。同 computed 在取值前也執(zhí)行 pushTarget 方法將 watcher 放在了全局上, 當(dāng)用戶取值時(shí)就收集了 watcher。 因此當(dāng)屬性值發(fā)生改變時(shí), watcher 就會更新。
如果監(jiān)聽的屬性值是個(gè)對象, 則取對象里的值就不會更新了, 因?yàn)槟J(rèn)只能對屬性進(jìn)行依賴收集, 不能對屬性值是對象的進(jìn)行依賴收集。想要不管屬性值是否是對象都能求值進(jìn)行收集依賴, 可設(shè)置 deep = true 。如設(shè)置了deep = true ,則會調(diào)用 traverse 方法進(jìn)行遞歸遍歷。

.Vue 組件中 data 為什么必須是一個(gè)函數(shù)?

因?yàn)?js 本身的特性帶來的,同一個(gè)組件被復(fù)用多次,會創(chuàng)建多個(gè)實(shí)例。這些實(shí)例是同一個(gè)構(gòu)造函數(shù)。如果 data 是一個(gè)對象的話,那么所有組件都共享了同一個(gè)對象。為了保證組件中數(shù)據(jù)的獨(dú)立性要求每個(gè)組件必須通過data 函數(shù)返回一個(gè)對象作為組件的狀態(tài)。
Vue 通過 extend 創(chuàng)建子類之后,會調(diào)用 mergeOptions 方法合并父類和子類的選項(xiàng),選中就包括 data。在循環(huán)完父類和子類之后調(diào)用 mergeField 函數(shù)的中的 strat 方法去合并 data,如果 data 不是函數(shù)而是個(gè)對象,則會報(bào)錯(cuò)提示 data 應(yīng)該是個(gè)函數(shù)。

.談?wù)凪VVM模式

Model: 代表數(shù)據(jù)模型,也可以在Model中定義數(shù)據(jù)修改和操作的業(yè)務(wù)邏輯。
View: 代表UI 組件,它負(fù)責(zé)將數(shù)據(jù)模型轉(zhuǎn)化成UI 展現(xiàn)出來。
ViewModel: 監(jiān)聽模型數(shù)據(jù)的改變和控制視圖行為、處理用戶交互,簡單理解就是一個(gè)同步View 和 Model的對象,連接Model和View。

在MVVM架構(gòu)下,View 和 Model 之間并沒有直接的聯(lián)系,而是通過ViewModel進(jìn)行交互,Model 和 ViewModel 之間的交互是雙向自動(dòng)的, 因此View 數(shù)據(jù)的變化會同步到Model中,而Model 數(shù)據(jù)的變化也會立即反應(yīng)到View 上。而開發(fā)者只需關(guān)注業(yè)務(wù)邏輯,不需要手動(dòng)操作DOM, 不需要關(guān)注數(shù)據(jù)狀態(tài)的同步問題,復(fù)雜的數(shù)據(jù)狀態(tài)維護(hù)完全由 MVVM 來統(tǒng)一管理。

MVVM 和 MVC區(qū)別?

mvcmvvm其實(shí)區(qū)別并不大。都是一種設(shè)計(jì)思想。主要就是 mvcController 演變成 mvvm 中的 viewModelmvvm 主要解決了mvc 中大量的 DOM 操作使頁面渲染性能降低,加載速度變慢,影響用戶體驗(yàn)。和當(dāng) Model 頻繁發(fā)生變化,開發(fā)者需要主動(dòng)更新到 View 。

.Vue 中事件綁定原理

Vue 中事件綁定分為兩種:

  1. 原生事件綁定: 采用的是 addEventListener 實(shí)現(xiàn)
  2. 組件事件綁定: 采用的是 $on 方法實(shí)現(xiàn)

click 事件為例,普通 dom 元素綁定事件是 @click ,編譯出來是 onclick事件,組件綁定事件是 @click 組件自定義事件 和 @click.native原生事件兩種,編譯出來分別是 onclick 事件, nativeOnclick 事件。組件的 nativeOn 等價(jià)于普通元素的 on ,而組件的 on 單獨(dú)處理。
渲染頁面時(shí),普通 dom 會掉用 updateDOMListeners 方法,內(nèi)部先把 data.on 方法拿出來,然后調(diào)用 updateListeners 方法來添加一個(gè)監(jiān)聽事件,同時(shí)會傳入一個(gè) add$1 方法。內(nèi)部調(diào)用 addEventListener 方法直接把事件綁定到元素上。
而組件會調(diào)用 updateComponentListeners 方法。內(nèi)部也是調(diào)用 updateListeners 方法但傳入的是 add 方法。這里的 add 方法與普通元素的 domadd$1 方法略有不同,采用的是自己定義的發(fā)布訂閱模式 $on 方法,解析的是 on 方法,組件內(nèi)部通過 $emit 方法觸發(fā)的。還有 click.native 方法是直接把事件綁在了最外層元素上,用的也是 updateListeners 方法傳入 add$1方法。

.v-model 的實(shí)現(xiàn)原理是什么?

通俗講 v-model 可以看成是 value + input 的語法糖。
組件的 v-model 也確實(shí)是這樣 。在組件初始化的時(shí)候, 如果檢測到有 model 屬性,就會調(diào)用 transformModel 方法轉(zhuǎn)化 model。如果沒有 prop 屬性和 event 屬性, 則默認(rèn)會給組件 propvalue 屬性, 給 eventinput 事件 。把 prop 的屬性賦給了 data.attrs 并把值也給了它, 即 data.attrs.value = '我們所賦的值'。會給 on 綁定 input 事件, 對應(yīng)的就是 callback。
如果在組件內(nèi)自定義 modelpropevent, 這樣的話組件初始化的時(shí)候, 接受 屬性事件 時(shí)不再是 valueinput 了, 而是我們自定義的 屬性事件
如果是普通的標(biāo)簽, 則在運(yùn)行時(shí)會自動(dòng)判斷標(biāo)簽的類型, 生成不同的屬性 domProp 和 事件 on。還增加了指令 directive, 針對輸入框的輸入法加上了一些邏輯并做了校驗(yàn)和處理。

. Vue中的 v-show 和 v-if 是做什么用的, 兩者有什么區(qū)別?

v-if:會在 with 方法里進(jìn)行判斷,如果條件為 true 則創(chuàng)建相應(yīng)的虛擬節(jié)點(diǎn),否則就創(chuàng)建一個(gè)空的虛擬節(jié)點(diǎn)也就是不會渲染 DOM。
v-show: 會在 with 方法里創(chuàng)建了一個(gè)指令就 v-show,在運(yùn)行的時(shí)候處理指令,添加了 style: display = none / originalDisplay。

v-if 才是“真正的”條件渲染, 因?yàn)樗鼤_保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)谋讳N毀和重建。
v-if也是惰性的, 如果在初次渲染時(shí)條件為假, 則什么也不做,一直到條件第一次變?yōu)檎鏁r(shí), 才會渲染條件塊。

相比之下, v-show 就簡單的多,不管初始條件是什么,元素總會被渲染, 并且只是簡單的基于 css 進(jìn)行切換。

一般來說,v-if 有更高的切換開銷,v-show 有更高的初始渲染開銷。
因此,如需要頻繁的切換則使用 v-show 較好,如在運(yùn)行時(shí)條件不大可能改變則使用 v-if 較好。

. v-if 和 v-for 為什么不能連用?

v-for 的優(yōu)先級會比 v-if 要高, 在 調(diào)用 with 方法編譯時(shí)會先進(jìn)行循環(huán), 然后再去做 v-if的條件判斷, 因此性能不高。
因此一般會把 v-if 提出來放在 v-for 外層, 或者想要連用把渲染數(shù)據(jù)放在計(jì)算屬性里進(jìn)行過濾。

.Vue 中的 v-html 會導(dǎo)致哪些問題

v-html 其原理就是用 innerHtml 實(shí)現(xiàn)的的, 如果不能保證內(nèi)容是完全可以被依賴的, 則可能會導(dǎo)致 xxs 攻擊。
在運(yùn)行的時(shí)候, 調(diào)用 updateDOMProps 方法或解析配置的屬性, 如果判斷屬性是 innerHTML 的話, 會清除所有的子元素。

.Vue 中f父子組件的調(diào)用順序

組件的調(diào)用都是先父后子,渲染完成的過程順序都是先子后父
組件的銷毀操作是先父后子,銷毀完成的順序是先子后父
在頁面渲染的時(shí)候,先執(zhí)行父組件的 beforeCreate -> created->befroreMount, 當(dāng)父組件實(shí)例化完成的時(shí)候會調(diào)用 rander 方法, 判斷組件是不是有子組件, 如果有子組件則繼續(xù)渲染子組件以此類推。當(dāng)子組件實(shí)例化完成時(shí)候, 會把子組件的插入方法先存起來放到 instertedVNodeQueue 隊(duì)列里, 最后會調(diào)用 invokeIntertHook 方法把當(dāng)前的隊(duì)列依次執(zhí)行。
更新也是一樣, 先父beforeUpdate -> 子beforeUpdate 再到 子 updated -> 父 updated

.Vue 中組件怎么通訊?

  1. 父子通訊: 父 -> 子 props, 子 -> 父 $on / $emit
    通過 eventsMixin 方法中的 $on 方法 維護(hù)一個(gè)事件的數(shù)組,然后將函數(shù)名傳入 $emit 方法,循環(huán)遍歷出函數(shù)并執(zhí)行。
  2. 獲得父子組件實(shí)例的方式:$parent / $children
    在初始化的時(shí)候調(diào)用 initLifecycle 方法初始化 $parent$children 放在實(shí)例上
  3. 在父組件中提供數(shù)據(jù)供子組件/孫子組件注入進(jìn)來: Provide / Inject。
    通過 initProvideinitInjections 方法分別把 providereject 放在 $options 上。在調(diào)用 reject 的時(shí)候,調(diào)用 resolveInject 方法遍歷,查看父級是否有此屬性,有則就直接 return 并把它定義在自己的實(shí)例上。
  4. Ref 獲得實(shí)例的方式調(diào)用組件的屬性或方法
    ref 被用來給元素或子組件注冊引用信息。 引用信息將會注冊在父組件的 $refs 對象上。
    用在 DOM 上就是 DOM 實(shí)例,用在組件上就是組件實(shí)例
  5. Event bus 實(shí)現(xiàn)跨組件通訊
    實(shí)質(zhì)上還是基于 $on$emit,因?yàn)槊總€(gè)實(shí)例都有 $on$emit 并且事件的綁定和觸發(fā)必須在同一個(gè)實(shí)例,所以一般會專門定義一個(gè)實(shí)例去用于通信,如 Vue.prototype.$bnts = new Vue
  6. Vuex 狀態(tài)管理實(shí)現(xiàn)通訊
  7. $attrs$Listeners 實(shí)現(xiàn)數(shù)據(jù) 和 事件的傳遞,還有 v-bind="$prop"

.為什么使用異步組件?

可使用異步的方式加載組件,減少打包體積,主要依賴 import() 語法,可實(shí)現(xiàn)文件的分割加載

components:{
  testCpt: (resove) => import("../components/testCpt")  或
  testCpt: r => require(['@/views/assetsInfo/assetsProofList'],r)
}

加載組件的時(shí)候, 如果組件是個(gè)函數(shù)會調(diào)用 resolveAsyncComponent 方法, 并傳入組件定義的函數(shù) asyncFactory , 并讓其馬上執(zhí)行。因?yàn)槭钱惒降乃詧?zhí)行后并不會馬上返回結(jié)果, 而返回的是一個(gè) promise,因此沒有返回值, 返回的是一個(gè)占位符。
加載完成后,會執(zhí)行 factory 函數(shù)并傳入了成功/失敗的回調(diào)。在回調(diào) resolve 成功的回調(diào)時(shí)會調(diào)用 forceRander 方法, 內(nèi)部調(diào)用 $forceUpdate 強(qiáng)制刷新。之后 resolveAsyncComponent 判斷已經(jīng)執(zhí)行成功,就是去創(chuàng)建組件、初始化組件和渲染組件。

.什么是作用域插槽?

  1. 插槽: 創(chuàng)建組件虛擬節(jié)點(diǎn)時(shí),會將組件兒子的虛擬節(jié)點(diǎn)先保存起來。初始化組件時(shí),通過插槽屬性將兒子進(jìn)行分類。(作用域?yàn)楦附M件)
    渲染組件時(shí)會拿對應(yīng)的 slot 屬性的節(jié)點(diǎn)進(jìn)行替換操作。
  2. 作用域插槽: 在解析的時(shí)候不會作為組件的孩子節(jié)點(diǎn)。會解析成函數(shù),當(dāng)子組件渲染時(shí),會調(diào)用此函數(shù)進(jìn)行渲染。(作用域?yàn)樽咏M件)

普通插槽編譯時(shí)調(diào)用 createElement 方法創(chuàng)建組件,并把子節(jié)點(diǎn)生成虛擬 dom 做好標(biāo)識存起來。渲染時(shí)調(diào)用 randerSlot 方法循環(huán)匹配出對應(yīng)的虛擬節(jié)點(diǎn)在父組件替換當(dāng)前位置。
而作用域插槽在編譯時(shí)會把子組件編譯成函數(shù),函數(shù)不調(diào)用就不會渲染。也就是說在初始化組件的時(shí)候并不會渲染子節(jié)點(diǎn)。渲染頁面時(shí)調(diào)用 randerSlot 方法執(zhí)行子節(jié)點(diǎn)的函數(shù)并把對應(yīng)的屬性傳過來。當(dāng)節(jié)點(diǎn)渲染完成之后在組件內(nèi)部替換當(dāng)前位置。

.說說對keep-alive的了解

keep-alive 是一個(gè)抽象組件,可實(shí)現(xiàn)組件緩存。當(dāng)組件切換時(shí)不會對當(dāng)前組件進(jìn)行卸載。
算法: LRU -->最近最久未使用法
常用的生命周期: activateddeactivated

聲明 keep-alive 時(shí)在函數(shù)里設(shè)置了幾個(gè)屬性: props,created,destroyed,mountedrander 等;

  1. props: 調(diào)用 keep-alive 組件可設(shè)置的屬性,共有三個(gè)屬性如下:
    include: 想緩存的組件
    exclude:不想緩存的組件
    max:最多緩存多少個(gè)
  2. created: 創(chuàng)建一個(gè)緩存列表
  3. destroyed: 銷毀時(shí)清空所有緩存列表
  4. mounted: 會監(jiān)聽 include 和 exclude, 動(dòng)態(tài)添加 或 移除緩存.
  5. rander: 渲染時(shí)拿到第一個(gè)組件,拿到第一個(gè)組件,判斷是不是在緩存里.

.$route$router 的區(qū)別是什么?

$routerVueRouter 實(shí)例,是個(gè)全局路由對象,包含路由跳轉(zhuǎn)方法、鉤子函數(shù)等。
$route 是路由信息對象||跳轉(zhuǎn)的路由對象,每一個(gè)路由都會有一個(gè)route對象,是一個(gè)局部對象,包含path,params,hash,query,fullPath,matched,name等路由信息參數(shù)。

. Vue 路由的鉤子函數(shù)

首頁可以控制導(dǎo)航跳轉(zhuǎn),beforeEach,afterEach等,一般用于頁面title的修改。
一些需要登錄才能調(diào)整頁面的重定向功能。
beforeEach主要有3個(gè)參數(shù)to,from,next:
to:route即將進(jìn)入的目標(biāo)路由對象,
from:route當(dāng)前導(dǎo)航正要離開的路由
next:function一定要調(diào)用該方法resolve這個(gè)鉤子。執(zhí)行效果依賴next方法的調(diào)用參數(shù)??梢钥刂凭W(wǎng)頁的跳轉(zhuǎn)。

. vue-router有哪幾種路由守衛(wèi)?

全局守衛(wèi):beforeEach
后置守衛(wèi):afterEach
全局解析守衛(wèi):beforeResolve
路由獨(dú)享守衛(wèi):beforeEnter

全局路由勾子(router.beforeEach)
件路由勾子(beforeRouteEnter)
組件路由勾子的next里的回調(diào)(beforeRouteEnter)

.Vue 中常見的性能優(yōu)化

  1. 編碼優(yōu)化
    (1). 不要將所有的數(shù)據(jù)放在data里,data中的數(shù)據(jù)都會增加 getter和setter,收收集對應(yīng)的 watcher
    (2). 在v-for時(shí)給每項(xiàng)元素綁定事件必須使用時(shí)間代理
    (3). SPA頁面采用 keep-alive 緩存組件
    (4). 拆分組件(提高復(fù)用性,增加代碼的可維護(hù)性,減少不必要的渲染)
    (5). v-if 當(dāng)值為 false 時(shí)內(nèi)部指令不執(zhí)行具有阻斷功能,很多情況下使用v-if 代替 v-show
    (6). 使用 key 保證唯一性
    (7). 使用 Object.freeze 凍結(jié)數(shù)據(jù),凍結(jié)后不再有 gettersetter
    (8). 合理使用路由懶加載和異步組件
    (9). 數(shù)據(jù)持久化問題如: 防抖、節(jié)流
  2. Vue 加載性能優(yōu)化
    (1). 第三方模塊按需導(dǎo)入(babel-plugin-component)
    (2). 滾動(dòng)可視區(qū)域動(dòng)態(tài)加載(vue-virtual-scroll-list / 'vue-virtual-scroller') -- 長列表優(yōu)化
    (3). 圖片懶加載(vue-lazyload)
  3. 用戶體驗(yàn)
    (1). app-skeleton 骨架屏
    (2). app-sheapp
  4. SEO 優(yōu)化
    (1). 預(yù)加載插件 prerender-spa-plugin
    (2). 服務(wù)端渲染 ssr
  5. 打包優(yōu)化
    (1). 使用 CDN 的方式加載第三方模塊
    (2). 多線程打包
    (3). splitChunk 抽離公共文件
  6. 緩存 壓縮
    (1). 客戶端緩存和服務(wù)端緩存
    (2). 服務(wù)端gzip壓縮

.Vue等單頁面應(yīng)用(spa)及其優(yōu)缺點(diǎn)

優(yōu)點(diǎn): Vue的目標(biāo)是通過盡可能簡單的 API實(shí)現(xiàn)響應(yīng)的數(shù)據(jù)綁定和組合的視圖組件,核心是一個(gè)響應(yīng)的數(shù)據(jù)綁定系統(tǒng)。MVVM、數(shù)據(jù)驅(qū)動(dòng)、組件化、輕量、簡潔、高效、快速、模塊友好;即第一次就將所有的東西都加載完成,因此,不會導(dǎo)致頁面卡頓。
缺點(diǎn): 不支持低版本的瀏覽器,最低只支持到IE9;不利于SEO的優(yōu)化(如果要支持SEO,建議通過服務(wù)端來進(jìn)行渲染組件);第一次加載首頁耗時(shí)相對長一些;不可以使用瀏覽器的導(dǎo)航按鈕需要自行實(shí)現(xiàn)前進(jìn)、后退。

.assets 和 static的區(qū)別

相同點(diǎn): assets 和 static兩個(gè)都是存放靜態(tài)資源文件。項(xiàng)目中所需要的資源文件圖片,字體圖標(biāo),樣式文件等都可以放在這兩個(gè)文件下。
不相同點(diǎn):
assets 中存放的靜態(tài)資源文件在項(xiàng)目打包時(shí),會將 assets 中放置的靜態(tài)資源文件進(jìn)行打包上傳,所謂打包簡單點(diǎn)可以理解為壓縮體積,代碼格式化。而壓縮后的靜態(tài)資源文件最終也都會放置在static文件中跟著index.html一同上傳至服務(wù)器。
static 中放置的靜態(tài)資源文件就不會要走打包壓縮格式化等流程,而是直接進(jìn)入打包好的目錄,直接上傳至服務(wù)器。因?yàn)楸苊饬藟嚎s直接進(jìn)行上傳,在打包時(shí)會提高一定的效率,但是static中的資源文件由于沒有進(jìn)行壓縮等操作,所以文件的體積也就相對于assets中打包后的文件提交較大點(diǎn)。在服務(wù)器中就會占據(jù)更大的空間。
建議:將項(xiàng)目中template需要的樣式文件js文件等都可以放置在assets中,走打包這一流程。減少體積。而項(xiàng)目中引入的第三方的資源文件如iconfoont.css等文件可以放置在static中,因?yàn)檫@些引入的第三方文件已經(jīng)經(jīng)過處理,我們不再需要處理,直接上傳。

.hash模式 和 history模式

hash: 在 url 中帶有 #,其原理是 onhashchange 事件。
可以在 window 對象上監(jiān)聽這個(gè)事件:

window.onhashchange = function(event){
     ...
}

history: 沒有原 # , 其原理是 popstate 事件,需要后臺配置支持。
html5中新增兩個(gè)操作歷史棧的API: pushState()replaceState() 方法。

history.pushState(data[,title][,url]); // 向歷史記錄中追加一條記錄
history.replaceState(data[,title][,url]); // 替換當(dāng)前頁在歷史記錄中的信息。

這兩個(gè)方法也可以改變url,頁面也不會重新刷新,在當(dāng)前已有的 back、forward、go 的基礎(chǔ)之上,它們提供了對歷史記錄進(jìn)行修改的功能。只是當(dāng)它們執(zhí)行修改時(shí),雖然改變了當(dāng)前的 URL,但瀏覽器不會立即向后端發(fā)送請求。

.Vuex是什么? 怎么使用它? 哪種功能場景使用?

Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。
Vuex 只能使用在 vue 上,因?yàn)槠涓叨纫蕾囉?vue 的雙向綁定和插件系統(tǒng)。
調(diào)用了 Vue.mixin,在所有組件的 beforeCreate 生命周期注入了設(shè)置 this.$store 這樣一個(gè)對象。
場景有:單頁應(yīng)用中,組件之間的狀態(tài)、音樂播放、登錄狀態(tài)、加入購物車
state: Vuex 使用單一狀態(tài)樹,存放的數(shù)據(jù)狀態(tài),不可以直接修改里面的數(shù)據(jù)。
mutations: 定義的方法動(dòng)態(tài)修改Vuex 的 store 中的狀態(tài)或數(shù)據(jù)。
getters: 類似vue的計(jì)算屬性,主要用來過濾一些數(shù)據(jù)。
actions: 可以理解為通過將mutations里面處里數(shù)據(jù)的方法變成可異步的處理數(shù)據(jù)的方法,簡單的說就是異步操作數(shù)據(jù)。view 層通過 store.dispath 來分發(fā) action。
modules: 項(xiàng)目特別復(fù)雜的時(shí)候,可以讓每一個(gè)模塊擁有自己的state、mutation、action、getters,使得結(jié)構(gòu)非常清晰,方便管理。

actions 和 mutations的區(qū)別

action主要處理的是異步的操作,mutation必須同步執(zhí)行,而action就不受這樣的限制,也就是說action中我們既可以處理同步,也可以處理異步的操作
action改變狀態(tài),最后是通過提交mutation

Vue 中 key 的作用是什么?

需要使用 key 給每一個(gè)節(jié)點(diǎn)做唯一標(biāo)識
diff 算法可以正確識別此節(jié)點(diǎn),可以更高效的更新虛擬DOM。

.用vnode描述一個(gè)DOM結(jié)構(gòu)

虛擬節(jié)點(diǎn)就是用一個(gè)對象描述真實(shí)的dom元素
會將 template 先轉(zhuǎn)換成 ast 樹, ast 通過代碼生成 codegen 轉(zhuǎn)成 rander函數(shù), rander函數(shù)內(nèi)部調(diào)用 $createElement 方法簡稱 _c, 傳入 tag (創(chuàng)建的元素), data(元素的屬性), children(子元素) . 會判斷 children 是不是一個(gè)字符串, 否則會做深度遞歸, 最后返回的結(jié)果就是一個(gè)對象,可描述出DOM 結(jié)構(gòu).

.簡述 Vue 中 diff 算法原理

  1. 先同級比較, 在比較子節(jié)點(diǎn).
  2. 判斷出一方有子節(jié)點(diǎn)另一方?jīng)]有子節(jié)點(diǎn)的情況.
    如果新的一方有子節(jié)點(diǎn),老的沒有,則把子節(jié)點(diǎn)直接插入到老節(jié)點(diǎn)里即可.
    如果老的一方有子節(jié)點(diǎn),新的沒有,則把老的子節(jié)點(diǎn)直接刪除.
  3. 判斷出都有子節(jié)點(diǎn)的情況, 遞歸遍歷子采用雙指針(頭/尾指針)的方式比對節(jié)點(diǎn).

.Vue與Angular以及React的區(qū)別?

1.與AngularJS的區(qū)別
相同點(diǎn):

  1. 都支持指令:內(nèi)置指令和自定義指令。
  2. 都支持過濾器:內(nèi)置過濾器和自定義過濾器。
  3. 都支持雙向數(shù)據(jù)綁定。
  4. 都不支持低端瀏覽器。

不同點(diǎn):

  1. AngularJS 的學(xué)習(xí)成本高,比如增加了 Dependency Injection 特性,而Vue.js本身提供的API都比較簡單、直觀。
  2. 在性能上,AngularJS依賴對數(shù)據(jù)做臟檢查,所以Watcher越多越慢。
    Vue.js使用基于依賴追蹤的觀察并且使用異步隊(duì)列更新。所有的數(shù)據(jù)都是獨(dú)立觸發(fā)的。 對于龐大的應(yīng)用來說,這個(gè)優(yōu)化差異還是比較明顯的。

2.與React的區(qū)別
相同點(diǎn):

  1. React采用特殊的JSX語法,Vue.js在組件開發(fā)中也推崇編寫.vue特殊文件格式,對文件內(nèi)容都有一些約定,兩者都需要編譯后使用。
  2. 中心思想相同:一切都是組件,組件實(shí)例之間可以嵌套。
  3. 都提供合理的鉤子函數(shù),可以讓開發(fā)者定制化地去處理需求。
  4. 都不內(nèi)置AJAX,Route等功能核心包,而是以插件的方式加載。
  5. 在組件開發(fā)中都支持mixins的特性。

不同點(diǎn):

React依賴Virtual DOM,而Vue.js使用的是DOM模板。React采用的Virtual DOM會對渲染出來的結(jié)果做臟檢查。
Vue.js在模板中提供了指令,過濾器等,可以非常方便,快捷地操作DOM

11. 高精度全局權(quán)限處理

權(quán)限控制由前端處理時(shí),通常使用 v-if / v-show 控制元素對不同權(quán)限的響應(yīng)效果。這種情況下,就會導(dǎo)致很多不必要的重復(fù)代碼,不容易維護(hù),因此可以造一個(gè)小車輪,掛在全局上對權(quán)限進(jìn)行處理。

  // 注冊全局自定義指令,對底層原生DOM操作
  Vue.directive('permission', {
        // inserted → 元素插入的時(shí)候
        inserted(el, binding){
            // 獲取到 v-permission 的值
            const { value } = binding
            if(value) {
                // 根據(jù)配置的權(quán)限,去當(dāng)前用戶的角色權(quán)限中校驗(yàn)
                const hasPermission = checkPermission(value)
                if(!hasPermission){
                    // 沒有權(quán)限,則移除DOM元素
                    el.parentNode && el.parentNode.removeChild(el)
                }
            } else{
                throw new Error(`need key! Like v-permission="['admin','editor']"`)
            }
        }
    })
    // --> 在組件中使用 v-permission
    <button v-permission="['admin']">權(quán)限1</button>
    <button v-permission="['admin', 'editor']">權(quán)限2</button>

12 Vue.use 與 Vue.component 的區(qū)別

都用于注冊全局組件/插件的

Vue.component() 每次只能注冊一個(gè)組件,功能很單一。
Vue.component('draggable', draggable)

Vue.use() 內(nèi)部調(diào)用的仍是 Vue.component() 去注冊全局組件/插件,但它可以做更多事情,比如多次調(diào)用 Vue.component() 一次性注冊多個(gè)組件,還可以調(diào)用Vue.directive()、Vue.mixins()、Vue.prototype.xxx=xxx 等等,其第二個(gè)可選參數(shù)又可以傳遞一些數(shù)據(jù)

Vue.use({
    install:function (Vue, options) {
        // 接收傳遞的參數(shù): { name: 'My-Vue', age: 28 }
        console.log(options.name, options.age)
        Vue.directive('my-directive',{
            inserted(el, binding, vnode) { }
        })
        Vue.mixin({
            mounted() { }
        })
        Vue.component('draggable', draggable)
        Vue.component('Tree', Tree)
    }
}, 
{ name: 'My-Vue', age: 28 })

在main.js 文件里 動(dòng)態(tài)注冊全局組件時(shí), 或用到 require.context

require.context(): 一個(gè) Webpack 的API,獲取一個(gè)特定的上下文(創(chuàng)建自己的context),主要用來實(shí)現(xiàn)自動(dòng)化導(dǎo)入模塊。
它會遍歷文件夾中的指定文件,然后自動(dòng)化導(dǎo)入,而不需要每次都顯式使用 import / require 語句導(dǎo)入模塊!
在前端工程中,如果需要一個(gè)文件夾引入很多模塊,則可以使用 require.context()

require.context(directory, useSubdirectories = false, regExp = /^\.\//)

directory {String} 讀取目錄的路徑
useSubdirectories {Boolean} 是否遞歸遍歷子目錄
regExp {RegExp} 匹配文件的正則

.對于 vue3.0 特性你有什么了解的嗎?

(1). 監(jiān)測機(jī)制的改變

3.0 基于代理 Proxy 的 observer 實(shí)現(xiàn),提供全語言覆蓋的反應(yīng)性跟蹤。替代了Vue 2采用 defineProperty去定義get 和 set, 意味著徹底放棄了兼容IE, 這也取消除了 Vue 2 當(dāng)中基于 Object.defineProperty 的實(shí)現(xiàn)所存在的很多限制:
=>只能監(jiān)測屬性,不能監(jiān)測對象:
=>檢測屬性的添加和刪除;
=>檢測數(shù)組索引和長度的變更;
=>支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 還提供了以下特性:
用于創(chuàng)建 observable 的公開 API。這為中小規(guī)模場景提供了簡單輕量級的跨組件狀態(tài)管理解決方案。
默認(rèn)采用惰性觀察。在 2.x 中,不管反應(yīng)式數(shù)據(jù)有多大,都會在啟動(dòng)時(shí)被觀察到。如果數(shù)據(jù)集很大,這可能會在應(yīng)用啟動(dòng)時(shí)帶來明顯的開銷。在 3.x 中,只觀察用于渲染應(yīng)用程序最初可見部分的數(shù)據(jù)。
更精確的變更通知。在 2.x 中,通過 Vue.set 強(qiáng)制添加新屬性將導(dǎo)致依賴于該對象的 watcher 收到變更通知。在 3.x 中,只有依賴于特定屬性的 watcher 才會收到通知。
不可變的 observable:我們可以創(chuàng)建值的“不可變”版本(即使是嵌套屬性),除非系統(tǒng)在內(nèi)部暫時(shí)將其“解禁”。這個(gè)機(jī)制可用于凍結(jié) prop 傳遞或 Vuex 狀態(tài)樹以外的變化。
更好的調(diào)試功能:我們可以使用新的 renderTracked 和 renderTriggered 鉤子精確地跟蹤組件在什么時(shí)候以及為什么重新渲染。

(2). 模板

模板方面沒有大的變更,只改了作用域插槽,2.x 的機(jī)制導(dǎo)致作用域插槽變了,父組件會重新渲染,而 3.0 把作用域插槽改成了函數(shù)的方式,這樣只會影響子組件的重新渲染,提升了渲染的性能。
同時(shí),對于 render 函數(shù)的方面,vue3.0 也進(jìn)行一系列更改來方便習(xí)慣直接使用 api 來生成 vdom 。

(3). 對象式的組件聲明方式

vue2.x 中的組件是通過聲明的方式傳入一系列 option,和 TypeScript 的結(jié)合需要通過一些裝飾器的方式來做,雖然能實(shí)現(xiàn)功能,但是比較麻煩。
vue3.0 修改了組件的聲明方式,改成了類式的寫法,這樣使得和 TypeScript 的結(jié)合變得很容易。

此外,vue 的源碼也改用了 TypeScript 來寫。其實(shí)當(dāng)代碼的功能復(fù)雜之后,必須有一個(gè)靜態(tài)類型系統(tǒng)來做一些輔助管理?,F(xiàn)在 vue3.0 也全面改用 TypeScript 來重寫了,更是使得對外暴露的 api 更容易結(jié)合 TypeScript。靜態(tài)類型系統(tǒng)對于復(fù)雜代碼的維護(hù)確實(shí)很有必要。

(4). 其它方面的更改

支持自定義渲染器,從而使得 weex 可以通過自定義渲染器的方式來擴(kuò)展,而不是直接 fork 源碼來改的方式。
支持 Fragment(多個(gè)根節(jié)點(diǎn))和 Protal(在 dom 其他部分渲染組建內(nèi)容)組件,針對一些特殊的場景做了處理。
基于 treeshaking 優(yōu)化,提供了更多的內(nèi)置功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374