大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以?點此加我微信ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北
籍前端群,可加我微信進群。
前言
大家好,我是村長,一個愛分享的老前端。前一段時間有很多小伙伴找我做經典Vue面試題解析。我整整花了1個月時間找題寫答案,錄視頻。本來打算全部寫完再來發一篇鴻篇巨制,不過找我要學習材料的太多,只好提前發布。這里是已經寫完的前半部分,大家務必點贊、收藏保存一下,我后續還在這一篇更新下半部分,希望大家多多支持村長。
相關學習資源
本系列有配套視頻教程,思維導圖和開源項目,大家學習同時千萬不要忘了三連
+ 關注
+ 分享
視頻教程:【Vue面試專題】金三銀四必備!56道經典Vue面試題詳
思維導圖:Vue面試專題
配套代碼:vue-interview
01-Vue組件之間通信方式有哪些
vue是組件化開發框架,所以對于vue應用來說組件間的數據通信非常重要。此題主要考查大家vue基本功,對于vue基礎api運用熟練度。另外一些邊界知識如provide/inject/$attrs則提現了面試者的知識廣度。
組件傳參的各種方式
思路分析:
總述知道的所有方式
按組件關系闡述使用場景
回答范例:
組件通信常用方式有以下8種:
props
$emit/$on
$children/$parent
$attrs/$listeners
ref
$root
eventbus
vuex
注意vue3中廢棄的幾個API
https://v3-migration.vuejs.org/breaking-changes/children.html
https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html
https://v3-migration.vuejs.org/breaking-changes/events-api.html#overview
根據組件之間關系討論組件通信最為清晰有效
父子組件
props
/$emit
/$parent
/ref
/$attrs
兄弟組件
$parent
/$root
/eventbus
/vuex
跨層級關系
eventbus
/vuex
/provide
+inject
02-v-if和v-for哪個優先級更高?
分析:
此題考查常識,文檔中曾有詳細說明v2|v3;也是一個很好的實踐題目,項目中經常會遇到,能夠看出面試者api熟悉程度和應用能力。
思路分析:
先給出結論
為什么是這樣的,說出細節
哪些場景可能導致我們這樣做,該怎么處理
總結,拔高
回答范例:
實踐中不應該把v-for和v-if放一起
在vue2中,v-for的優先級是高于v-if,把它們放在一起,輸出的渲染函數中可以看出會先執行循環再判斷條件,哪怕我們只渲染列表中一小部分元素,也得在每次重渲染的時候遍歷整個列表,這會比較浪費;另外需要注意的是在vue3中則完全相反,v-if的優先級高于v-for,所以v-if執行時,它調用的變量還不存在,就會導致異常
通常有兩種情況下導致我們這樣做:
為了過濾列表中的項目 (比如
v-for="user in users" v-if="user.isActive"
)。此時定義一個計算屬性 (比如activeUsers
),讓其返回過濾后的列表即可(比如users.filter(u=>u.isActive)
)。為了避免渲染本應該被隱藏的列表 (比如
v-for="user in users" v-if="shouldShowUsers"
)。此時把v-if
移動至容器元素上 (比如ul
、ol
)或者外面包一層template
即可。
文檔中明確指出永遠不要把 v-if
和 v-for
同時用在同一個元素上,顯然這是一個重要的注意事項。
源碼里面關于代碼生成的部分,能夠清晰的看到是先處理v-if還是v-for,順序上vue2和vue3正好相反,因此產生了一些癥狀的不同,但是不管怎樣都是不能把它們寫在一起的。
知其所以然:
做個測試,test.html兩者同級時,渲染函數如下:
??anonymous(
)?{
with(this){return?_c('div',{attrs:{"id":"app"}},_l((items),function(item){return?(item.isActive)?_c('div',{key:item.id},[_v("\n??????"+_s(item.name)+"\n????")]):_e()}),0)}
}
做個測試,test-v3.html

源碼中找答案
v2:https://github1s.com/vuejs/vue/blob/HEAD/src/compiler/codegen/index.js#L65-L66
v3:https://github1s.com/vuejs/core/blob/HEAD/packages/compiler-core/src/codegen.ts#L586-L587
03-簡述 Vue 的生命周期以及每個階段做的事
必問題目,考查vue基礎知識。
思路
給出概念
列舉生命周期各階段
闡述整體流程
結合實踐
擴展:vue3變化
回答范例
1.每個Vue組件實例被創建后都會經過一系列初始化步驟,比如,它需要數據觀測,模板編譯,掛載實例到dom上,以及數據變化時更新dom。這個過程中會運行叫做生命周期鉤子的函數,以便用戶在特定階段有機會添加他們自己的代碼。
2.Vue生命周期總共可以分為8個階段:創建前后, 載入前后, 更新前后, 銷毀前后,以及一些特殊場景的生命周期。vue3中新增了三個用于調試和服務端渲染場景。
生命周期v2 | 生命周期v3 | 描述 |
---|---|---|
beforeCreate | beforeCreate | 組件實例被創建之初 |
created | created | 組件實例已經完全創建 |
beforeMount | beforeMount | 組件掛載之前 |
mounted | mounted | 組件掛載到實例上去之后 |
beforeUpdate | beforeUpdate | 組件數據發生變化,更新之前 |
updated | updated | 數據數據更新之后 |
beforeDestroy | beforeUnmount | 組件實例銷毀之前 |
destroyed | unmounted | 組件實例銷毀之后 |
生命周期v2 | 生命周期v3 | 描述 |
---|---|---|
activated | activated | keep-alive 緩存的組件激活時 |
deactivated | deactivated | keep-alive 緩存的組件停用時調用 |
errorCaptured | errorCaptured | 捕獲一個來自子孫組件的錯誤時被調用 |
- | renderTracked | 調試鉤子,響應式依賴被收集時調用 |
- | renderTriggered | 調試鉤子,響應式依賴被觸發時調用 |
- | serverPrefetch | ssr only,組件實例在服務器上被渲染前調用 |
3.Vue
生命周期流程圖:

