想要拿到20k+的offer到底要達到什麼樣的水平?

java互聯搬磚工人 發佈 2020-12-12T00:17:53+00:00

2020年註定是不平凡的一年,網際網路行業的低迷、疫情的發生,對經濟影響挺大的。作為有6年前端開發的老司機表示壓力山大,「找工作」成了首要任務,最近面試了很多公司,都在問「原理」、「源碼」,不能只知道怎麼用了,而是更關注深層的技術點。深度、廣度是很重要的。

2020年註定是不平凡的一年,網際網路行業的低迷、疫情的發生,對經濟影響挺大的。作為有6年前端開發的老司機表示壓力山大,「找工作」成了首要任務,最近面試了很多公司,都在問「原理」、「源碼」,不能只知道怎麼用了,而是更關注深層的技術點。深度、廣度是很重要的。

本篇文章主要是 「 從源碼角度 - 解讀Vue常考面試題 」。


* 回答面試題的套路

1、先說這個點的明確定義,或者是特性;

2、再說具體的應用場景;

3、說說自己的看法、觀點;

4、可以稍微舉一反三,說說同類特性,或者類似的框架,更好的方案。


簡短的概括:

一、常考-基礎點

1、對 SPA 單⻚⾯的理解,優缺點是什麼?

2、new Vue() 發生了什麼?

3、Vue.use是幹什麼的?原理是什麼?

4、請說一下響應式數據的理解?

5、Vue如何檢測數組變化?

6、Vue.set 方法是如何實現的?

7、Vue中模板編譯原理?

8、Proxy 與 Object.defineProperty 優劣對比

9、Vue3.x響應式數據原理


二、常考-生命周期

1、Vue的生命周期方法有哪些?一般在哪一步發起請求及原因

2、生命周期鉤子是如何實現的?

3、Vue 的父組件和子組件生命周期鉤子執行順序


三、常考-組件通信

1、Vue中的組件的data 為什麼是一個函數?

2、Vue 組件間通信有哪幾種方式?

3、組件中寫 name選項有哪些好處及作用?

4、keep-alive平時在哪裡使用?原理是?

5、Vue.minxin的使用場景和原理?


四、常考-路由

1、Vue-router有幾種鉤子函數?具體是什麼及執行流程是怎樣的?

2、vue-router 兩種模式的區別?


五、常考-屬性作用與對比

1、nextTick在哪裡使用?原理是?

2、Vue 為什麼需要虛擬DOM? 虛擬DOM的優劣如何?

3、Vue中key的作用和工作原理,說說你對它的理解

4、Vue 中的diff原理

5、v-if 與 v-for的優先級

6、v-if與v-show的區別

7、computed 和 watch 的區別和運用的場景?

8、如何理解自定義指令?


六、常考-性能優化

1、編碼階段

2、用戶體驗:

3、SEO優化

4、打包優化


正文從這裡開始~~~


詳細如下:


一、常考-基礎點



對 SPA 單⻚⾯的理解,優缺點是什麼?



核心答案:

SPA( single-page application )僅在 Web ⻚⾯初始化時加載相應的 HTML、JavaScript 和 CSS。⼀旦⻚⾯加載完成,SPA 不會因為⽤戶的操作⽽進⾏⻚⾯的重新加載或跳轉;取⽽代之的是利⽤路由機制實現 HTML 內容的變換,UI 與⽤戶的交互,避免⻚⾯的重新加載。

優點:

1)⽤戶體驗好、快,內容的改變不需要重新加載整個⻚⾯,避免了不必要的跳轉和重複渲染

2)SPA 相對對伺服器壓⼒⼩

3)前後端職責分離,架構清晰,前端進⾏交互邏輯,後端負責數據處理;

缺點:

1)⾸屏(初次)加載慢:為實現單⻚ Web 應⽤功能及顯示效果,需要在加載⻚⾯的時候將JavaScript、CSS 統⼀加載,部分⻚⾯按需加載;

2)不利於 SEO:由於所有的內容都在⼀個⻚⾯中動態替換顯示,所以在 SEO 上其有著天然的弱勢。



new Vue() 發生了什麼?



