vue高頻面試題合集(三)附答案

為什么在 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ù)中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容