4.結合實踐:
beforeCreate:通常用于插件開發中執行一些初始化任務
created:組件初始化完畢,可以訪問各種數據,獲取接口數據等
mounted:dom已創建,可用于獲取訪問數據和dom元素;訪問子組件等。
beforeUpdate:此時view
層還未更新,可用于獲取更新前各種狀態
updated:完成view
層的更新,更新后,所有狀態已是最新
beforeunmount:實例被銷毀前調用,可用于一些定時器或訂閱的取消
unmounted:銷毀一個實例。可清理它與其它實例的連接,解綁它的全部指令及事件監聽器
可能的追問
setup和created誰先執行?
setup中為什么沒有beforeCreate和created?
知其所以然
vue3中生命周期的派發時刻:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555
vue2中聲明周期的派發時刻:
https://github1s.com/vuejs/vue/blob/HEAD/src/core/instance/init.js#L55-L56
04-能說一說雙向綁定使用和原理嗎?
題目分析:
雙向綁定是vue
的特色之一,開發中必然會用到的知識點,然而此題還問了實現原理,升級為深度考查。
思路分析:
給出雙綁定義
雙綁帶來的好處
在哪使用雙綁
使用方式、使用細節、vue3變化
原理實現描述
回答范例:
vue中雙向綁定是一個指令
v-model
,可以綁定一個響應式數據到視圖,同時視圖中變化能改變該值。v-model
是語法糖,默認情況下相當于:value
和@input
。使用v-model
可以減少大量繁瑣的事件處理代碼,提高開發效率。通常在表單項上使用
v-model
,還可以在自定義組件上使用,表示某個值的輸入和輸出控制。通過
<input v-model="xxx">
的方式將xxx的值綁定到表單元素value上;對于checkbox,可以使用true-value
和false-value指定特殊的值,對于radio可以使用value指定特殊的值;對于select可以通過options元素的value設置特殊的值;還可以結合.lazy,.number,.trim對v-mode的行為做進一步限定;v-model
用在自定義組件上時又會有很大不同,vue3中它類似于sync
修飾符,最終展開的結果是modelValue屬性和update:modelValue事件;vue3中我們甚至可以用參數形式指定多個不同的綁定,例如v-model:foo和v-model:bar,非常強大!v-model
是一個指令,它的神奇魔法實際上是vue的編譯器完成的。我做過測試,包含v-model
的模板,轉換為渲染函數之后,實際上還是是value屬性的綁定以及input事件監聽,事件回調函數中會做相應變量更新操作。編譯器根據表單元素的不同會展開不同的DOM屬性和事件對,比如text類型的input和textarea會展開為value和input事件;checkbox和radio類型的input會展開為checked和change事件;select用value作為屬性,用change作為事件。
可能的追問:
v-model
和sync
修飾符有什么區別自定義組件使用
v-model
如果想要改變事件名或者屬性名應該怎么做
知其所以然:
測試代碼,test.html
觀察輸出的渲染函數:
//?<input?type="text"?v-model="foo">
_c('input',?{?directives:?[{?name:?"model",?rawName:?"v-model",?value:?(foo),?expression:?"foo"?}],?attrs:?{?"type":?"text"?},?domProps:?{?"value":?(foo)?},?on:?{?"input":?function?($event)?{?if?($event.target.composing)?return;?foo?=?$event.target.value?}?}?
})
//?<input?type="checkbox"?v-model="bar">
_c('input',?{?directives:?[{?name:?"model",?rawName:?"v-model",?value:?(bar),?expression:?"bar"?}],?attrs:?{?"type":?"checkbox"?},?domProps:?{?"checked":?Array.isArray(bar)???_i(bar,?null)?>?-1?:?(bar)?},?on:?{?"change":?function?($event)?{?var?$$a?=?bar,?$$el?=?$event.target,?$$c?=?$$el.checked???(true)?:?(false);?if?(Array.isArray($$a))?{?var?$$v?=?null,?$$i?=?_i($$a,?$$v);?if?($$el.checked)?{?$$i?<?0?&&?(bar?=?$$a.concat([$$v]))?}?else?{?$$i?>?-1?&&?(bar?=?$$a.slice(0,?$$i).concat($$a.slice($$i?+?1)))?}?}?else?{?bar?=?$$c?}?}?}?
})
//?<select?v-model="baz">
//?????<option?value="vue">vue</option>
//?????<option?value="react">react</option>
//?</select>
_c('select',?{?directives:?[{?name:?"model",?rawName:?"v-model",?value:?(baz),?expression:?"baz"?}],?on:?{?"change":?function?($event)?{?var?$$selectedVal?=?Array.prototype.filter.call($event.target.options,?function?(o)?{?return?o.selected?}).map(function?(o)?{?var?val?=?"_value"?in?o???o._value?:?o.value;?return?val?});?baz?=?$event.target.multiple???$$selectedVal?:?$$selectedVal[0]?}?}?
},?[_c('option',?{?attrs:?{?"value":?"vue"?}?},?[_v("vue")]),?_v("?"),?_c('option',?{?attrs:?{?"value":?"react"?}?},?[_v("react")])
])
05-Vue中如何擴展一個組件
此題屬于實踐題,考察大家對vue常用api使用熟練度,答題時不僅要列出這些解決方案,同時最好說出他們異同。
答題思路:
按照邏輯擴展和內容擴展來列舉,
邏輯擴展有:mixins、extends、composition api;
內容擴展有slots;
分別說出他們使用方法、場景差異和問題。
作為擴展,還可以說說vue3中新引入的composition api帶來的變化
回答范例:
常見的組件擴展方法有:mixins,slots,extends等
混入mixins是分發 Vue 組件中可復用功能的非常靈活的方式。混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被混入該組件本身的選項。
//?復用代碼:它是一個配置對象,選項和組件里面一樣 const?mymixin?=?{methods:?{dosomething(){}} } //?全局混入:將混入對象傳入 Vue.mixin(mymixin)//?局部混入:做數組項設置到mixins選項,僅作用于當前組件 const?Comp?=?{mixins:?[mymixin] }
插槽主要用于vue組件中的內容分發,也可以用于組件擴展。
子組件Child
<div><slot>這個內容會被父組件傳遞的內容替換</slot> </div>
父組件Parent
<div><Child>來自老爹的內容</Child> </div>
如果要精確分發到不同位置可以使用具名插槽,如果要使用子組件中的數據可以使用作用域插槽。
組件選項中還有一個不太常用的選項extends,也可以起到擴展組件的目的
//?擴展對象 const?myextends?=?{methods:?{dosomething(){}} } //?組件擴展:做數組項設置到extends選項,僅作用于當前組件 //?跟混入的不同是它只能擴展單個對象 //?另外如果和混入發生沖突,該選項優先級較高,優先起作用 const?Comp?=?{extends:?myextends }
混入的數據和方法不能明確判斷來源且可能和當前組件內變量產生命名沖突,vue3中引入的composition api,可以很好解決這些問題,利用獨立出來的響應式模塊可以很方便的編寫獨立邏輯并提供響應式的數據,然后在setup選項中組合使用,增強代碼的可讀性和維護性。例如:
//?復用邏輯1 function?useXX()?{} //?復用邏輯2 function?useYY()?{} //?邏輯組合 const?Comp?=?{setup()?{const?{xx}?=?useXX()const?{yy}?=?useYY()return?{xx,?yy}} }
可能的追問
Vue.extend方法你用過嗎?它能用來做組件擴展嗎?
知其所以然
mixins原理:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L232-L233
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L545
slots原理:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentSlots.ts#L129-L130
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1373-L1374
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/renderSlot.ts#L23-L24
06-子組件可以直接改變父組件的數據么,說明原因
分析
這是一個實踐知識點,組件化開發過程中有個單項數據流原則,不在子組件中修改父組件是個常識問題。
參考文檔:https://staging.vuejs.org/guide/components/props.html#one-way-data-flow
思路
講講單項數據流原則,表明為何不能這么做
舉幾個常見場景的例子說說解決方案
結合實踐講講如果需要修改父組件狀態應該如何做
回答范例
所有的 prop 都使得其父子之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。另外,每次父級組件發生變更時,子組件中所有的 prop 都將會刷新為最新的值。這意味著你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器控制臺中發出警告。
const?props?=?defineProps(['foo']) //???下面行為會被警告,?props是只讀的! props.foo?=?'bar'
實際開發過程中有兩個場景會想要修改一個屬性:
這個 prop 用來傳遞一個初始值;這個子組件接下來希望將其作為一個本地的 prop 數據來使用。在這種情況下,最好定義一個本地的 data,并將這個 prop 用作其初始值:
const?props?=?defineProps(['initialCounter']) const?counter?=?ref(props.initialCounter)
這個 prop 以一種原始的值傳入且需要進行轉換。在這種情況下,最好使用這個 prop 的值來定義一個計算屬性:
const?props?=?defineProps(['size']) //?prop變化,計算屬性自動更新 const?normalizedSize?=?computed(()?=>?props.size.trim().toLowerCase())
實踐中如果確實想要改變父組件屬性應該emit一個事件讓父組件去做這個變更。注意雖然我們不能直接修改一個傳入的對象或者數組類型的prop,但是我們還是能夠直接改內嵌的對象或屬性。
07-Vue要做權限管理該怎么做?控制到按鈕級別的權限怎么做?
分析
綜合實踐題目,實際開發中經常需要面臨權限管理的需求,考查實際應用能力。
權限管理一般需求是兩個:頁面權限和按鈕權限,從這兩個方面論述即可。