核心答案:

1)結論:new Vue()是創建Vue實例,它內部執行了根實例的初始化過程。

2)具體包括以下操作:

  • 選項合併
  • $children,$refs,$slots,$createElement等實例屬性的方法初始化
  • 自定義事件處理
  • 數據響應式處理
  • 生命周期鉤子調用 (beforecreate created)
  • 可能的掛載

3)總結:new Vue()創建了根實例並準備好數據和方法,未來執行掛載時,此過程還會遞歸的應用於它的子組件上,最終形成一個有緊密關係的組件實例樹。


源碼地址:src/core/instance/init.js



Vue.use是幹什麼的?原理是什麼?



核心答案:


vue.use 是用來使用插件的,我們可以在插件中擴展全局組件、指令、原型方法等。

1、檢查插件是否註冊,若已註冊,則直接跳出;

2、處理入參,將第一個參數之後的參數歸集,並在首部塞入 this 上下文;

3、執行註冊方法,調用定義好的 install 方法,傳入處理的參數,若沒有 install 方法並且插件本身為 function 則直接進行註冊;


1) 插件不能重複的加載

install 方法的第一個參數是vue的構造函數,其他參數是Vue.set中除了第一個參數的其他參數; 代碼:args.unshift(this)

2) 調用插件的install 方法 代碼:typeof plugin.install === 'function'

3) 插件本身是一個函數,直接讓函數執行。 代碼:plugin.apply(null, args)

4) 緩存插件。 代碼:installedPlugins.push(plugin)

源碼地址:src/core/global-api/use.js



請說一下響應式數據的理解?



核心答案:

根據數據類型來做不同處理,數組和對象類型當值變化時如何劫持。

1) 對象內部通過defineReactive方法,使用 Object.defineProperty() 監聽數據屬性的 get 來進行數據依賴收集,再通過 set 來完成數據更新的派發;


2) 數組則通過重寫數組方法來實現的。擴展它的 7 個變更⽅法,通過監聽這些方法可以做到依賴收集和派發更新;( push/pop/shift/unshift/splice/reverse/sort )


這裡在回答時可以帶出一些相關知識點 (比如多層對象是通過遞歸來實現劫持,順帶提出vue3中是使用 proxy來實現響應式數據)


補充回答:

內部依賴收集是怎麼做到的,每個屬性都擁有自己的dep屬性,存放他所依賴的 watcher,當屬性變化後會通知自己對應的 watcher去更新。


響應式流程:


1、defineReactive 把數據定義成響應式的;

2、給屬性增加一個 dep,用來收集對應的那些watcher;

3、等數據變化進行更新

dep.depend() // get 取值:進行依賴收集

dep.notify() // set 設置時:通知視圖更新


這裡可以引出性能優化相關的內容:1)對象層級過深,性能就會差。2)不需要響應數據的內容不要放在data中。3)object.freeze() 可以凍結數據。

源碼地址:src/core/observer/index.js 158



Vue如何檢測數組變化?



核心答案:

數組考慮性能原因沒有用defineProperty對數組的每一項進行攔截,而是選擇重寫數組 方法以進行重寫。當數組調用到這 7 個方法的時候,執行 ob.dep.notify() 進行派發通知 Watcher 更新

* 重寫數組方法:push/pop/shift/unshift/splice/reverse/sort


補充回答:

在Vue中修改數組的索引和長度是無法監控到的。需要通過以下7種變異方法修改數組才會觸發數組對應的wacther進行更新。數組中如果是對象數據類型也會進行遞歸劫持

說明:那如果想要改索引更新數據怎麼辦?

可以通過Vue.set()來進行處理 =》 核心內部用的是 splice 方法。

// 取出原型方法;
const arrayProto = Array.prototype  
// 拷貝原型方法;
export const arrayMethods = Object.create(arrayProto)  
// 重寫數組方法;
def(arrayMethods, method, function mutator (...args) { }
ob.dep.notify()  // 調用方法時更新視圖;

源碼地址:src/core/observer/array.js 15



Vue.set 方法是如何實現的?



核心答案:

為什麼$set可以觸發更新,我們給對象和數組本身都增加了dep屬性,當給對象新增不存在的屬性則觸發對象依賴的watcher去更新,當修改數組索引時我們調用數組本身的splice方法去更新數組。


補充回答:

官方定義 Vue.set(object, key, value)

1) 如果是數組,調用重寫的splice方法 (這樣可以更新視圖 )

