臨近2019年的尾聲,是否是該爲了更好的2020年再戰一回呢? ‘勝敗兵家事不期,包羞忍恥是男兒。江東子弟多才俊,捲土重來未可知’,那些在秋招失利的人,難道就心甘情願放棄嗎!css
此文總結2019年以來本人經歷以及瀏覽文章中,較熱門的一些面試題,涵蓋從CSS到JS再到Vue再到網絡等前端基礎到進階的一些知識。html
總結面試題涉及的知識點是對本身的一個提高,也但願能夠幫助到同窗們,在2020年會有一個更好的競爭能力。前端
css篇
- juejin.cn/post/684490…Javavscript篇
- juejin.cn/post/684490…ECMAScript 6篇
- juejin.cn/post/684490…MVC
指的是Model-View-Controller
,即模型-視圖-控制器。
MVC
的目的就是將模型與視圖分離MVC
屬於單向通訊,必須經過Controller
來承上啓下,既必須由控制器來獲取數據,將結果返回給前端,頁面從新渲染MVVM
指的是Model-View-ViewModel
,即模型-視圖-視圖模型,「模型」指的是後端傳遞的數據,「視圖」指的是所看到的頁面,「視圖模型」是MVVM
的核心,它是鏈接View
與Model
的橋樑,實現view
的變化會自動更新到viewModel
中,viewModel
中的變化也會自動顯示在view
上,是一種數據驅動視圖的模型區別:vue
MVC
中的Control
在MVVM
中演變成viewModel
MVVM
經過數據來顯示視圖,而不是經過節點操做MVVM
主要解決了MVC
中大量的DOM
操做,使頁面渲染性能下降,加載速度慢,影響用戶體驗的問題Vue
底層對於響應式數據的核心是object.defineProperty
,Vue
在初始化數據時,會給data
中的屬性使用object.defineProperty
從新定義屬性(劫持屬性的getter
和setter
),當頁面使用對應屬性時,會經過Dep類進行依賴收集(收集當前組件的watcher
),若是屬性發生變化,會通知相關依賴調用其update方法進行更新操做node
Vue
經過數據劫持配合發佈者-訂閱者的設計模式,內部經過調用object.defineProperty()
來劫持各個屬性的getter
和setter
,在數據變化的時候通知訂閱者,並觸發相應的回調關於底層源碼,能夠看看這篇文章:segmentfault.com/a/119000001…面試
Vue數據雙向綁定是指:數據變化更新視圖,視圖變化更新數據,例如輸入框輸入內容變化時,data中的數據同步變化;data中的數據變化時,文本節點的內容同步變化正則表達式
Vue主要經過如下4個步驟實現響應式數據算法
Object.defineProperty()
在屬性上都加上getter
和setter
,這樣後,給對象的某個值賦值,就會觸發setter
,那麼就能監聽到數據變化Vue
模板指令,將模板中的變量都替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,調用更新函數進行數據更新Watcher
訂閱者是Observer
和Compile
之間通訊的橋樑,主要任務是訂閱Observer
中的屬性值變化的消息,當收到屬性值變化的消息時,觸發解析器Compile
中對應的更新函數Watcher
,對監聽器Observer
和訂閱者Watcher
進行統一管理因爲
Object.defineProperty()
只能對屬性進行數據劫持,而不能對整個對象(數組)進行數據劫持,所以Vue框架經過遍歷數組和對象,對每個屬性進行劫持,從而達到利用Object.defineProperty()
也能對對象和數組(部分方法的操做)進行監聽vue-router
因爲JavaScript
的限制,Vue
不能檢測到如下數組的變更vuex
vm.items[indexOfItem] = newValue
vm.items.length = newLength
爲了解決第一個問題,Vue
提供瞭如下操做方式
// Vue.set Vue.set(vm.items, indexOfItem, newValue) // vm.$set,Vue.set的一個別名 vm.$set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue) 複製代碼
爲了解決第二個問題,Vue
提供瞭如下操做方式
// Array.prototype.splice
vm.items.splice(newLength - 1)
複製代碼
push,pop,unshift,shift···
)Vue
將data
中的數組,進行了原型鏈的重寫,指向了本身所定義的數組原型方法,當調用數組的API
時,能夠通知依賴更新,若是數組中包含着引用類型,會對數組中的引用類型再次進行監控vm.$set()
來解決對象新增/刪除屬性不能響應的問題?因爲
JavaScript
的限制,Vue沒法檢測到對象屬性的添加或刪除。這是因爲Vue會在初始化實例時對屬性的getter
和setter
進行劫持,因此屬性必須在data對象上存在才能讓Vue將它們轉換爲響應式數據。
Vue
提供了Vue.set(object, propertyName, value) / vm.$set(object, propertyName, value)
來實現爲對象添加響應式屬性,其原理以下:
splice
方法觸發響應式defineReactive()
方法進行響應式處理(defineReactive()
是Vue對Object.defineProperty()
的二次封裝)由於若是不採用異步渲染,那麼每次更新數據都會進行從新渲染,爲了提升性能,Vue
經過異步渲染的方式,在本輪數據更新後,再去異步更新視圖
Object.defineProperty
只能劫持對象的屬性,所以須要遍歷對象的每一個屬性,而Proxy
能夠直接代理對象Object.defineProperty
對新增屬性須要手動進行觀察,因爲Object.defineProperty
劫持的是對象的屬性(第一點),因此新增屬性時,須要從新遍歷對象,對其新增屬性再使用Object.defineProperty
進行劫持 (正是這個緣由致使咱們在給data
中的數組或對象新增屬性時,須要使用$set
才能保證視圖能夠更新)Proxy
性能高,支持13種攔截方式優勢:
Dom
須要適配任何上層API可能產生的操做,它的一些Dom
操做的實現必須是廣泛適用的,因此它的性能並非最優的,但比起粗暴的Dom
操做要好不少,所以保證了性能的下限Dom
:框架會根據虛擬Dom
和數據的雙向綁定,幫咱們更新視圖,提升開發效率Dom
本質上是JavaScript
對象,而真實Dom
與平臺相關,相比下虛擬Dom
能夠更好地跨平臺操做缺點:
虛擬Dom
的實現原理主要包括如下三部分:
JavaScript
對象模擬真實Dom
樹,對真實Dom
進行抽象diff
算法對兩個虛擬Dom
對象進行比較patch
算法將兩個虛擬Dom
對象的差別應用到真實Dom
樹上EventLoop
事件循環
callback
的時候,先不去調用它,而是把它push到一個全局的queue隊列,等待下一個任務隊列的時候再一次性把這個queue裏的函數依次執行dom
更新循環結束後執行延遲迴調,當咱們修改數據以後當即使用nextTick()
來獲取最新更新的Dom當用戶指定了watch
中的deep:true
時,若是當前監控的值是數組類型(對象類型),會對對象中的每一項進行求值,此時會將當前watcher
存入到對應屬性的依賴中,這樣數組中的對象發生變化也會通知數據進行更新
本質上是由於Vue內部對設置了deep的watch,會進行遞歸的訪問(只要此屬性也是響應式屬性),而再此過程也會不斷髮生依賴收集
缺點:因爲須要對每一項都進行操做,性能會下降,不建議屢次使用deep:true
v-for
優先級高於v-if
,若是連在一塊兒使用的話會把v-if
給每個元素都添加上,重複運行於每個v-for
循環中,會形成性能浪費
在Vue
中,組件都是可複用的,一個組件建立好後,能夠在多個地方重複使用,而無論複用多少次,組件內的data
都必須是相互隔離,互不影響的,若是data
以對象的形式存在,因爲Javascript
中對象是引用類型,做用域沒有隔離,當你在模板中屢次聲明這個組件,組件中的data會指向同一個引用,此時若是對某個組件的data進行修改,會致使其餘組件裏的data也被修改。所以data
必須以函數的形式返回
❗ 小知識: new Vue
根組件不須要複用,所以不須要以函數方式返回
key
是爲每一個vnode
指定惟一的id
,在同級vnode
的Diff
過程當中,能夠根據key
快速的進行對比,來判斷是否爲相同節點,並利用key
的惟一性生成map
來更快的獲取相應的節點,另外指定key
後,能夠保證渲染的準確性。
注:不建議將index做爲key值,具體學習一下【晨曦時夢見兮】的文章:juejin.cn/post/684490…
beforeCreate
→ 在實例初始化以後,數據觀測(data observer
)以前被調用created
→ 實例已經建立完成以後被調用。在這裏,實例已完成如下配置:
data observer
)watch/event
事件回調$el
beforeMount
→ 在掛載開始以前被調用,相關的render
函數首次被調用mounted
→ $el
被新建立的vm.$el
替換,並掛載到實例上以後調用該鉤子beforeUpdate
→ 數據更新時調用,發生在虛擬DOM
從新渲染和打補丁以前updated
→ 因爲數據更改致使的虛擬DOM
從新渲染和打補丁,在這以後會調用該鉤子(該鉤子在服務器端渲染期間不被調用)beforeDestroy
→ 實例銷燬以前調用,在這裏,實例仍然徹底可使用destroyed
→ Vue
實例銷燬後調用。調用該鉤子後,Vue
實例指示的全部東西都會解綁,全部的事件監聽器會被移除,全部的子實例也會被銷燬(該鉤子在服務器端渲染期間不被調用)created
→ 實例已經建立完成,因爲它是最先觸發的,因此能夠進行一些數據,資源的請求mounted
→ 實例已經掛載完成,能夠進行一些DOM
操做beforeUpdate
→ 能夠在該鉤子中進一步地更改狀態,這不會觸發附加的渲染過程updated
→ 能夠執行依賴於DOM
的操做。但在大多數狀況下,應避免在該鉤子中更改狀態,由於這可能致使更新無限循環destroyed
→ 能夠執行一些優化操做,例如清空定時器,清理緩存,解除事件綁定等「beforeCreate
」、「created
」、「beforeMount
」、「mounted
」、「beforeUpdate
」、「updated
」、「beforeDestroy
」、「destroyed
」
❗ 小知識:
<keep-alive>
擁有本身獨立的鉤子函數 activated
| deactivated
activated
→ 在被<keep-alive>
包裹的組件中才有效,當組件被激活時使用該鉤子deactivated
→ 在被<keep-alive>
包裹的組件中才有效,當組件被中止時使用該鉤子父組件掛載完成必須是等到子組件都掛載完成以後,纔算父組件掛載完,因此父組件的
mounted
確定是在子組件mounted
以後
So:「父」beforeCreate → 「父」created → 「父」beforeMount → 「子」beforeCreate → 「子」created → 「子」beforeMount → 「子」mounted → 「父」mounted
子組件更新過程(取決於對父組件是否有影響)
父組件更新過程(取決於對子組件是否有影響)
銷燬過程
好比有一個父組件Parent和子組件Child,若是父組件監聽到子組件掛載
mounted
就作一些邏輯處理、
// Parent.vue <Child @mounted='doSomething' /> // Child.vue mounted(){ this.$emit('mounted') } 複製代碼
// Parent.vue <Child @hook:mounted='doSomething' /> # @hook能夠監聽其餘生命週期 複製代碼
vue
中,父組件能夠經過prop
將數據傳遞給子組件,但這個prop
只能由父組件來修改,子組件修改的話會拋出錯誤$emit
由子組件派發事件,並由父組件接收事件進行修改因爲vue
提倡單向數據流,即父級props
的更新會流向子組件,但反過來則不行。這是爲了防止意外的改變父組件的狀態,使得應用的數據流變得難以理解。若是破壞了單項數據流,當應用複雜時,debug的成本將會很是高
父子組件通訊
props
/ event
$parent
/ $children
ref
provide
/ inject
.sync
非父子組件通訊
eventBus
$root
vuex
$attr
/ $listeners
provide
/ inject
❗ 小知識: 關於.sync
的使用
假設有一個組件 comp <comp :foo.sync="bar"></comp> 傳遞foo值並用sync修飾,會被擴展成 <comp :foo="bar" @update:foo="val => bar = val"></comp> 複製代碼
當子組件comp須要更新foo的值時,它須要顯示地觸發一個更新事件 this.$emit('update:foo', newValue) 複製代碼
多個組件經過同一個掛載點進行組件的切換,
is
的值是哪一個組件的名稱,那麼頁面就會顯示哪一個組件
<div :is='xxx'></div> 複製代碼
組件是能夠在它們本身的模板中調用自身的,不過它們只能經過
name
選項來作這件事
首先咱們要知道,既然是遞歸組件,那麼必定要有一個結束的條件,不然就會致使組件無限循環使用,最終出現
max stack size exceeded
的錯誤,也就是棧溢出。因此,咱們應該使用v-if = 'false'
來做爲遞歸組件的結束條件,當遇到v-if = 'false'
時,組件將不會再進行渲染
v-model
本質是v-bind
和v-on
的語法糖,用來在表單控件或組件上建立雙向綁定(僅僅只是語法糖,區分響應式數據)
原理:在表單元素上綁定vlue而且監聽input事件
<input v-model='searchText'> 等價於 <input v-bind:value='searchText' v-on:input='searchText = $event.target.value'> 複製代碼
在一個組件上使用v-model
,默認會爲組件綁定名爲value
的prop
和名爲input
的事件
vuex
和全局對象主要有兩大區別:
vuex
的狀態存儲是響應式的。當vue
組件從store
中讀取狀態時,若store
中的狀態發生變化,那麼相應的組件也會獲得高效更新store
中的狀態,改變store
中的狀態惟一方法是顯示地提交mutation
(commit
)。這樣使得咱們能夠方便地跟蹤每個狀態的變化vuex
中全部的狀態更新的惟一方式都是提交mutation
,異步操做須要經過action
來提交mutation
(dispatch
)。這樣使得咱們能夠方便地跟蹤每個狀態的變化,從而讓咱們可以實現一些工具幫助咱們更好地使用vuex
每一個mutation
執行完後都會對應獲得一個新的狀態變動,這樣devtools
就能夠打個快照存下來,而後就能夠實現time-travel
了。
若是mutation
支持異步操做,就沒有辦法知道狀態是什麼時候更新,沒法很好的進行狀態追蹤,影響調試效率
v-if
指若是條件不成立則不會渲染當前指令所在節點的Dom
元素,會在切換過程當中對條件塊的事件監聽器和子組件進行銷燬和重建v-show
只是基於css
進行切換,無論條件是什麼,都會進行渲染(切換display:block | none
)So:v-if
切換的開銷較大,而v-show
初始化的開銷較大,因此在須要頻繁切換顯示和隱藏的Dom
元素時,使用v-show
更合適,渲染後不多進行切換則使用v-if
較合適
computed
是依賴於其餘屬性的一個計算值,而且具有緩存,只有當依賴的值發生變化纔會更新(自動監聽依賴值的變化,從而動態返回內容)watch
是在監聽的屬性發生變化的時候,觸發一個回調,在回調中執行一些邏輯(引用晨曦時夢見兮) 實際上computed
會擁有本身的watcher
,它具備一個dirty
屬性來決定computed的值是須要從新計算仍是直接複用以前的值,例如這個例子:
computed: { sum() { return this.count + 1 } } 複製代碼
sum
第一次進行求值的時候會讀取響應式屬性count
,收集到這個響應式數據做爲依賴。而且計算出一個值來保存在自身的value
上,把dirty
設爲false,接下來在模板裏再訪問sum
就直接返回這個求好的值value
,並不進行從新求值。So:computed
和watch
區別在於用法上的不一樣,computed
適合在模板渲染中,若是是須要經過依賴來獲取動態值,就可使用計算屬性。而若是是想在監聽值變化時執行業務邏輯,就使用watch
v-html
能夠用來識別HTML標籤並渲染出去
致使問題: 在網站上動態渲染任意Html
,很容易致使受到Xss
攻擊,因此只能在可信內容上使用v-html
,且永遠不能用於用戶提交的內容上
包裹在<keep-alive>
裏組件,在切換時會保存其組件的狀態,使其不被銷燬,防止屢次渲染
keep-alive
擁有兩個獨立的生命週期(activated
| deactivated
),使keep-alive
包裹的組件在切換時不被銷燬,而是緩存到內存中並執行deactivated
鉤子,切換回組件時會獲取內存,渲染後執行activated
鉤子include
和exclude
屬性,二者都支持字符串或正則表達式
include
表示只有名稱匹配的組件纔會被緩存exclude
表示任何名稱匹配的組件都不會被緩存exclude
優先級高於include
var app = new Vue({ el: '#app', data: { }, // 建立指令(能夠多個) directives: { // 指令名稱 dir1: { inserted(el) { // toDo } } } }) 複製代碼
Vue.directive('dir2', { inserted(el) { // inserted 表示元素插入時 // toDo } }) 複製代碼
<div id="app"> <div :dir1='..'></div> </div> 複製代碼
var app = new Vue({ el: '#app', data: { }, // 建立指令(能夠多個) filters: { // 指令名稱 newfilter:function(value){ // toDo } } }) 複製代碼
Vue.filter('newfilter', function (value) { // toDo }) 複製代碼
<div>{{xxx | newfilter}}</div>
複製代碼
.prevent
:攔截默認事件.passive
:不攔截默認事件.stop
:阻止事件冒泡.self
:當事件發生在該元素而不是子元素的時候會觸發.capture
:事件偵聽,事件發生的時候會調用Class能夠經過對象語法和數組語法進行動態綁定
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div> data: { isActive: true, hasError: false } 複製代碼
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div> data: { activeClass: 'active', errorClass: 'text-danger' } 複製代碼
Style也能夠經過對象語法和數組語法進行動態綁定
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> data: { activeColor: 'red', fontSize: 30 } 複製代碼
<div v-bind:style="[styleColor, styleSize]"></div> data: { styleColor: { color: 'red' }, styleSize:{ fontSize:'23px' } } 複製代碼
SPA(singlg-page application)
僅在Web頁面初始化時加載相應的Html
,JavaScript
,Css
,一旦頁面加載完成,SPA
不會由於用戶的操做而進行頁面的從新加載或跳轉,取而代之的是利用路由機制實現Html
內容的變換,UI
與用戶的交互,避免頁面的從新加載
JavaScript
,Css
統一加載,部分頁面按需加載)在
<style>
標籤上寫入scoped
便可
route
表示路由信息對象,包括path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息參數router
表示路由實例對象,包括了路由的跳轉方法,鉤子函數等beforeEach
,beforeResolve
,afterEach
beforeEnter
beforeRouteEnter
,beforeRouteUpdate
,beforeRouteLeave
導航解析流程:
beforeRouteLeave
離開守衛beforeEach
守衛beforeRouteUpdate
守衛beforeEnter
守衛beforeRouteEnter
守衛beforeResolve
守衛afterEach
守衛Dom
更新beforeRouteEnter
守衛中傳給next
的回調hash
模式會在url
上顯示'#',而history
模式沒有hash
模式能夠正常加載到hash
值對應的頁面,history
模式沒有處理的話,會返回404,通常須要後端將全部頁面都配置重定向到首頁路由hash
模式能夠支持低版本瀏覽器和IEhash
模式
#
後面hash
值的變化,不會致使瀏覽器向服務器發出請求,瀏覽器不發出請求,就不會刷新頁面,同時經過監聽hashchange
事件能夠知道hash
發生了哪些變化。根據hash
變化來實現頁面的局部更新history
模式
history
模式的實現,主要是Html5
標準發佈的兩個Api(pushState
和replaceState
),這兩個Api能夠改變url
,可是不會發送請求,這樣就能夠監聽url
的變化來實現局部更新path
屬性過程當中,使用動態路徑參數,以冒號開頭{ path:'/details/:id', name:'Details', components:Details } # 訪問`details`前綴下的路徑,例如`details/1`,`details/2`等,都會映射到`Details`這個組件 複製代碼
/details
下的路由時,參數值會被設置到this.$route.params
下,因此經過這個屬性能夠獲取動態參數this.$route.params.id 複製代碼
params
name
,不能用path
url
上query
path
,不能用name
name
可使用path
路徑url
上state
中)Ajax
請求次數,有些情景能夠直接從內存中的State
獲取vuex
中的State
就會從新變回初始化狀態State
:vuex
的基本數據,用來存儲變量Getter
:從基本數據state
派生的數據,至關於state
的計算屬性Mutation
:提交更新數據的方法,必須是同步的(須要異步則使用action
)。每一個mutation
都有一個字符串的事件類型(type
)和一個回調函數(handler
)Action
:和mutation
的功能大體相同,不一樣在於
action
提交的是mutation
,而不是直接變動狀態action
能夠包含任意異步操做Module
:模塊化vuex
,可讓每個模塊擁有本身的state
,mutation
,action
,getter
,使得結構清晰,方便管理vuex
就是一個倉庫,倉庫裏面放了不少對象,其中state
就是數據源存放地state
裏面存放的數據是響應式的,Vue
組件從store
中讀取數據,如果store
中的數據改變,依賴這個數據的組件也會更新數據mapState
把全局的state
和getters
映射到當前組件的computed
計算屬性中getters
能夠對state
進行計算操做,能夠把它看作store
的computed
計算屬性getters
能夠在多個組件之間複用getters
vuex
的state
裏action
裏,方便複用;若是不須要複用這個請求,直接寫在Vue
文件裏會更方便
Vue.js
是構建客戶端應用程序的框架,默認狀況下,能夠在瀏覽器中輸出Vue
組件,進行生成Dom
和操做Dom
。然而,也能夠將同一個組件渲染爲服務器的Html
字符串,將它們直接發送給瀏覽器,最後將這些靜態標記激活爲客戶端上徹底能夠交互的應用程序
即:
SSR
大體意思就是vue
在客戶端將標籤渲染成整個html
片斷的工做交給服務端完成,服務端造成的Html
片斷直接返回給前端,這個過程就叫作SSR
(服務端渲染)
服務端渲染SSR的優勢:
SPA
頁面的內容是經過Ajax
獲取,而搜索引擎爬取工具並不會等待Ajax
異步完成後再抓取頁面內容,因此在SPA
中是抓取不到頁面經過Ajax
獲取到的內容;而SSR
是直接由服務器返回已經渲染好的頁面(數據已經包含在頁面中),因此搜索引擎爬取工具能夠抓取渲染好的頁面SPA
會等待全部Vue
編譯後的Js
文件都下載完成後,纔開始進行頁面渲染,文件下載等須要必定時間,因此首屏渲染須要必定時間;而SSR
直接由服務器渲染好頁面直接返回顯示,無需等待下載Js
文件後再去渲染,因此SSR
有更快的內容到達時間服務端渲染SSR的缺點:
beforeCreate
,created
生命週期鉤子,這會致使一些外部擴展庫須要特殊處理,才能在服務端渲染應用程序中運行;而且與能夠部署在任何靜態文件服務器上的徹底靜態單頁面應用程序SPA
不一樣,服務器渲染應用程序須要處於NodeJs
環境下運行NodeJs
中渲染完整的應用程序,顯然會比僅僅提供靜態文件的server
更佔用CPU資源