思路
權限管理需求分析:頁面和按鈕權限
權限管理的實現方案:分后端方案和前端方案闡述
說說各自的優缺點
回答范例
權限管理一般需求是頁面權限和按鈕權限的管理
具體實現的時候分后端和前端兩種方案:
前端方案會把所有路由信息在前端配置,通過路由守衛要求用戶登錄,用戶登錄后根據角色過濾出路由表。比如我會配置一個
asyncRoutes
數組,需要認證的頁面在其路由的meta
中添加一個roles
字段,等獲取用戶角色之后取兩者的交集,若結果不為空則說明可以訪問。此過濾過程結束,剩下的路由就是該用戶能訪問的頁面,最后通過router.addRoutes(accessRoutes)
方式動態添加路由即可。后端方案會把所有頁面路由信息存在數據庫中,用戶登錄的時候根據其角色查詢得到其能訪問的所有頁面路由信息返回給前端,前端再通過
addRoutes
動態添加路由信息按鈕權限的控制通常會實現一個指令,例如
v-permission
,將按鈕要求角色通過值傳給v-permission指令,在指令的moutned
鉤子中可以判斷當前用戶角色和按鈕是否存在交集,有則保留按鈕,無則移除按鈕。純前端方案的優點是實現簡單,不需要額外權限管理頁面,但是維護起來問題比較大,有新的頁面和角色需求就要修改前端代碼重新打包部署;服務端方案就不存在這個問題,通過專門的角色和權限管理頁面,配置頁面和按鈕權限信息到數據庫,應用每次登陸時獲取的都是最新的路由信息,可謂一勞永逸!
知其所以然
路由守衛
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L13-L14
路由生成
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/store/modules/permission.js#L50-L51
動態追加路由
https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L43-L44
可能的追問
類似
Tabs
這類組件能不能使用v-permission
指令實現按鈕權限控制?<el-tabs>?<el-tab-pane?label="?戶管理"?name="first">?戶管理</el-tab-pane>?<el-tab-pane?label="??管理"?name="third">??管理</el-tab-pane> </el-tabs>
服務端返回的路由信息如何添加到路由器中?
//?前端組件名和組件映射表 const?map?=?{//xx:?require('@/views/xx.vue').default?//?同步的?式xx:?()?=>?import('@/views/xx.vue')?//?異步的?式 } //?服務端返回的asyncRoutes const?asyncRoutes?=?[{?path:?'/xx',?component:?'xx',...?} ] //?遍歷asyncRoutes,將component替換為map[component] function?mapComponent(asyncRoutes)?{asyncRoutes.forEach(route?=>?{route.component?=?map[route.component];if(route.children)?{route.children.map(child?=>?mapComponent(child))}}) } mapComponent(asyncRoutes)
08 - 說一說你對vue響應式理解?
分析
這是一道必問題目,但能回答到位的比較少。如果只是看看一些網文,通常沒什么底氣,經不住面試官推敲,但像我們這樣即看過源碼還造過輪子的,回答這個問題就會比較有底氣啦。
答題思路:
啥是響應式?
為什么vue需要響應式?
它能給我們帶來什么好處?
vue的響應式是怎么實現的?有哪些優缺點?
vue3中的響應式的新變化
回答范例:
所謂數據響應式就是能夠使數據變化可以被檢測并對這種變化做出響應的機制。
MVVM框架中要解決的一個核心問題是連接數據層和視圖層,通過數據驅動應用,數據變化,視圖更新,要做到這點的就需要對數據做響應式處理,這樣一旦數據發生變化就可以立即做出更新處理。
以vue為例說明,通過數據響應式加上虛擬DOM和patch算法,開發人員只需要操作數據,關心業務,完全不用接觸繁瑣的DOM操作,從而大大提升開發效率,降低開發難度。
vue2中的數據響應式會根據數據類型來做不同處理,如果是對象則采用Object.defineProperty()的方式定義數據攔截,當數據被訪問或發生變化時,我們感知并作出響應;如果是數組則通過覆蓋數組對象原型的7個變更方法,使這些方法可以額外的做更新通知,從而作出響應。這種機制很好的解決了數據響應化的問題,但在實際使用中也存在一些缺點:比如初始化時的遞歸遍歷會造成性能損失;新增或刪除屬性時需要用戶使用Vue.set/delete這樣特殊的api才能生效;對于es6中新產生的Map、Set這些數據結構不支持等問題。
為了解決這些問題,vue3重新編寫了這一部分的實現:利用ES6的Proxy代理要響應化的數據,它有很多好處,編程體驗是一致的,不需要使用特殊api,初始化性能和內存消耗都得到了大幅改善;另外由于響應化的實現代碼抽取為獨立的reactivity包,使得我們可以更靈活的使用它,第三方的擴展開發起來更加靈活了。
知其所以然
vue2響應式:
https://github1s.com/vuejs/vue/blob/HEAD/src/core/observer/index.js#L135-L136
vue3響應式:
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L89-L90
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/ref.ts#L67-L68
09 - 說說你對虛擬 DOM 的理解?
分析
現有框架幾乎都引入了虛擬 DOM 來對真實 DOM 進行抽象,也就是現在大家所熟知的 VNode 和 VDOM,那么為什么需要引入虛擬 DOM 呢?圍繞這個疑問來解答即可!
思路
vdom是什么
引入vdom的好處
vdom如何生成,又如何成為dom
在后續的diff中的作用
回答范例
虛擬dom顧名思義就是虛擬的dom對象,它本身就是一個
JavaScript
對象,只不過它是通過不同的屬性去描述一個視圖結構。通過引入vdom我們可以獲得如下好處:
將真實元素節點抽象成 VNode,有效減少直接操作 dom 次數,從而提高程序性能
方便實現跨平臺
同一 VNode 節點可以渲染成不同平臺上的對應的內容,比如:渲染在瀏覽器是 dom 元素節點,渲染在 Native( iOS、Android) 變為對應的控件、可以實現 SSR 、渲染到 WebGL 中等等
Vue3 中允許開發者基于 VNode 實現自定義渲染器(renderer),以便于針對不同平臺進行渲染。
直接操作 dom 是有限制的,比如:diff、clone 等操作,一個真實元素上有許多的內容,如果直接對其進行 diff 操作,會去額外 diff 一些沒有必要的內容;同樣的,如果需要進行 clone 那么需要將其全部內容進行復制,這也是沒必要的。但是,如果將這些操作轉移到 JavaScript 對象上,那么就會變得簡單了。
操作 dom 是比較昂貴的操作,頻繁的dom操作容易引起頁面的重繪和回流,但是通過抽象 VNode 進行中間處理,可以有效減少直接操作dom的次數,從而減少頁面重繪和回流。
vdom如何生成?在vue中我們常常會為組件編寫模板 - template, 這個模板會被編譯器 - compiler編譯為渲染函數,在接下來的掛載(mount)過程中會調用render函數,返回的對象就是虛擬dom。但它們還不是真正的dom,所以會在后續的patch過程中進一步轉化為dom。
image-20220209153820845 掛載過程結束后,vue程序進入更新流程。如果某些響應式數據發生變化,將會引起組件重新render,此時就會生成新的vdom,和上一次的渲染結果diff就能得到變化的地方,從而轉換為最小量的dom操作,高效更新視圖。
知其所以然
vnode定義:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L127-L128
觀察渲染函數:21-vdom/test-render-v3.html
創建vnode:
createElementBlock:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L291-L292
createVnode:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L486-L487
首次調用時刻:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L283-L284
mount:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1171-L1172
調試mount過程:mountComponent
21-vdom/test-render-v3.html
10 - 你了解diff算法嗎?
分析
必問題目,涉及vue更新原理,比較考查理解深度。