代碼:target.splice(key, 1, val)

2) 如果不是響應式的也不需要將其定義成響應式屬性。

3) 如果是對象,將屬性定義成響應式的 defineReactive(ob.value, key, val)

通知視圖更新 ob.dep.notify()

源碼地址:src/core/observer/index.js 202



Vue中模板編譯原理?



核心答案:

如何將template轉換成render函數(這裡要注意的是我們在開發時儘量不要使用template,因為將template轉化成render方法需要在運行時進行編譯操作會有性能損耗,同時引用帶有complier包的vue體積也會變大) 默認.vue文件中的 template處理是通過vue-loader 來進行處理的並不是通過運行時的編譯。

1) 將 template 模板轉換成 ast 語法樹 - parserHTML

2) 對靜態語法做靜態標記 - markUp

3) 重新生成代碼 - codeGen


補充回答:

模板引擎的實現原理就是new Function + with來進行實現的。

vue-loader中處理template屬性主要靠的是 vue-template-compiler

vue-loader

// template => ast => codegen => with+function 實現生成render方法 
let {ast, render } = VueTemplateCompiler.compile(`<div>{{aaa}}</div>`)
console.log(ast, render)

// 模板引擎的實現原理 with + new Function
console.log(new Function(render).tostring())
// render方法執行完畢後生成的是虛擬 dom
// with(this){return _c('div',[_s(aaa)])}
// 代碼生成

源碼設置:

const ast = parse(template.trim(), options) // 將代碼解析成ast語法樹
  if (options.optimize !== false) {
    optimize(ast, options) // 優化代碼 標記靜態點 標記樹
  }
  const code = generate(ast, options) // 生成代碼 

源碼地址:src/compiler/index.js



Proxy 與 Object.defineProperty 優劣對比



核心答案:


Proxy 的優勢如下:

1)Proxy 可以直接監聽對象而非屬性;

2)Proxy 可以直接監聽數組的變化;

3)Proxy 有多達 13 種攔截方法,不限於 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具備的;

4)Proxy 返回的是一個新對象,我們可以只操作新的對象達到目的,而 Object.defineProperty 只能遍歷對象屬性直接修改;

5)Proxy 作為新標準將受到瀏覽器廠商重點持續的性能優化,也就是傳說中的新標準的性能紅利;

Object.defineProperty 的優勢如下:

兼容性好,支持 IE9,而 Proxy 的存在瀏覽器兼容性問題,而且無法用 polyfill 磨平,因此 Vue 的作者才聲明需要等到下個大版本( 3.0 )才能用 Proxy 重寫。



Vue3.x響應式數據原理



核心答案:

Vue3.x改用Proxy替代Object.defineProperty。因為Proxy可以直接監聽對象和數組的變化,並且有多達13種攔截方法。並且作為新標準將受到瀏覽器廠商重點持續的性能優化。


Proxy只會代理對象的第一層,那麼Vue3又是怎樣處理這個問題的呢?

判斷當前Reflect.get的返回值是否為Object,如果是則再通過reactive方法做代理, 這樣就實現了深度觀測。


監測數組的時候可能觸發多次get/set,那麼如何防止觸發多次呢?

我們可以判斷key是否為當前被代理對象target自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,才有可能執行trigger。

二、常考-生命周期


Vue的生命周期方法有哪些?一般在哪一步發起請求及原因


核心答案:

總共分為8個階段:創建前/後,載入前/後,更新前/後,銷毀前/後。

1、創建前/後:

1) beforeCreate階段:vue實例的掛載元素el和數據對象data都為undefined,還未初始化。

說明:在當前階段data、methods、computed以及watch上的數據和方法都不能被訪問。

2) created階段:vue實例的數據對象data有了,el還沒有。

