為什么在 Vue3.0 采用了 Proxy,拋棄了 Object.defineProperty?
Object.defineProperty 本身有一定的監(jiān)控到數(shù)組下標(biāo)變化的能力,但是在 Vue 中,從性能/體驗(yàn)的性價(jià)比考慮,尤大大就棄用了這個(gè)特性。為了解決這個(gè)問題,經(jīng)過 vue 內(nèi)部處理后可以使用以下幾種方法來監(jiān)聽數(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 里,是通過 遞歸 + 遍歷 data 對(duì)象來實(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)用,通常是初始化頁面完成后,再對(duì)html的dom節(jié)點(diǎn)進(jìn)行一些需要的操作。
Vue.js的template編譯
簡(jiǎn)而言之,就是先轉(zhuǎn)化成AST樹,再得到的render函數(shù)返回VNode(Vue的虛擬DOM節(jié)點(diǎn)),詳細(xì)步驟如下:
首先,通過compile編譯器把template編譯成AST語法樹(abstract syntax tree 即 源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式),compile是createCompiler的返回值,createCompiler是用以創(chuàng)建編譯器的。另外compile還負(fù)責(zé)合并option。
然后,AST會(huì)經(jīng)過generate(將AST語法樹轉(zhuǎn)化成render funtion字符串的過程)得到render函數(shù),render的返回值是VNode,VNode是Vue的虛擬DOM節(jié)點(diǎn),里面有(標(biāo)簽名、子節(jié)點(diǎn)、文本等等)
生命周期鉤子是如何實(shí)現(xiàn)的
Vue 的生命周期鉤子核心實(shí)現(xiàn)是利用發(fā)布訂閱模式先把用戶傳入的的生命周期鉤子訂閱好(內(nèi)部采用數(shù)組的方式存儲(chǔ))然后在創(chuàng)建組件實(shí)例的過程中會(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);
}
};
寫過自定義指令嗎 原理是什么
指令本質(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)用,而無論綁定值是否變化。通過比較更新前后的綁定值,可以忽略不必要的模板更新。
4. componentUpdated:被綁定元素所在模板完成一次更新周期時(shí)調(diào)用。
5. unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。
Vue中封裝的數(shù)組方法有哪些,其如何實(shí)現(xiàn)頁面更新
在Vue中,對(duì)響應(yīng)式處理利用的是Object.defineProperty對(duì)數(shù)據(jù)進(jìn)行攔截,而這個(gè)方法并不能監(jiān)聽到數(shù)組內(nèi)部變化,數(shù)組長(zhǎng)度變化,數(shù)組的截取變化等,所以需要對(duì)這些操作進(jìn)行hack,讓Vue能監(jiān)聽到其中的變化。 那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)聽
// notify change
ob.dep.notify();// 通知依賴更新
// 返回原生數(shù)組方法的執(zhí)行結(jié)果
return result;
});
});
復(fù)制代碼
簡(jiǎn)單來說就是,重寫了數(shù)組中的那些原生方法,首先獲取到這個(gè)數(shù)組的ob,也就是它的Observer對(duì)象,如果有新的值,就調(diào)用observeArray繼續(xù)對(duì)新的值觀察變化(也就是通過target__proto__ == arrayMethods來改變了數(shù)組實(shí)例的型),然后手動(dòng)調(diào)用notify,通知渲染watcher,執(zhí)行update。
Proxy 與 Object.defineProperty 優(yōu)劣對(duì)比
Proxy 的優(yōu)勢(shì)如下:
- Proxy 可以直接監(jiān)聽對(duì)象而非屬性;
- Proxy 可以直接監(jiān)聽數(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)化,也就是傳說中的新標(biāo)準(zhǔn)的性能紅利;
Object.defineProperty 的優(yōu)勢(shì)如下:
- 兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,而且無法用 polyfill 磨平,因此 Vue 的作者才聲明需要等到下個(gè)大版本( 3.0 )才能用 Proxy 重寫。
Vue 單頁應(yīng)用與多頁應(yīng)用的區(qū)別
概念:
- SPA單頁面應(yīng)用(SinglePage Web Application),指只有一個(gè)主頁面的應(yīng)用,一開始只需要加載一次js、css等相關(guān)資源。所有內(nèi)容都包含在主頁面,對(duì)每一個(gè)功能模塊組件化。單頁應(yīng)用跳轉(zhuǎn),就是切換相關(guān)組件,僅僅刷新局部資源。
- MPA多頁面應(yīng)用 (MultiPage Application),指有多個(gè)獨(dú)立頁面的應(yīng)用,每個(gè)頁面必須重復(fù)加載js、css等相關(guān)資源。多頁應(yīng)用跳轉(zhuǎn),需要整頁資源刷新。
Vue模版編譯原理知道嗎,能簡(jiǎn)單說一下嗎?
簡(jiǎn)單說,Vue的編譯過程就是將template轉(zhuǎn)化為render函數(shù)的過程。會(huì)經(jīng)歷以下階段:
- 生成AST樹
- 優(yōu)化
- codegen
首先解析模版,生成AST語法樹(一種用JavaScript對(duì)象的形式來描述整個(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)化過程就是深度遍歷AST樹,按照相關(guān)條件對(duì)樹節(jié)點(diǎn)進(jìn)行標(biāo)記。這些被標(biāo)記的節(jié)點(diǎn)(靜態(tài)節(jié)點(diǎn))我們就可以跳過對(duì)它們的比對(duì),對(duì)運(yùn)行時(shí)的模板起到很大的優(yōu)化作用。
編譯的最后一步是將優(yōu)化后的AST樹轉(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 的過程
vue的模版編譯過程主要如下: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(抽象語法樹)
constast = parse(template.trim(), options)
復(fù)制代碼
- parse的目標(biāo):把tamplate轉(zhuǎn)換為AST樹,它是一種用 JavaScript對(duì)象的形式來描述整個(gè)模板。
- 解析過程:利用正則表達(dá)式順序解析模板,當(dāng)解析到開始標(biāo)簽、閉合標(biāo)簽、文本的時(shí)候都會(huì)分別執(zhí)行對(duì)應(yīng)的 回調(diào)函數(shù),來達(dá)到構(gòu)造AST樹的目的。
AST元素節(jié)點(diǎn)總共三種類型:type為1表示普通元素、2為表達(dá)式、3為純文本
(2)對(duì)靜態(tài)節(jié)點(diǎn)做優(yōu)化
optimize(ast,options)
復(fù)制代碼
這個(gè)過程主要分析出哪些是靜態(tài)節(jié)點(diǎn),給其打一個(gè)標(biāo)記,為后續(xù)更新渲染可以直接跳過靜態(tài)節(jié)點(diǎn)做優(yōu)化
深度遍歷AST,查看每個(gè)子樹的節(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抽象語法樹編譯成 render字符串并將靜態(tài)部分放到 staticRenderFns 中,最后通過 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í)行的。只要偵聽到數(shù)據(jù)變化, Vue 將開啟一個(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)化
- 事件的銷毀
- 圖片資源懶加載
- 路由懶加載
- 第三方插件的按需引入
- 優(yōu)化無限列表性能
- 服務(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)化
- 開啟 gzip 壓縮
- 瀏覽器緩存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶頸
為什么不建議用index作為key?
使用index 作為 key和沒寫基本上沒區(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è)頁面開發(fā)的時(shí)候,會(huì)產(chǎn)生很多重復(fù)的功能,比如element中的xxxx。像這種純粹非頁面的UI,便成為我們常用的UI組件,最初的前端組件也就僅僅指的是UI組件
- 2.隨著業(yè)務(wù)邏輯變得越來多是,我們就想要我們的組件可以處理很多事,這就是我們常說的組件化,這個(gè)組件就不是UI組件了,而是包具體業(yè)務(wù)的業(yè)務(wù)組件
- 3.這種開發(fā)思想就是分而治之。最大程度的降低開發(fā)難度和維護(hù)成本的效果。并且可以多人協(xié)作,每個(gè)人寫不同的組件,最后像撘積木一樣的把它構(gòu)成一個(gè)頁面
$nextTick 原理及作用
Vue 的 nextTick 其本質(zhì)是對(duì) JavaScript 執(zhí)行原理 EventLoop 的一種應(yīng)用。
nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法來模擬對(duì)應(yīng)的微/宏任務(wù)的實(shí)現(xiàn),本質(zhì)是為了利用 JavaScript 的這些異步回調(diào)任務(wù)隊(duì)列來實(shí)現(xiàn) Vue 框架中自己的異步回調(diào)隊(duì)列。
nextTick 不僅是 Vue 內(nèi)部的異步隊(duì)列的調(diào)用方法,同時(shí)也允許開發(fā)者在實(shí)際項(xiàng)目中使用這個(gè)方法來滿足實(shí)際應(yīng)用中對(duì) DOM 更新數(shù)據(jù)時(shí)機(jī)的后續(xù)邏輯處理
nextTick 是典型的將底層 JavaScript 執(zhí)行原理應(yīng)用到具體案例中的示例,引入異步更新隊(duì)列機(jī)制的原因∶
- 如果是同步更新,則多次對(duì)一個(gè)或多個(gè)屬性賦值,會(huì)頻繁觸發(fā) UI/DOM 的渲染,可以減少一些無用渲染
- 同時(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ài)后的渲染過程需要更多的計(jì)算,而這種無用功也將浪費(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的視圖并沒有更新,這時(shí)就需要用到了nextTick了。
由于Vue的DOM操作是異步的,所以,在上面的情況中,就要將DOM2獲取數(shù)據(jù)的操作寫在$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ù)中,頁面的DOM還未渲染,這時(shí)候也沒辦法操作DOM,所以,此時(shí)如果想要操作DOM,必須將操作的代碼放在nextTick()的回調(diào)函數(shù)中。