思路
diff算法是干什么的
它的必要性
它何時執行
具體執行方式
拔高:說一下vue3中的優化
回答范例
1.Vue中的diff算法稱為patching算法,它由Snabbdom修改而來,虛擬DOM要想轉化為真實DOM就需要通過patch方法轉換。
2.最初Vue1.x視圖中每個依賴均有更新函數對應,可以做到精準更新,因此并不需要虛擬DOM和patching算法支持,但是這樣粒度過細導致Vue1.x無法承載較大應用;Vue 2.x中為了降低Watcher粒度,每個組件只有一個Watcher與之對應,此時就需要引入patching算法才能精確找到發生變化的地方并高效更新。
3.vue中diff執行的時刻是組件內響應式數據變更觸發實例執行其更新函數時,更新函數會再次執行render函數獲得最新的虛擬DOM,然后執行patch函數,并傳入新舊兩次虛擬DOM,通過比對兩者找到變化的地方,最后將其轉化為對應的DOM操作。
4.patch過程是一個遞歸過程,遵循深度優先、同層比較的策略;以vue3的patch為例:
首先判斷兩個節點是否為相同同類節點,不同則刪除重新創建
如果雙方都是文本則更新文本內容
如果雙方都是元素節點則遞歸更新子元素,同時更新元素屬性
更新子節點時又分了幾種情況:
新的子節點是文本,老的子節點是數組則清空,并設置文本;
新的子節點是文本,老的子節點是文本則直接更新文本;
新的子節點是數組,老的子節點是文本則清空文本,并創建新子節點數組中的子元素;
新的子節點是數組,老的子節點也是數組,那么比較兩組子節點,更新細節blabla
vue3中引入的更新策略:編譯期優化patchFlags、block等
知其所以然
patch關鍵代碼
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355
調試 test-v3.html
11 - 你知道哪些vue3新特性
分析
官網列舉的最值得注意的新特性:https://v3-migration.vuejs.org/