說明:可以做一些初始數據的獲取,在當前階段無法與Dom進行交互,如果非要想,可以通過vm.$nextTick來訪問Dom。

2、載入前/後:

1) beforeMount階段:vue實例的$el和data都初始化了,但還是掛載之前為虛擬的dom節點。

說明:當前階段虛擬Dom已經創建完成,即將開始渲染。在此時也可以對數據進行更改,不會觸發updated。

2) mounted階段:vue實例掛載完成,data.message成功渲染。

說明:在當前階段,真實的Dom掛載完畢,數據完成雙向綁定,可以訪問到Dom節點,使用$refs屬性對Dom進行操作。

3、更新前/後

1) beforeUpdate階段:響應式數據更新時調用,發生在虛擬DOM打補丁之前,適合在更新之前訪問現有的DOM,比如手動移除已添加的事件監聽器。

說明:可以在當前階段進行更改數據,不會造成重渲染。

2) updated階段:虛擬DOM重新渲染和打補丁之後調用,組成新的DOM已經更新,避免在這個鉤子函數中操作數據,防止死循環。

說明:當前階段組件Dom已完成更新。要注意的是避免在此期間更改數據,因為這可能會導致無限循環的更新。

4、銷毀前/後

1) beforeDestroy階段:實例銷毀前調用,實例還可以用,this能獲取到實例,常用於銷毀定時器,解綁事件。

說明:在當前階段實例完全可以被使用,我們可以在這時進行善後收尾工作,比如清除計時器。

2) destroyed階段:實例銷毀後調用,調用後所有事件監聽器會被移除,所有的子實例都會被銷毀。

說明:當前階段組件已被拆解,數據綁定被卸除,監聽被移出,子實例也統統被銷毀。

補充回答:

第一次頁面加載時會觸發:beforeCreate, created, beforeMount, mounted。

1) created 實例已經創建完成,因為它是最早觸發的原因可以進行一些數據,資源的請求。(伺服器渲染支持created方法)

2) mounted 實例已經掛載完成,可以進行一些DOM操作。(接口請求)

源碼地址:src/core/instance/lifecycle.js



生命周期鉤子是如何實現的?


核心答案:

Vue的生命周期鉤子就是回調函數而已,當創建組件實例的過程中會調用對應的鉤子方法。

補充回答:

內部主要是使用callHook方法來調用對應的方法。核心是一個發布訂閱模式,將鉤子訂閱好(內部採用數組的方式存儲),在對應的階段進行發布。

源碼地址:src/core/util/options.js 146 core/instance/lifecycle.js 336



Vue 的父組件和子組件生命周期鉤子執行順序



核心答案:

第一次頁面加載時會觸發 beforeCreate, created, beforeMount, mounted 這幾個鉤子。

渲染過程:

父組件掛載完成一定是等子組件都掛載完成後,才算是父組件掛載完,所以父組件的mounted在子組件mouted之後

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

子組件更新過程:

影響到父組件:父beforeUpdate -> 子beforeUpdate->子updated -> 父updted

不影響父組件:子beforeUpdate -> 的updated

父組件更新過程:

影響到子組件:父beforeUpdate -> 子beforeUpdate->子updated -> 父updted

不影響子組件:父beforeUpdate -> 父updated

銷毀過程:

父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

重要:父組件等待子組件完成後,才會執行自己對應完成的鉤子。


三、常考-組件通信


Vue中的組件的data 為什麼是一個函數?



核心答案:

每次使用組件時都會對組件進行實例化操作,並且調用data函數返回一個對象作為組件的數據源。這樣可以保證多個組件間數據互不影響。

如果data是對象的話,對象屬於引用類型,會影響到所有的實例。所以為了保證組件不同的實例之間data不衝突,data必須是一個函數。

源碼地址:src/core/util/options 121



Vue 組件間通信有哪幾種方式?



核心答案:


Vue 組件間通信只要指以下 3 類通信:父子組件通信、隔代組件通信、兄弟組件通信,下面我們分別介紹每種通信方式且會說明此種方法可適用於哪類組件間通信。

1、props / $emit 適用 父子組件通信

