為什么在 Vue3.0 采用了 Proxy,拋棄了 Object.defineProperty?
Object.defineProperty 本身有一定的監(jiān)控到數(shù)組下標(biāo)變化的能力,但是在 Vue 中,從性能/體驗(yàn)的性?xún)r(jià)比考慮,尤大大就棄用了這個(gè)特性拜隧。為了解決這個(gè)問(wèn)題,經(jīng)過(guò) vue 內(nèi)部處理后可以使用以下幾種方法來(lái)監(jiān)聽(tīng)數(shù)組
push();
pop();
shift();
unshift();
splice();
sort();
reverse();
復(fù)制代碼
由于只針對(duì)了以上 7 種方法進(jìn)行了 hack 處理,所以其他數(shù)組的屬性也是檢測(cè)不到的,還是具有一定的局限性。
Object.defineProperty 只能劫持對(duì)象的屬性,因此我們需要對(duì)每個(gè)對(duì)象的每個(gè)屬性進(jìn)行遍歷壁公。Vue 2.x 里,是通過(guò) 遞歸 + 遍歷 data 對(duì)象來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)控的,如果屬性值也是對(duì)象那么需要深度遍歷,顯然如果能劫持一個(gè)完整的對(duì)象是才是更好的選擇。
Proxy 可以劫持整個(gè)對(duì)象,并返回一個(gè)新的對(duì)象绅项。Proxy 不僅可以代理對(duì)象,還可以代理數(shù)組紊册。還可以代理動(dòng)態(tài)增加的屬性。
created和mounted的區(qū)別
- created:在模板渲染成html前調(diào)用快耿,即通常初始化某些屬性值囊陡,然后再渲染成視圖。
- mounted:在模板渲染成html后調(diào)用掀亥,通常是初始化頁(yè)面完成后关斜,再對(duì)html的dom節(jié)點(diǎn)進(jìn)行一些需要的操作。
Vue.js的template編譯
簡(jiǎn)而言之铺浇,就是先轉(zhuǎn)化成AST樹(shù)痢畜,再得到的render函數(shù)返回VNode(Vue的虛擬DOM節(jié)點(diǎn)),詳細(xì)步驟如下:
首先鳍侣,通過(guò)compile編譯器把template編譯成AST語(yǔ)法樹(shù)(abstract syntax tree 即 源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹(shù)狀表現(xiàn)形式)丁稀,compile是createCompiler的返回值,createCompiler是用以創(chuàng)建編譯器的倚聚。另外compile還負(fù)責(zé)合并option线衫。
然后,AST會(huì)經(jīng)過(guò)generate(將AST語(yǔ)法樹(shù)轉(zhuǎn)化成render funtion字符串的過(guò)程)得到render函數(shù)惑折,render的返回值是VNode授账,VNode是Vue的虛擬DOM節(jié)點(diǎn),里面有(標(biāo)簽名惨驶、子節(jié)點(diǎn)白热、文本等等)
生命周期鉤子是如何實(shí)現(xiàn)的
Vue 的生命周期鉤子核心實(shí)現(xiàn)是利用發(fā)布訂閱模式先把用戶(hù)傳入的的生命周期鉤子訂閱好(內(nèi)部采用數(shù)組的方式存儲(chǔ))然后在創(chuàng)建組件實(shí)例的過(guò)程中會(huì)一次執(zhí)行對(duì)應(yīng)的鉤子方法(發(fā)布)
相關(guān)代碼如下
export function callHook(vm, hook) {
// 依次執(zhí)行生命周期對(duì)應(yīng)的方法
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i].call(vm); //生命周期里面的this指向當(dāng)前實(shí)例
}
}
}
// 調(diào)用的時(shí)候
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options, options);
callHook(vm, "beforeCreate"); //初始化數(shù)據(jù)之前
// 初始化狀態(tài)
initState(vm);
callHook(vm, "created"); //初始化數(shù)據(jù)之后
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
寫(xiě)過(guò)自定義指令嗎 原理是什么
指令本質(zhì)上是裝飾器,是 vue 對(duì) HTML 元素的擴(kuò)展粗卜,給 HTML 元素增加自定義功能屋确。vue 編譯 DOM 時(shí),會(huì)找到指令對(duì)象续扔,執(zhí)行指令的相關(guān)方法攻臀。
自定義指令有五個(gè)生命周期(也叫鉤子函數(shù)),分別是 bind纱昧、inserted刨啸、update、componentUpdated识脆、unbind
1. bind:只調(diào)用一次设联,指令第一次綁定到元素時(shí)調(diào)用加匈。在這里可以進(jìn)行一次性的初始化設(shè)置。
2. inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在仑荐,但不一定已被插入文檔中)雕拼。
3. update:被綁定于元素所在的模板更新時(shí)調(diào)用,而無(wú)論綁定值是否變化粘招。通過(guò)比較更新前后的綁定值啥寇,可以忽略不必要的模板更新。
4. componentUpdated:被綁定元素所在模板完成一次更新周期時(shí)調(diào)用洒扎。
5. unbind:只調(diào)用一次辑甜,指令與元素解綁時(shí)調(diào)用。
Vue中封裝的數(shù)組方法有哪些袍冷,其如何實(shí)現(xiàn)頁(yè)面更新
在Vue中磷醋,對(duì)響應(yīng)式處理利用的是Object.defineProperty對(duì)數(shù)據(jù)進(jìn)行攔截,而這個(gè)方法并不能監(jiān)聽(tīng)到數(shù)組內(nèi)部變化胡诗,數(shù)組長(zhǎng)度變化邓线,數(shù)組的截取變化等,所以需要對(duì)這些操作進(jìn)行hack煌恢,讓Vue能監(jiān)聽(tīng)到其中的變化骇陈。 那Vue是如何實(shí)現(xiàn)讓這些數(shù)組方法實(shí)現(xiàn)元素的實(shí)時(shí)更新的呢,下面是Vue中對(duì)這些方法的封裝:
// 緩存數(shù)組原型
const arrayProto = Array.prototype;
// 實(shí)現(xiàn) arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 需要進(jìn)行功能拓展的方法
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
// 緩存原生數(shù)組方法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
// 執(zhí)行并緩存原生數(shù)組功能
const result = original.apply(this, args);
// 響應(yīng)式處理
const ob = this.__ob__;
let inserted;
switch (method) {
// push瑰抵、unshift會(huì)新增索引你雌,所以要手動(dòng)observer
case "push":
case "unshift":
inserted = args;
break;
// splice方法,如果傳入了第三個(gè)參數(shù)二汛,也會(huì)有索引加入婿崭,也要手動(dòng)observer。
case "splice":
inserted = args.slice(2);
break;
}
//
if (inserted) ob.observeArray(inserted);// 獲取插入的值肴颊,并設(shè)置響應(yīng)式監(jiān)聽(tīng)
// notify change
ob.dep.notify();// 通知依賴(lài)更新
// 返回原生數(shù)組方法的執(zhí)行結(jié)果
return result;
});
});
復(fù)制代碼
簡(jiǎn)單來(lái)說(shuō)就是氓栈,重寫(xiě)了數(shù)組中的那些原生方法,首先獲取到這個(gè)數(shù)組的ob苫昌,也就是它的Observer對(duì)象颤绕,如果有新的值幸海,就調(diào)用observeArray繼續(xù)對(duì)新的值觀察變化(也就是通過(guò)target__proto__ == arrayMethods
來(lái)改變了數(shù)組實(shí)例的型)祟身,然后手動(dòng)調(diào)用notify,通知渲染watcher物独,執(zhí)行update袜硫。
Proxy 與 Object.defineProperty 優(yōu)劣對(duì)比
Proxy 的優(yōu)勢(shì)如下:
- Proxy 可以直接監(jiān)聽(tīng)對(duì)象而非屬性;
- Proxy 可以直接監(jiān)聽(tīng)數(shù)組的變化挡篓;
- Proxy 有多達(dá) 13 種攔截方法,不限于 apply婉陷、ownKeys帚称、deleteProperty、has 等等是 Object.defineProperty 不具備的秽澳;
- Proxy 返回的是一個(gè)新對(duì)象,我們可以只操作新的對(duì)象達(dá)到目的,而 Object.defineProperty 只能遍歷對(duì)象屬性直接修改闯睹;
Proxy 作為新標(biāo)準(zhǔn)將受到瀏覽器廠商重點(diǎn)持續(xù)的性能優(yōu)化,也就是傳說(shuō)中的新標(biāo)準(zhǔn)的性能紅利担神;
Object.defineProperty 的優(yōu)勢(shì)如下:
- 兼容性好楼吃,支持 IE9,而 Proxy 的存在瀏覽器兼容性問(wèn)題,而且無(wú)法用 polyfill 磨平妄讯,因此 Vue 的作者才聲明需要等到下個(gè)大版本( 3.0 )才能用 Proxy 重寫(xiě)孩锡。
Vue 單頁(yè)應(yīng)用與多頁(yè)應(yīng)用的區(qū)別
概念:
- SPA單頁(yè)面應(yīng)用(SinglePage Web Application),指只有一個(gè)主頁(yè)面的應(yīng)用亥贸,一開(kāi)始只需要加載一次js躬窜、css等相關(guān)資源。所有內(nèi)容都包含在主頁(yè)面炕置,對(duì)每一個(gè)功能模塊組件化荣挨。單頁(yè)應(yīng)用跳轉(zhuǎn),就是切換相關(guān)組件朴摊,僅僅刷新局部資源垦沉。
- MPA多頁(yè)面應(yīng)用 (MultiPage Application),指有多個(gè)獨(dú)立頁(yè)面的應(yīng)用仍劈,每個(gè)頁(yè)面必須重復(fù)加載js厕倍、css等相關(guān)資源。多頁(yè)應(yīng)用跳轉(zhuǎn)贩疙,需要整頁(yè)資源刷新讹弯。
Vue模版編譯原理知道嗎,能簡(jiǎn)單說(shuō)一下嗎这溅?
簡(jiǎn)單說(shuō)组民,Vue的編譯過(guò)程就是將template
轉(zhuǎn)化為render
函數(shù)的過(guò)程。會(huì)經(jīng)歷以下階段:
- 生成AST樹(shù)
- 優(yōu)化
- codegen
首先解析模版悲靴,生成AST語(yǔ)法樹(shù)
(一種用JavaScript對(duì)象的形式來(lái)描述整個(gè)模板)臭胜。 使用大量的正則表達(dá)式對(duì)模板進(jìn)行解析,遇到標(biāo)簽癞尚、文本的時(shí)候都會(huì)執(zhí)行對(duì)應(yīng)的鉤子進(jìn)行相關(guān)處理耸三。
Vue的數(shù)據(jù)是響應(yīng)式的,但其實(shí)模板中并不是所有的數(shù)據(jù)都是響應(yīng)式的浇揩。有一些數(shù)據(jù)首次渲染后就不會(huì)再變化仪壮,對(duì)應(yīng)的DOM也不會(huì)變化。那么優(yōu)化過(guò)程就是深度遍歷AST樹(shù)胳徽,按照相關(guān)條件對(duì)樹(shù)節(jié)點(diǎn)進(jìn)行標(biāo)記积锅。這些被標(biāo)記的節(jié)點(diǎn)(靜態(tài)節(jié)點(diǎn))我們就可以跳過(guò)對(duì)它們的比對(duì)
爽彤,對(duì)運(yùn)行時(shí)的模板起到很大的優(yōu)化作用。
編譯的最后一步是將優(yōu)化后的AST樹(shù)轉(zhuǎn)換為可執(zhí)行的代碼
缚陷。
如何在組件中重復(fù)使用Vuex的mutation
使用mapMutations輔助函數(shù),在組件中這么使用
import { mapMutations } from 'vuex'
methods:{
...mapMutations({
setNumber:'SET_NUMBER',
})
}
復(fù)制代碼
然后調(diào)用this.setNumber(10)
相當(dāng)調(diào)用this.$store.commit('SET_NUMBER',10)
Vue template 到 render 的過(guò)程
vue的模版編譯過(guò)程主要如下:template -> ast -> render函數(shù)
vue 在模版編譯版本的碼中會(huì)執(zhí)行 compileToFunctions 將template轉(zhuǎn)化為render函數(shù):
// 將模板編譯為render函數(shù)const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)
復(fù)制代碼
CompileToFunctions中的主要邏輯如下∶ (1)調(diào)用parse方法將template轉(zhuǎn)化為ast(抽象語(yǔ)法樹(shù))
constast = parse(template.trim(), options)
復(fù)制代碼
- parse的目標(biāo):把tamplate轉(zhuǎn)換為AST樹(shù)适篙,它是一種用 JavaScript對(duì)象的形式來(lái)描述整個(gè)模板。
- 解析過(guò)程:利用正則表達(dá)式順序解析模板箫爷,當(dāng)解析到開(kāi)始標(biāo)簽匙瘪、閉合標(biāo)簽、文本的時(shí)候都會(huì)分別執(zhí)行對(duì)應(yīng)的 回調(diào)函數(shù)蝶缀,來(lái)達(dá)到構(gòu)造AST樹(shù)的目的丹喻。
AST元素節(jié)點(diǎn)總共三種類(lèi)型:type為1表示普通元素、2為表達(dá)式翁都、3為純文本
(2)對(duì)靜態(tài)節(jié)點(diǎn)做優(yōu)化
optimize(ast,options)
復(fù)制代碼
這個(gè)過(guò)程主要分析出哪些是靜態(tài)節(jié)點(diǎn)碍论,給其打一個(gè)標(biāo)記,為后續(xù)更新渲染可以直接跳過(guò)靜態(tài)節(jié)點(diǎn)做優(yōu)化
深度遍歷AST柄慰,查看每個(gè)子樹(shù)的節(jié)點(diǎn)元素是否為靜態(tài)節(jié)點(diǎn)或者靜態(tài)節(jié)點(diǎn)根鳍悠。如果為靜態(tài)節(jié)點(diǎn),他們生成的DOM永遠(yuǎn)不會(huì)改變坐搔,這對(duì)運(yùn)行時(shí)模板更新起到了極大的優(yōu)化作用藏研。
(3)生成代碼
const code = generate(ast, options)
復(fù)制代碼
generate將ast抽象語(yǔ)法樹(shù)編譯成 render字符串并將靜態(tài)部分放到 staticRenderFns 中,最后通過(guò) new Function(`` render``)
生成render函數(shù)概行。
Vue data 中某一個(gè)屬性的值發(fā)生改變后蠢挡,視圖會(huì)立即同步執(zhí)行重新渲染嗎?
不會(huì)立即同步執(zhí)行重新渲染凳忙。Vue 實(shí)現(xiàn)響應(yīng)式并不是數(shù)據(jù)發(fā)生變化之后 DOM 立即變化业踏,而是按一定的策略進(jìn)行 DOM 的更新。Vue 在更新 DOM 時(shí)是異步執(zhí)行的涧卵。只要偵聽(tīng)到數(shù)據(jù)變化勤家, Vue 將開(kāi)啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更柳恐。
如果同一個(gè)watcher被多次觸發(fā)伐脖,只會(huì)被推入到隊(duì)列中一次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和 DOM 操作是非常重要的乐设。然后讼庇,在下一個(gè)的事件循環(huán)tick中,Vue 刷新隊(duì)列并執(zhí)行實(shí)際(已去重的)工作伤提。
你有對(duì) Vue 項(xiàng)目進(jìn)行哪些優(yōu)化巫俺?
(1)代碼層面的優(yōu)化
- v-if 和 v-show 區(qū)分使用場(chǎng)景
- computed 和 watch 區(qū)分使用場(chǎng)景
- v-for 遍歷必須為 item 添加 key,且避免同時(shí)使用 v-if
- 長(zhǎng)列表性能優(yōu)化
- 事件的銷(xiāo)毀
- 圖片資源懶加載
- 路由懶加載
- 第三方插件的按需引入
- 優(yōu)化無(wú)限列表性能
- 服務(wù)端渲染 SSR or 預(yù)渲染
(2)Webpack 層面的優(yōu)化
- Webpack 對(duì)圖片進(jìn)行壓縮
- 減少 ES6 轉(zhuǎn)為 ES5 的冗余代碼
- 提取公共代碼
- 模板預(yù)編譯
- 提取組件的 CSS
- 優(yōu)化 SourceMap
- 構(gòu)建結(jié)果輸出分析
- Vue 項(xiàng)目的編譯優(yōu)化
(3)基礎(chǔ)的 Web 技術(shù)的優(yōu)化
- 開(kāi)啟 gzip 壓縮
- 瀏覽器緩存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶頸
為什么不建議用index作為key?
使用index 作為 key和沒(méi)寫(xiě)基本上沒(méi)區(qū)別肿男,因?yàn)椴还軘?shù)組的順序怎么顛倒介汹,index 都是 0, 1, 2...這樣排列,導(dǎo)致 Vue 會(huì)復(fù)用錯(cuò)誤的舊子節(jié)點(diǎn)舶沛,做很多額外的工作嘹承。
Vue-router 路由有哪些模式?
一般有兩種模式:
(1)hash 模式:后面的 hash 值的變化如庭,瀏覽器既不會(huì)向服務(wù)器發(fā)出請(qǐng)求叹卷,瀏覽器也不會(huì)刷新,每次 hash 值的變化會(huì)觸發(fā) hashchange 事件坪它。
(2)history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法骤竹。這兩個(gè)方法應(yīng)用于瀏覽器的歷史記錄棧,在當(dāng)前已有的 back往毡、forward蒙揣、go 的基礎(chǔ)之上,它們提供了對(duì)歷史記錄進(jìn)行修改的功能开瞭。只是當(dāng)它們執(zhí)行修改時(shí)懒震,雖然改變了當(dāng)前的 URL,但瀏覽器不會(huì)立即向后端發(fā)送請(qǐng)求嗤详。
談?wù)刅ue和React組件化的思想
- 1.我們?cè)诟鱾€(gè)頁(yè)面開(kāi)發(fā)的時(shí)候个扰,會(huì)產(chǎn)生很多重復(fù)的功能,比如element中的xxxx葱色。像這種純粹非頁(yè)面的UI递宅,便成為我們常用的UI組件,最初的前端組件也就僅僅指的是UI組件
- 2.隨著業(yè)務(wù)邏輯變得越來(lái)多是苍狰,我們就想要我們的組件可以處理很多事恐锣,這就是我們常說(shuō)的組件化,這個(gè)組件就不是UI組件了舞痰,而是包具體業(yè)務(wù)的業(yè)務(wù)組件
- 3.這種開(kāi)發(fā)思想就是分而治之土榴。最大程度的降低開(kāi)發(fā)難度和維護(hù)成本的效果。并且可以多人協(xié)作响牛,每個(gè)人寫(xiě)不同的組件玷禽,最后像撘積木一樣的把它構(gòu)成一個(gè)頁(yè)面
$nextTick 原理及作用
Vue 的 nextTick 其本質(zhì)是對(duì) JavaScript 執(zhí)行原理 EventLoop 的一種應(yīng)用。
nextTick 的核心是利用了如 Promise 呀打、MutationObserver矢赁、setImmediate、setTimeout的原生 JavaScript 方法來(lái)模擬對(duì)應(yīng)的微/宏任務(wù)的實(shí)現(xiàn)贬丛,本質(zhì)是為了利用 JavaScript 的這些異步回調(diào)任務(wù)隊(duì)列來(lái)實(shí)現(xiàn) Vue 框架中自己的異步回調(diào)隊(duì)列撩银。
nextTick 不僅是 Vue 內(nèi)部的異步隊(duì)列的調(diào)用方法,同時(shí)也允許開(kāi)發(fā)者在實(shí)際項(xiàng)目中使用這個(gè)方法來(lái)滿(mǎn)足實(shí)際應(yīng)用中對(duì) DOM 更新數(shù)據(jù)時(shí)機(jī)的后續(xù)邏輯處理
nextTick 是典型的將底層 JavaScript 執(zhí)行原理應(yīng)用到具體案例中的示例豺憔,引入異步更新隊(duì)列機(jī)制的原因∶
- 如果是同步更新额获,則多次對(duì)一個(gè)或多個(gè)屬性賦值够庙,會(huì)頻繁觸發(fā) UI/DOM 的渲染,可以減少一些無(wú)用渲染
- 同時(shí)由于 VirtualDOM 的引入抄邀,每一次狀態(tài)發(fā)生變化后耘眨,狀態(tài)變化的信號(hào)會(huì)發(fā)送給組件,組件內(nèi)部使用 VirtualDOM 進(jìn)行計(jì)算得出需要更新的具體的 DOM 節(jié)點(diǎn)境肾,然后對(duì) DOM 進(jìn)行更新操作剔难,每次更新?tīng)顟B(tài)后的渲染過(guò)程需要更多的計(jì)算,而這種無(wú)用功也將浪費(fèi)更多的性能奥喻,所以異步渲染變得更加至關(guān)重要
Vue采用了數(shù)據(jù)驅(qū)動(dòng)視圖的思想偶宫,但是在一些情況下,仍然需要操作DOM环鲤。有時(shí)候砂蔽,可能遇到這樣的情況私股,DOM1的數(shù)據(jù)發(fā)生了變化,而DOM2需要從DOM1中獲取數(shù)據(jù),那這時(shí)就會(huì)發(fā)現(xiàn)DOM2的視圖并沒(méi)有更新鹉胖,這時(shí)就需要用到了nextTick
了辫狼。
由于Vue的DOM操作是異步的露该,所以译暂,在上面的情況中,就要將DOM2獲取數(shù)據(jù)的操作寫(xiě)在$nextTick
中蔫耽。
this.$nextTick(() => { // 獲取數(shù)據(jù)的操作...})
復(fù)制代碼
所以结耀,在以下情況下,會(huì)用到nextTick:
- 在數(shù)據(jù)變化后執(zhí)行的某個(gè)操作匙铡,而這個(gè)操作需要使用隨數(shù)據(jù)變化而變化的DOM結(jié)構(gòu)的時(shí)候图甜,這個(gè)操作就需要方法在
nextTick()
的回調(diào)函數(shù)中。 - 在vue生命周期中鳖眼,如果在created()鉤子進(jìn)行DOM操作黑毅,也一定要放在
nextTick()
的回調(diào)函數(shù)中。
因?yàn)樵赾reated()鉤子函數(shù)中钦讳,頁(yè)面的DOM還未渲染矿瘦,這時(shí)候也沒(méi)辦法操作DOM,所以愿卒,此時(shí)如果想要操作DOM缚去,必須將操作的代碼放在nextTick()
的回調(diào)函數(shù)中。