也就是下面這些:
Composition API
SFC Composition API語法糖
Teleport傳送門
Fragments片段
Emits選項
自定義渲染器
SFC CSS變量
Suspense
以上這些是api相關,另外還有很多框架特性也不能落掉。
回答范例
api層面Vue3新特性主要包括:Composition API、SFC Composition API語法糖、Teleport傳送門、Fragments 片段、Emits選項、自定義渲染器、SFC CSS變量、Suspense
另外,Vue3.0在框架層面也有很多亮眼的改進:
更快
虛擬DOM重寫
編譯器優化:靜態提升、patchFlags、block等
基于Proxy的響應式系統
更小:更好的搖樹優化
更容易維護:TypeScript + 模塊化
更容易擴展
獨立的響應化模塊
自定義渲染器
知其所以然
體驗編譯器優化
https://sfc.vuejs.org/
reactive實現
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L90-L91
12 - 怎么定義動態路由?怎么獲取傳過來的動態參數?
分析
API題目,考查基礎能力,不容有失,盡可能說的詳細。
思路
什么是動態路由
什么時候使用動態路由,怎么定義動態路由
參數如何獲取
細節、注意事項
回答范例
很多時候,我們需要將給定匹配模式的路由映射到同一個組件,這種情況就需要定義動態路由。
例如,我們可能有一個
User
組件,它應該對所有用戶進行渲染,但用戶 ID 不同。在 Vue Router 中,我們可以在路徑中使用一個動態字段來實現,例如:{ path: '/users/:id', component: User }
,其中:id
就是路徑參數路徑參數 用冒號
:
表示。當一個路由被匹配時,它的 params 的值將在每個組件中以this.$route.params
的形式暴露出來。參數還可以有多個,例如
/users/:username/posts/:postId
;除了$route.params
之外,$route
對象還公開了其他有用的信息,如$route.query
、$route.hash
等。
可能的追問
如何響應動態路由參數的變化
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#%E5%93%8D%E5%BA%94%E8%B7%AF%E7%94%B1%E5%8F%82%E6%95%B0%E7%9A%84%E5%8F%98%E5%8C%96
我們如何處理404 Not Found路由
https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#%E6%8D%95%E8%8E%B7%E6%89%80%E6%9C%89%E8%B7%AF%E7%94%B1%E6%88%96-404-not-found-%E8%B7%AF%E7%94%B1
13-如果讓你從零開始寫一個vue路由,說說你的思路
思路分析:
首先思考vue路由要解決的問題:用戶點擊跳轉鏈接內容切換,頁面不刷新。
借助hash或者history api實現url跳轉頁面不刷新
同時監聽hashchange事件或者popstate事件處理跳轉
根據hash值或者state值從routes表中匹配對應component并渲染之
回答范例:
一個SPA應用的路由需要解決的問題是頁面跳轉內容改變同時不刷新,同時路由還需要以插件形式存在,所以:
首先我會定義一個
createRouter
函數,返回路由器實例,實例內部做幾件事:
保存用戶傳入的配置項
監聽hash或者popstate事件
回調里根據path匹配對應路由
將router定義成一個Vue插件,即實現install方法,內部做兩件事:
實現兩個全局組件:router-link和router-view,分別實現頁面跳轉和內容顯示
定義兩個全局變量:和router,組件內可以訪問當前路由和路由器實例
知其所以然:
createRouter如何創建實例
https://github1s.com/vuejs/router/blob/HEAD/src/router.ts#L355-L356
事件監聽
https://github1s.com/vuejs/router/blob/HEAD/src/history/html5.ts#L314-L315 RouterView
頁面跳轉RouterLink
https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L184-L185
內容顯示RouterView
https://github1s.com/vuejs/router/blob/HEAD/src/RouterView.ts#L43-L44
14-能說說key的作用嗎?
分析:
這是一道特別常見的問題,主要考查大家對虛擬DOM和patch細節的掌握程度,能夠反映面試者理解層次。
思路分析:
給出結論,key的作用是用于優化patch性能
key的必要性
實際使用方式
總結:可從源碼層面描述一下vue如何判斷兩個節點是否相同
回答范例:
key的作用主要是為了更高效的更新虛擬DOM。
vue在patch過程中判斷兩個節點是否是相同節點是key是一個必要條件,渲染一組列表時,key往往是唯一標識,所以如果不定義key的話,vue只能認為比較的兩個節點是同一個,哪怕它們實際上不是,這導致了頻繁更新元素,使得整個patch過程比較低效,影響性能。
實際使用中在渲染一組列表時key必須設置,而且必須是唯一標識,應該避免使用數組索引作為key,這可能導致一些隱蔽的bug;vue中在使用相同標簽元素過渡切換時,也會使用key屬性,其目的也是為了讓vue可以區分它們,否則vue只會替換其內部屬性而不會觸發過渡效果。
從源碼中可以知道,vue判斷兩個節點是否相同時主要判斷兩者的key和元素類型等,因此如果不設置key,它的值就是undefined,則可能永遠認為這是兩個相同節點,只能去做更新操作,這造成了大量的dom更新操作,明顯是不可取的。
知其所以然
測試代碼,test-v3.html
上面案例重現的是以下過程