這種方法是 Vue 組件的基礎,相信大部分同學耳聞能詳,所以此處就不舉例展開介紹。

2、ref 與 $parent / $children 適用 父子組件通信

1)ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例

2)$parent / $children:訪問父 / 子實例

3、EventBus ($emit / $on) 適用於 父子、隔代、兄弟組件通信

這種方法通過一個空的 Vue 實例作為中央事件總線(事件中心),用它來觸發事件和監聽事件,從而實現任何組件間的通信,包括父子、隔代、兄弟組件。

4、$attrs/$listeners 適用於 隔代組件通信

1)$attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 ( class 和 style 除外 )。當一個組件沒有聲明任何 prop 時,這裡會包含所有父作用域的綁定 ( class 和 style 除外 ),並且可以通過 v-bind="$attrs" 傳入內部組件。通常配合 inheritAttrs 選項一起使用。

2)$listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件

5、provide / inject 適用於 隔代組件通信

祖先組件中通過 provider 來提供變量,然後在子孫組件中通過 inject 來注入變量。provide / inject API 主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間建立了一種主動提供與依賴注入的關係。

6、Vuex 適用於 父子、隔代、兄弟組件通信

Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。每一個 Vuex 應用的核心就是 store(倉庫)。「store」 基本上就是一個容器,它包含著你的應用中大部分的狀態 ( state )。



組件中寫 name選項有哪些好處及作用?


核心答案:

1) 可以通過名字找到對應的組件 ( 遞歸組件 )

2) 可以通過name屬性實現緩存功能 (keep-alive)

3) 可以通過name來識別組件 (跨級組件通信時非常重要)

Vue.extend = function () {
    if(name) {
        Sub.options.componentd[name] = Sub
    }
}

源碼地址:src/core/vdom/create-element.js 111


keep-alive平時在哪裡使用?原理是?


核心答案:

keep-alive 主要是組件緩存,採用的是LRU算法。最近最久未使用法。

常用的兩個屬性include/exclude,允許組件有條件的進行緩存。

兩個生命周期activated/deactivated,用來得知當前組件是否處於活躍狀態。

abstract: true, // 抽象組件 
props:{
    include: patternTypes,  // 要緩存的有哪些
    exclude: patternTypes, // 要排除的有哪些
    max: [String, Number] //最大緩存數量 
}
if(cache[key]) { // 通過key 找到緩存,獲取實例
    vnode.componentInstance = cache[key].componentInstance
    remove(keys, key) //將key刪除掉 
    keys.push(key) // 放到末尾 
} else {
    cache[key] = vnode // 沒有緩存過 
    keys.push(key) //存儲key
    if(this.max && keys.length > parseInt(this.max)) { // 如果超過最大緩存數 
    // 刪除最早緩存的 
    pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true // 標記走了緩存 

Vue.minxin的使用場景和原理?


核心答案:

Vue.mixin的作用就是抽離公共的業務邏輯,原理類似「對象的繼承」,當組件初始化時會調用 mergeOptions方法進行合併,採用策略模式針對不同的屬性進行合併,如果混入的數據和本身組件中的數據衝突,會採用「就近原則」以組件的數據為準。

補充回答:

mixin中有很多缺陷「命名衝突問題」,「依賴問題」,「數據來源問題」,這裡強調一下mixin的數據是不會被共享的。

源碼地址:src/core/util/options.js


四、常考-路由



Vue-router有幾種鉤子函數?具體是什麼及執行流程是怎樣的?



核心答案:

路由鉤子的執行流程,鉤子函數種類有:全局守衛、路由守衛、組件守衛。

完整的導航解析流程

1.導航被觸發;

2.在失活的組件里調用beforeRouteLeave守衛;

3.調用全局beforeEach守衛;

4.在復用組件里調用beforeRouteUpdate守衛;

5.調用路由配置里的beforeEnter守衛;

6.解析異步路由組件;

7.在被激活的組件里調用beforeRouteEnter守衛;

8.調用全局beforeResolve守衛;

9.導航被確認;

10.調用全局的afterEach鉤子;

11.DOM更新;

12.用創建好的實例調用beforeRouteEnter守衛中傳給next的回調函數。



vue-router 兩種模式的區別?



核心答案:

vue-router 有 3 種路由模式:hash、history、abstract。

1) hash模式:hash + hashChange

特點:hash雖然在URL中,但不被包括在HTTP請求中;用來指導瀏覽器動作,對服務端安全無用,hash不會重加載頁面。通過監聽 hash(#)的變化來執行js代碼 從而實現 頁面的改變。

核心代碼:

window.addEventListener(『hashchange『,function(){

self.urlChange()

})

2) history模式:historyApi + popState

HTML5推出的history API,由pushState()記錄操作歷史,監聽popstate事件來監聽到狀態變更;

因為 只要刷新 這個url(www.ff.ff/jjkj/fdfd/fdf/fd)就會請求伺服器,然而伺服器上根本沒有這個資源,所以就會報404,解決方案就 配置一下伺服器端。

說明:


1)hash: 使用 URL hash 值來作路由。支持所有瀏覽器,包括不支持 HTML5 History Api 的瀏覽器;

2)history : 依賴 HTML5 History API 和伺服器配置。具體可以查看 HTML5 History 模式;

3)abstract : 支持所有 JavaScript 運行環境,如 Node.js 伺服器端。如果發現沒有瀏覽器的 API,路由會自動強制進入這個模式.

五、常考-屬性作用與對比


nextTick在哪裡使用?原理是?


核心答案:

nextTick的回調是在下次DOM更新循環結束之後執行的延遲回調。在修改數據之後立即使用這個方法,獲取更新後的DOM。nextTick主要使用了宏任務和微任務。原理就是異步方法(promise, mutationObserver, setImmediate, setTimeout)經常與事件循環一起來問。

補充回答:

vue多次更新數據,最終會進行批處理更新。內部調用的就是nextTick實現了延遲更新,用戶自定義的nextTick中的回調會被延遲到更新完成後調用,從而可以獲取更新後的DOM。

源碼地址:src/core/util/next-tick.js 42


Vue 為什麼需要虛擬DOM? 虛擬DOM的優劣如何?


核心答案:

Virtual DOM 就是用js對象來描述真實DOM,是對真實DOM的抽象,由於直接操作DOM性能低但是js層的操作效率高,可以將DOM操作轉化成對象操作,最終通過diff算法比對差異進行更新DOM (減少了對真實DOM的操作)。虛擬DOM不依賴真實平台環境從而也可以實現跨平台。

補充回答:

虛擬DOM的實現就是普通對象包含tag、data、children等屬性對真實節點的描述。(本質上就是在JS和DOM之間的一個緩存)

Vue2的 Virtual DOM 借鑑了開源庫snabbdom的實現。

VirtualDOM映射到真實DOM要經歷VNode的create、diff、patch等階段。

源碼地址:src/core/vdom/vnode: 3


Vue中key的作用和工作原理,說說你對它的理解


核心答案:

例如:v-for="(item, itemIndex) in tabs" :key="itemIndex"

key的作用主要是為了高效的更新虛擬DOM,其原理是vue在patch過程中通過key可以精準判斷兩個節點是否是同一個,從而避免頻繁更新不同元素,使得整個patch過程更加高效,減少DOM操作量,提高性能。

補充回答:

1、若不設置key還可能在列表更新時引發一些隱蔽的bug

2、vue中在使用相同標籤名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓vue可以區分它們,否則vue只會替換其內部屬性而不會觸發過渡效果。

源碼地址:src\core\vdom\patch.js - updateChildren()


Vue 中的diff原理


核心答案:

vue的diff算法是平級比較,不考慮跨級比較的情況。內部採用深度遞歸的方式 + 雙指針的方式進行比較。

補充回答:

1) 先比較是否是相同節點

2) 相同節點比較屬性,並復用老節點

3) 比較兒子節點,考慮老節點和新節點兒子的情況

4) 優化比較:頭頭、尾尾、頭尾、尾頭

5) 比對查找進行復用

Vue2 與 Vue3.x 的diff算法:

Vue2的核心Diff算法採用了雙端比較的算法,同時從新舊children的兩端開始進行比較,藉助key值找到可復用的節點,再進行相關操作。