不使用key

如果使用key
//?首次循環patch?A
A?B?C?D?E
A?B?F?C?D?E//?第2次循環patch?B
B?C?D?E
B?F?C?D?E//?第3次循環patch?E
C?D?E
F?C?D?E//?第4次循環patch?D
C?D
F?C?D//?第5次循環patch?C
C?
F?C//?oldCh全部處理結束,newCh中剩下的F,創建F并插入到C前面
源碼中找答案:
判斷是否為相同節點
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L342-L343
更新時的處理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1752-L1753
不使用key

如果使用key
//?首次循環patch?A
A?B?C?D?E
A?B?F?C?D?E//?第2次循環patch?B
B?C?D?E
B?F?C?D?E//?第3次循環patch?E
C?D?E
F?C?D?E//?第4次循環patch?D
C?D
F?C?D//?第5次循環patch?C
C?
F?C//?oldCh全部處理結束,newCh中剩下的F,創建F并插入到C前面
源碼中找答案:
判斷是否為相同節點
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L342-L343
更新時的處理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1752-L1753
15-說說nextTick的使用和原理?
分析
這道題及考察使用,有考察原理,nextTick在開發過程中應用的也較少,原理上和vue異步更新有密切關系,對于面試者考查很有區分度,如果能夠很好回答此題,對面試效果有極大幫助。
答題思路
nextTick是做什么的?
為什么需要它呢?
開發時何時使用它?抓抓頭,想想你在平時開發中使用它的地方
下面介紹一下如何使用nextTick
原理解讀,結合異步更新和nextTick生效方式,會顯得你格外優秀
回答范例:
nextTick是等待下一次 DOM 更新刷新的工具方法。
Vue有個異步更新策略,意思是如果數據變化,Vue不會立刻更新DOM,而是開啟一個隊列,把組件更新函數保存在隊列中,在同一事件循環中發生的所有數據變更會異步的批量更新。這一策略導致我們對數據的修改不會立刻體現在DOM上,此時如果想要獲取更新后的DOM狀態,就需要使用nextTick。
開發時,有兩個場景我們會用到nextTick:
created中想要獲取DOM時;
響應式數據變化后獲取DOM更新后的狀態,比如希望獲取列表更新后的高度。
nextTick簽名如下:
function nextTick(callback?: () => void): Promise<void>
所以我們只需要在傳入的回調函數中訪問最新DOM狀態即可,或者我們可以await nextTick()方法返回的Promise之后做這件事。
在Vue內部,nextTick之所以能夠讓我們看到DOM更新后的結果,是因為我們傳入的callback會被添加到隊列刷新函數(flushSchedulerQueue)的后面,這樣等隊列內部的更新函數都執行完畢,所有DOM操作也就結束了,callback自然能夠獲取到最新的DOM值。
知其所以然:
源碼解讀:
組件更新函數入隊:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1547-L1548
入隊函數:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/scheduler.ts#L84-L85
nextTick定義:
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/scheduler.ts#L58-L59
測試案例,test-v3.html
16-watch和computed的區別以及選擇?
兩個重要API,反應應聘者熟練程度。
思路分析
先看computed, watch兩者定義,列舉使用上的差異
列舉使用場景上的差異,如何選擇
使用細節、注意事項
vue3變化
computed特點:具有響應式的返回值
const?count?=?ref(1)
const?plusOne?=?computed(()?=>?count.value?+?1)
watch特點:偵測變化,執行回調
const?state?=?reactive({?count:?0?})
watch(()?=>?state.count,(count,?prevCount)?=>?{/*?...?*/}
)
回答范例
計算屬性可以從組件數據派生出新數據,最常見的使用方式是設置一個函數,返回計算之后的結果,computed和methods的差異是它具備緩存性,如果依賴項不變時不會重新計算。偵聽器可以偵測某個響應式數據的變化并執行副作用,常見用法是傳遞一個函數,執行副作用,watch沒有返回值,但可以執行異步操作等復雜邏輯。
計算屬性常用場景是簡化行內模板中的復雜表達式,模板中出現太多邏輯會是模板變得臃腫不易維護。偵聽器常用場景是狀態變化之后做一些額外的DOM操作或者異步操作。選擇采用何用方案時首先看是否需要派生出新值,基本能用計算屬性實現的方式首選計算屬性。
使用過程中有一些細節,比如計算屬性也是可以傳遞對象,成為既可讀又可寫的計算屬性。watch可以傳遞對象,設置deep、immediate等選項。
vue3中watch選項發生了一些變化,例如不再能偵測一個點操作符之外的字符串形式的表達式;reactivity API中新出現了watch、watchEffect可以完全替代目前的watch選項,且功能更加強大。
回答范例
計算屬性可以從組件數據派生出新數據,最常見的使用方式是設置一個函數,返回計算之后的結果,computed和methods的差異是它具備緩存性,如果依賴項不變時不會重新計算。偵聽器可以偵測某個響應式數據的變化并執行副作用,常見用法是傳遞一個函數,執行副作用,watch沒有返回值,但可以執行異步操作等復雜邏輯。
計算屬性常用場景是簡化行內模板中的復雜表達式,模板中出現太多邏輯會是模板變得臃腫不易維護。偵聽器常用場景是狀態變化之后做一些額外的DOM操作或者異步操作。選擇采用何用方案時首先看是否需要派生出新值,基本能用計算屬性實現的方式首選計算屬性。
使用過程中有一些細節,比如計算屬性也是可以傳遞對象,成為既可讀又可寫的計算屬性。watch可以傳遞對象,設置deep、immediate等選項。
vue3中watch選項發生了一些變化,例如不再能偵測一個點操作符之外的字符串形式的表達式;reactivity API中新出現了watch、watchEffect可以完全替代目前的watch選項,且功能更加強大。
可能追問
watch會不會立即執行?
watch 和 watchEffect有什么差異
知其所以然
computed的實現
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L79-L80
ComputedRefImpl
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L26-L27
緩存性
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L59-L60
https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L45-L46
watch的實現
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L158-L159
17-說一下 Vue 子組件和父組件創建和掛載順序
這題考查大家對創建過程的理解程度。
思路分析
給結論
闡述理由
回答范例
創建過程自上而下,掛載過程自下而上;即:
parent created
child created
child mounted
parent mounted
之所以會這樣是因為Vue創建過程是一個遞歸過程,先創建父組件,有子組件就會創建子組件,因此創建時先有父組件再有子組件;子組件首次創建時會添加mounted鉤子到隊列,等到patch結束再執行它們,可見子組件的mounted鉤子是先進入到隊列中的,因此等到patch結束執行這些鉤子時也先執行。
知其所以然
觀察beforeCreated和created鉤子的處理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L741-L742
觀察beforeMount和mounted鉤子的處理
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1310-L1311
測試代碼,test-v3.html
18-怎么緩存當前的組件?緩存后怎么更新?
緩存組件使用keep-alive組件,這是一個非常常見且有用的優化手段,vue3中keep-alive有比較大的更新,能說的點比較多。
思路
緩存用keep-alive,它的作用與用法
使用細節,例如緩存指定/排除、結合router和transition
組件緩存后更新可以利用activated或者beforeRouteEnter
原理闡述
回答范例
開發中緩存組件使用keep-alive組件,keep-alive是vue內置組件,keep-alive包裹動態組件component時,會緩存不活動的組件實例,而不是銷毀它們,這樣在組件切換過程中將狀態保留在內存中,防止重復渲染DOM。
<keep-alive><component :is="view"></component> </keep-alive>
結合屬性include和exclude可以明確指定緩存哪些組件或排除緩存指定組件。vue3中結合vue-router時變化較大,之前是
keep-alive
包裹router-view
,現在需要反過來用router-view
包裹keep-alive
:<router-view v-slot="{ Component }"><keep-alive><component :is="Component"></component></keep-alive> </router-view>
緩存后如果要獲取數據,解決方案可以有以下兩種:
beforeRouteEnter:在有vue-router的項目,每次進入路由的時候,都會執行
beforeRouteEnter
beforeRouteEnter(to,?from,?next){next(vm=>{console.log(vm)//?每次進入路由執行vm.getData()??//?獲取數據}) },
actived:在
keep-alive
緩存的組件被激活的時候,都會執行actived
鉤子activated(){this.getData()?//?獲取數據 },
keep-alive是一個通用組件,它內部定義了一個map,緩存創建過的組件實例,它返回的渲染函數內部會查找內嵌的component組件對應組件的vnode,如果該組件在map中存在就直接返回它。由于component的is屬性是個響應式數據,因此只要它變化,keep-alive的render函數就會重新執行。
知其所以然
KeepAlive定義
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L73-L74
緩存定義
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L102-L103
緩存組件
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L215-L216
獲取緩存組件
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L241-L242
測試緩存特性,test-v3.html
19-從0到1自己構架一個vue項目,說說有哪些步驟、哪些重要插件、目錄結構你會怎么組織
綜合實踐類題目,考查實戰能力。沒有什么絕對的正確答案,把平時工作的重點有條理的描述一下即可。
思路
構建項目,創建項目基本結構
引入必要的插件:
代碼規范:prettier,eslint
提交規范:husky,lint-staged
其他常用:svg-loader,vueuse,nprogress
常見目錄結構
回答范例
從0創建一個項目我大致會做以下事情:項目構建、引入必要插件、代碼規范、提交規范、常用庫和組件
目前vue3項目我會用vite或者create-vue創建項目
接下來引入必要插件:路由插件vue-router、狀態管理vuex/pinia、ui庫我比較喜歡element-plus和antd-vue、http工具我會選axios
其他比較常用的庫有vueuse,nprogress,圖標可以使用vite-svg-loader
下面是代碼規范:結合prettier和eslint即可
最后是提交規范,可以使用husky,lint-staged,commitlint
目錄結構我有如下習慣:
.vscode
:用來放項目中的 vscode 配置plugins
:用來放 vite 插件的 plugin 配置public
:用來放一些諸如 頁頭icon 之類的公共文件,會被打包到dist根目錄下src
:用來放項目代碼文件api
:用來放http的一些接口配置assets
:用來放一些 CSS 之類的靜態資源components
:用來放項目通用組件layout
:用來放項目的布局router
:用來放項目的路由配置store
:用來放狀態管理Pinia的配置utils
:用來放項目中的工具方法類views
:用來放項目的頁面文件
20-實際工作中,你總結的vue最佳實踐有哪些?
看到這樣的題目,可以用以下圖片來回答:

思路
查看vue官方文檔:
風格指南:https://vuejs.org/style-guide/
性能:https://vuejs.org/guide/best-practices/performance.html#overview
安全:https://vuejs.org/guide/best-practices/security.html
訪問性:https://vuejs.org/guide/best-practices/accessibility.html
發布:https://vuejs.org/guide/best-practices/production-deployment.html
回答范例
我從編碼風格、性能、安全等方面說幾條:
編碼風格方面:
命名組件時使用“多詞”風格避免和HTML元素沖突
使用“細節化”方式定義屬性而不是只有一個屬性名
屬性名聲明時使用“駝峰命名”,模板或jsx中使用“肉串命名”
使用v-for時務必加上key,且不要跟v-if寫在一起
性能方面:
路由懶加載減少應用尺寸
利用SSR減少首屏加載時間
利用v-once渲染那些不需要更新的內容
一些長列表可以利用虛擬滾動技術避免內存過度占用
對于深層嵌套對象的大數組可以使用shallowRef或shallowReactive降低開銷
避免不必要的組件抽象
安全:
不使用不可信模板,例如使用用戶輸入拼接模板:
template: <div> + userProvidedString + </div>
小心使用v-html,:url,:style等,避免html、url、樣式等注入
等等......
21 - 簡單說一說你對vuex理解?

思路
給定義
必要性闡述
何時使用
拓展:一些個人思考、實踐經驗等
范例
Vuex 是一個專為 Vue.js 應用開發的狀態管理模式 + 庫。它采用集中式存儲,管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
我們期待以一種簡單的“單向數據流”的方式管理應用,即狀態 -> 視圖 -> 操作單向循環的方式。但當我們的應用遇到多個組件共享狀態時,比如:多個視圖依賴于同一狀態或者來自不同視圖的行為需要變更同一狀態。此時單向數據流的簡潔性很容易被破壞。因此,我們有必要把組件的共享狀態抽取出來,以一個全局單例模式管理。通過定義和隔離狀態管理中的各種概念并通過強制規則維持視圖和狀態間的獨立性,我們的代碼將會變得更結構化且易維護。這是vuex存在的必要性,它和react生態中的redux之類是一個概念。
Vuex 解決狀態管理的同時引入了不少概念:例如state、mutation、action等,是否需要引入還需要根據應用的實際情況衡量一下:如果不打算開發大型單頁應用,使用 Vuex 反而是繁瑣冗余的,一個簡單的 store 模式就足夠了。但是,如果要構建一個中大型單頁應用,Vuex 基本是標配。
我在使用vuex過程中感受到一些blabla
可能的追問
vuex有什么缺點嗎?你在開發過程中有遇到什么問題嗎?
action和mutation的區別是什么?為什么要區分它們?
22-說說從 template 到 render 處理過程
分析
問我們template到render過程,其實是問vue編譯器
工作原理。
思路
引入vue編譯器概念
說明編譯器的必要性
闡述編譯器工作流程
回答范例
Vue中有個獨特的編譯器模塊,稱為“compiler”,它的主要作用是將用戶編寫的template編譯為js中可執行的render函數。
之所以需要這個編譯過程是為了便于前端程序員能高效的編寫視圖模板。相比而言,我們還是更愿意用HTML來編寫視圖,直觀且高效。手寫render函數不僅效率底下,而且失去了編譯期的優化能力。
在Vue中編譯器會先對template進行解析,這一步稱為parse,結束之后會得到一個JS對象,我們成為抽象語法樹AST,然后是對AST進行深加工的轉換過程,這一步成為transform,最后將前面得到的AST生成為JS代碼,也就是render函數。
知其所以然
vue3編譯過程窺探:
https://github1s.com/vuejs/core/blob/HEAD/packages/compiler-core/src/compile.ts#L61-L62
測試,test-v3.html
可能的追問
Vue中編譯器何時執行?
react有沒有編譯器?
23-Vue實例掛載的過程中發生了什么?
分析
掛載過程完成了最重要的兩件事:
初始化
建立更新機制
把這兩件事說清楚即可!
回答范例
掛載過程指的是app.mount()過程,這個過程中整體上做了兩件事:初始化和建立更新機制
初始化會創建組件實例、初始化組件狀態,創建各種響應式數據
建立更新機制這一步會立即執行一次組件更新函數,這會首次執行組件渲染函數并執行patch將前面獲得vnode轉換為dom;同時首次執行渲染函數會創建它內部響應式數據之間和組件更新函數之間的依賴關系,這使得以后數據變化時會執行對應的更新函數。
知其所以然
測試代碼,test-v3.html mount函數定義
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L277-L278
首次render過程
https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L2303-L2304
可能的追問
響應式數據怎么創建
依賴關系如何建立
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 ruochuan12、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~