Vue3.x借鑑了ivi算法和 inferno算法,該算法中還運用了動態規劃的思想求解最長遞歸子序列。(實際的實現可以結合Vue3.x源碼看。)

源碼地址:src/core/vdom/patch.js 501



v-if 與 v-for的優先級



核心答案:

1、v-for優先於v-if被解析

2、如果同時出現,每次渲染都會先執行循環再判斷條件,無論如何循環都不可避免,浪費了性能

3、要避免出現這種情況,則在外層嵌套template,在這一層進行v-if判斷,然後在內部進行v-for循環

4、如果條件出現在循環內部,可通過計算屬性提前過濾掉那些不需要顯示的項

源碼地址:compiler/codegen/index.js


v-if與v-show的區別


核心答案:

v-if 是真正的條件渲染,直到條件第一次變為真實,才會開始渲染。

v-show 不管初始條件是什麼會渲染,並且只是簡單地基於 CSS 的 「display」 屬性進行切換。

注意:v-if 適用於不需要頻繁切換條件的場景;v-show 則適用於需要非常頻繁切換條件的場景。



computed 和 watch 的區別和運用的場景?



核心答案:

computed: 計算屬性。依賴其它屬性值,並且 computed 的只有緩存,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時才會重新計算 computed 的值;

watch: 監聽數據的變化。更多的是「觀察」的作用,類似於某些數據的監聽回調 ,每當監聽的數據變化時都會執行回調進行後續操作;

運用場景:

1)當我們需要進行數值計算,並且依賴於其它數據時,應該使用 computed,因為可以利用 computed 的緩存特性,避免每次獲取值時,都要重新計算

2)當我們需要在數據變化時執行異步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行異步操作 ( 訪問一個 API ),限制我們執行該操作的頻率,並在我們得到最終結果前,設置中間狀態。這些都是計算屬性無法做到的。


如何理解自定義指令?


核心答案:

指令的實現原理,可以從編譯原理 =>代碼生成=> 指令鉤子實現進行概述

1、在生成 ast 語法樹時,遇到指令會給當前元素添加directives屬性

2、通過 genDirectives 生成指令代碼

3、在patch前將指令的鉤子提取到 cbs中,在patch過程中調用對應的鉤子。

4、當執行指令對應鉤子函數時,調用對應指令定義的方法


V-model的原理是什麼?



核心答案:

v-model本質就是一個語法糖,可以看成是value + input方法的語法糖。可以通過model屬性的prop和event屬性來進行自定義。原生的v-model,會根據標籤的不同生成不同的事件和屬性。

v-model 在內部為不同的輸入元素使用不同的屬性並拋出不同的事件:

1)text 和 textarea 元素使用 value 屬性和 input 事件;

2)checkbox 和 radio 使用 checked 屬性和 change 事件;

3)select 欄位將 value 作為 prop 並將 change 作為事件。

六、常考-性能優化


Vue性能優化


1、你都做過哪些Vue的性能優化?( 統計後的結果 )

1)編碼階段

  • 儘量減少data中的數據,data中的數據都會增加getter和setter,會收集對應的watcher;
  • 如果需要使用v-for給每項元素綁定事件時使用事件代理;
  • SPA 頁面採用keep-alive緩存組件;
  • 在更多的情況下,使用v-if替代v-show;
  • key保證唯一;
  • 使用路由懶加載、異步組件;
  • 防抖、節流;
  • 第三方模塊按需導入;
  • 長列表滾動到可視區域動態加載;
  • 圖片懶加載;

2)用戶體驗:

  • 骨架屏;
  • PWA;
  • 還可以使用緩存(客戶端緩存、服務端緩存)優化、服務端開啟gzip壓縮等。

3)SEO優化

  • 預渲染;
  • 服務端渲染SSR;

4)打包優化

  • 壓縮代碼;
  • Tree Shaking/Scope Hoisting;
  • 使用cdn加載第三方模塊;
  • 多線程打包happypack;
  • splitChunks抽離公共文件;
  • sourceMap優化;

說明:優化是個大工程,會涉及很多方面

關鍵字: