本着對社區的小夥伴們負責的態度,有些文章裏應付面試用的一些講解實在是看不下去。前端
本文針對 @小明同窗喲 的 《前端常見的Vue面試題目彙總》 這篇文章,提出一些錯誤。vue
先放一張大圖,有興趣的同窗能夠點開圖片看一下原文,簡單來講就是寫了不少不知道從哪裏收集來的劣質總結,而後底下放個公衆號騙粉絲。react
且不說原文中每一個答案都過於簡略,並不能達到面試官的要求,其中還有不少錯誤的地方會誤導讀者,接下來我重點指出一下錯誤的地方。es6
這裏不放原文連接的緣由是我但願抵制這樣的做者,這個做者的掘力值快要 5000 了,而掘金會對掘力值 5000 以上的做者進行文章首頁推薦。若是之後首頁都是這樣的低質量文章,那真的很讓人絕望。面試
另外比較好笑的是,昨天在這篇文章下提出了一些反駁的觀點,今早一看這篇文章的評論區,已經被做者刪的一乾二淨,只留下她的「水軍號」的一條評論了。不由唏噓,直接刪掉文章的反對觀點來掩耳盜鈴。算法
接下來開始針對做者文章中的觀點進行逐條的反駁,注意「引用」 中的文字的便是做者原文,錯別字我也原樣保留了。vuex
默認Vue在初始化數據時,會給data中的屬性使用Object.defineProperty從新定義全部屬性,當頁面到對應屬性時,會進行依賴收集(收集當前組件中的watcher)若是屬性發生變化會通知相關依賴進行更新操做segmentfault
收集當前組件中的watcher,我進一步問你什麼叫當前組件的 watcher
?我面試時常常聽到這種模糊的說法,感受就是看了些造玩具的文章就說熟悉響應式原理了,起碼的流程要清晰一些:api
render
函數是由 Watcher
去代理執行的,Watcher
在執行前會把 Watcher
自身先賦值給 Dep.target
這個全局變量,等待響應式屬性去收集它render
函數時訪問了響應式屬性,響應式屬性就會精確的收集到當前全局存在的 Dep.target
做爲自身的依賴Watcher
去從新調用 vm._update(vm._render())
進行組件的視圖更新響應式部分,若是你想在簡歷上寫熟悉的話,仍是要抽時間好好的去看一下源碼中真正的實現,而不是看這種模棱兩可的說法就以爲本身熟練掌握了。數組
由於若是不採用異步更新,那麼每次更新數據都會對當前租金按進行從新渲染,因此爲了性能考慮,Vue會在本輪數據更新後,再去異步更新數據
什麼叫本輪數據更新後,再去異步更新數據?
輪指的是什麼,在 eventLoop
裏的 task
和 microTask
,他們分別的執行時機是什麼樣的,爲何優先選用 microTask
,這都是值得深思的好問題。
建議看看這篇文章: Vue源碼詳解之nextTick:MutationObserver只是浮雲,microtask纔是核心!
nextTick方法主要是使用了宏任務和微任務,定義一個異步方法,屢次調用nextTick會將方法存在隊列中,經過這個異步方法清空當前隊列。因此這個nextTick方法就是異步方法
這句話說的很亂,典型的讓面試官忍不住想要深挖一探究竟的回答。(由於一聽你就不是真的懂)
正確的流程應該是先去 嗅探環境
,依次去檢測
Promise的then
-> MutationObserver的回調函數
-> setImmediate
-> setTimeout
是否存在,找到存在的就使用它,以此來肯定回調函數隊列是以哪一個 api 來異步執行。
在 nextTick
函數接受到一個 callback
函數的時候,先不去調用它,而是把它 push 到一個全局的 queue
隊列中,等待下一個任務隊列的時候再一次性的把這個 queue
裏的函數依次執行。
這個隊列多是 microTask
隊列,也多是 macroTask
隊列,前兩個 api 屬於微任務隊列,後兩個 api 屬於宏任務隊列。
簡化實現一個異步合併任務隊列:
let pending = false
// 存放須要異步調用的任務
const callbacks = []
function flushCallbacks () {
pending = false
// 循環執行隊列
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]()
}
// 清空
callbacks.length = 0
}
function nextTick(cb) {
callbacks.push(cb)
if (!pending) {
pending = true
// 利用Promise的then方法 在下一個微任務隊列中把函數所有執行
// 在微任務開始以前 依然能夠往callbacks裏放入新的回調函數
Promise.resolve().then(flushCallbacks)
}
}
複製代碼
測試一下:
// 第一次調用 then方法已經被調用了 可是 flushCallbacks 還沒執行
nextTick(() => console.log(1))
// callbacks裏push這個函數
nextTick(() => console.log(2))
// callbacks裏push這個函數
nextTick(() => console.log(3))
// 同步函數優先執行
console.log(4)
// 此時調用棧清空了,瀏覽器開始檢查微任務隊列,發現了 flushCallbacks 方法,執行。
// 此時 callbacks 裏的 3 個函數被依次執行。
// 4
// 1
// 2
// 3
複製代碼
虛擬DOM把最終的DOM操做計算出來並優化,因爲這個DOM操做屬於預處理操做,並無真實的操做DOM,因此叫作虛擬DOM。最後在計算完畢才真正將DOM操做提交,將DOM操做變化反映到DOM樹上
看起來講的很厲害,其實也沒說到點上。關於虛擬 DOM 的優缺點,直接看 Vue 做者尤雨溪本人的知乎回答,你會對它有進一步的理解:
網上都說操做真實 DOM 慢,但測試結果卻比 React 更快,爲何?
雙向數據綁定經過MVVM思想實現數據的雙向綁定,讓開發者不用再操做dom對象,有更多的時間去思考業務邏輯
開發者不操做dom對象,和雙向綁定沒太大關係。React不提供雙向綁定,開發者照樣不須要操做dom。雙向綁定只是一種語法糖,在表單元素上綁定 value
而且監聽 onChange
事件去修改 value
觸發響應式更新。
我建議真正想看模板被編譯後的原理的同窗,能夠去尤大開源的vue-template-explorer 網站輸入對應的模板,就會展現出對應的 render 函數。
運行速度更快,像比較與react而言,一樣都是操做虛擬dom,就性能而言,vue存在很大的優點
爲何快,快在哪裏,什麼狀況下快,有數據支持嗎?事實上在初始化數據量不一樣的場景是很差比較的,React
不須要對數據遞歸的進行 響應式定義
。
而在更新的場景下 Vue
可能更快一些,由於 Vue
的更新粒度是組件級別的,而 React
是遞歸向下的進行 reconciler
,React
引入了 Fiber
架構和異步更新,目的也是爲了讓這個工做能夠分在不一樣的 時間片
中進行,不要去阻塞用戶高優先級的操做。
Proxy是es6提供的新特性,兼容性很差,因此致使Vue3一致沒有正式發佈讓開發者使用
Vue3 沒發佈不是由於兼容性很差,工做正在有序推動中,新的語法也在不斷迭代,而且發佈 rfc
徵求社區意見。
Object.defineProperty的缺點:沒法監控到數組下標的變化,致使直接經過數組的下標給數組設置值,不能實時響應
事實上能夠,而且尤大說只是爲了性能的權衡纔不去監聽。數組下標本質上也就是對象的一個屬性。
React默認是經過比較引用的方式(diff)進行的,React不精確監聽數據變化。
比較引用和 diff
有什麼關係,難道 Vue 就不 diff
了嗎。
Vue2.0能夠經過props實現雙向綁定,用vuex單向數據流的狀態管理框架
雙向綁定是 v-model
吧。
Vue 父組件經過props向子組件傳遞數據或回調
Vue 雖然能夠傳遞迴調,可是通常來講仍是經過 v-on:change
或者 @change
的方式去綁定事件吧,這和回調是兩套機制。
模板渲染方式不一樣,Vue經過HTML進行渲染
事實上 Vue 是本身實現了一套模板引擎系統,HTML
能夠被利用爲模板的而已,你在 .vue
文件裏寫的 template
和 HTML
本質上沒有關係。
React組合不一樣功能方式是經過HoC(高階組件),本質是高階函數
事實上高階函數只是社區提出的一種方案被 React 所採納而已,其餘的方案還有 renderProps
和 最近流行的Hook
Vue 也能夠利用高階函數 實現組合和複用。
兩個數的徹底的diff算法是一個時間複雜度爲o(n3), Vue進行了優化O(n3)複雜度的問題轉換成O(n)複雜度的問題(只比較同級不考慮跨級問題)在前端當中,你不多會跨級層級地移動Dom元素,因此Virtual Dom只會對同一個層級地元素進行對比
聽這個描述來講,React 沒有對 O(n3)
的複雜度進行優化?事實上 React 和 Vue 都只會對 tag
相同的同級節點進行 diff
,若是不一樣則直接銷燬重建,都是 O(n)
的複雜度。
單個插槽當子組件模板只有一個沒有屬性的插槽時, 父組件傳入的整個內容片斷將插入到插槽所在的 DOM 位置, 並替換掉插槽標籤自己。
跟 DOM 不要緊,是在虛擬節點樹的插槽位置替換。
若是不加key,那麼vue會選擇複用節點(Vue的就地更新策略),致使以前節點的狀態被保留下來,會產生一系列的bug
不加 key 也不必定就會複用,關於 diff 和 key 的使用,建議你們仍是找一些非造玩具的文章真正深刻的看一下原理。
爲何 Vue 中不要用 index 做爲 key?(diff 算法詳解)
由於組件是用來複用的,JS裏對象是引用關係,這樣做用域沒有隔離,而new Vue的實例,是不會被複用的,所以不存在引用對象問題
這句話反正我壓根沒聽懂,事實上若是組件裏 data 直接寫了一個對象的話,那麼若是你在模板中屢次聲明這個組件,組件中的 data 會指向同一個引用。
此時若是在某個組件中對 data 進行修改,會致使其餘組件裏的 data 也被污染。 而若是使用函數的話,每一個組件裏的 data 會有單獨的引用,這個問題就能夠避免了。
這個問題我一樣舉個例子來方便理解,假設咱們有這樣的一個組件,其中的 data 直接使用了對象而不是函數:
var Counter = {
template: `<span @click="count++"></span>`
data: {
count: 0
}
}
複製代碼
注意,這裏的 Counter.data
僅僅是一個對象而已,它 是一個引用,也就是它是在當前的運行環境下全局惟一
的,它真正的值在堆內存中佔用了一部分空間。
也就是說,無論利用這份 data
數據建立了多少個組件實例,這個組件實例內部的 data
都指向這一個惟一的對象。
而後咱們在模板中調用兩次 Counter
組件:
<div>
<Counter id="a" />
<Counter id="b" />
</div>
複製代碼
咱們從原理出發,先看看它被編譯成什麼樣的 render
函數:
function render() {
with(this) {
return _c('div', [_c('Counter'), _c('Counter')], 1)
}
}
複製代碼
每個 Counter
會被 _c
所調用,也就是 createElement
,想象一下 createElement
內部會發生什麼,它會直接拿着 Counter
上的 data
這個引用去建立一個組件。 也就是全部的 Counter
組件實例上的 data
都指向同一個引用。
此時假如 id 爲 a 的 Counter 組件內部調用了 count++
,會去對 data
這個引用上的 count 屬性賦值,那麼此時因爲 id 爲 b 的 Counter 組件內部也是引用的同一份 data,它也會感受到變化而更新組件,這就形成了多個組件之間的數據混亂了。
那麼若是換成函數的狀況呢?每建立一次組件實例就執行一次 data()
函數:
function data() {
return { count: 0 }
}
// 組件a建立一份data
const a = data()
// 組件b建立一份data
const b = data()
a === b // false
複製代碼
是否是一目瞭然,每一個組件擁有了本身的一份全新的 data
,不再會互相污染數據了。
計算屬性是基於他們的響應式依賴進行緩存的,只有在依賴發生變化時,纔會計算求值,而使用 methods,每次都會執行相應的方法
這也是一個一問就倒的回答,依賴變化是計算屬性就從新求值嗎?中間經歷了什麼過程,爲何說 computed
是有緩存值的?隨便挑一個點深刻問下去就站不住。 事實上 computed
會擁有本身的 watcher
,它內部有個屬性 dirty
開關來決定 computed
的值是須要從新計算仍是直接複用以前的值。
以這樣的一個例子來講:
computed: {
sum() {
return this.count + 1
}
}
複製代碼
首先明確兩個關鍵字:
「dirty」 從字面意義來說就是 髒
的意思,這個開關開啓了,就意味着這個數據是髒數據,須要從新求值了拿到最新值。
「求值」 的意思的對用戶傳入的函數進行執行,也就是執行 return this.count + 1
sum
第一次進行求值的時候會讀取響應式屬性 count
,收集到這個響應式數據做爲依賴。而且計算出一個值來保存在自身的 value
上,把 dirty
設爲 false,接下來在模板裏再訪問 sum
就直接返回這個求好的值 value
,並不進行從新的求值。count
發生變化了之後會通知 sum
所對應的 watcher
把自身的 dirty
屬性設置成 true,這也就至關於把從新求值的開關打開來了。這個很好理解,只有 count
變化了, sum
才須要從新去求值。this.sum
的時候,纔會真正的去從新調用 sum
函數求值,而且再次把 dirty
設置爲 false,等待下次的開啓……後續我會考慮單獨出一篇文章進行詳細講解。
當用戶指定了watch中的deep屬性爲true時,若是當前監控的值是數組類型,會對對象中的每一項進行求值,此時會將當前watcher存入到對應屬性的依賴中,這樣數組中的對象發生變化時也會通知數據更新。
不光是數組類型,對象類型也會對深層屬性進行 依賴收集
,好比deep watch
了 obj
,那麼對 obj.a.b.c = 5
這樣深層次的修改也同樣會觸發 watch 的回調函數。本質上是由於 Vue 內部對須要 deep watch
的屬性會進行遞歸的訪問
,而在此過程當中也會不斷髮生依賴收集。(只要此屬性也是響應式屬性
)
在回答這道題的時候,一樣也要考慮到 遞歸收集依賴
對性能上的損耗和權衡,這樣纔是一份合格的回答。
mutation是同步更新數據(內部會進行是否爲異步方式更新數據的檢測)
內部並不能檢測到是否異步更新,而是實例上有一個開關變量 _committing
,
mutation
的調用棧,此時的開關已是關上的,天然能檢測到對 state 的修改並報錯。具體能夠查看源碼中的 withCommit
函數。這是一種很經典對於 js單線程機制
的利用。Store.prototype._withCommit = function _withCommit (fn) {
var committing = this._committing;
this._committing = true;
fn();
this._committing = committing;
};
複製代碼
此外 @小明同窗喲 這個做者和 @小夢喲 這兩個做者之間有說不清道不明的關係(以前看好像是情侶頭像,而後常常互動,而且兩我的分別著有《一個湖北女生的總結》、《一個湖北男生的總結》)。
兩個做者之間把同一篇低質量文章來回發,都是那種評論區能指出特別多錯誤的水文。
來波 diff 算法
這是 @小明同窗喲 的 《前端面試大廠手寫源碼系列(上)》:
這是 @小夢喲 的 《面試時,你被要求手寫常見原理了嗎?》
基本上就是順序調換一下,內容徹底重複的文章,閱讀量還不低。
最開始接觸到這個做者,是由於她寫了一篇 《Vue仿餓了麼app項目總結》,我正好在這個項目的做者黃軼老師的羣裏,羣友很是憤慨的來評論區討公道後她纔在評論區裏聲明這是和慕課網的黃軼老師學習課程後進行的總結。
我能夠理解爲若是沒人說的話,她就想瞞混過去做爲本身的項目了,惋惜她不瞭解行情,這門課早就在幾年前就家喻戶曉,成爲 Vue 面試必備的實戰項目了。
我一直以爲知識變現不可恥,這是一個「自媒體」流行的時代,認真輸出本身觀點而且影響他人的人理應得到本身的收益,我並不以爲這有什麼丟人的,
我在 寫給初中級前端的高級進階指南 這篇文章裏放了幾個掘金小冊的推廣碼,是我認真讀過之後真心想推薦給你們的,這也是掘金官方提供的一種變現機制。我真心不以爲這有什麼不對。知識是有價值的,前提是你不要輸出二手錯誤百出的知識。甚至在你們的公衆號上看到廣告的時候,我也是會心一笑,由於這個做者曾經或「原創」或「轉載」的優質文章給我帶來了很大的收益……
而原做者 @小明同窗喲 的水平明顯還不足以給社區的新人一些啓發,甚至我感受大概至關於某c字開頭的論壇上面充斥着的新手學習筆記,這樣子爲了變現而影響社區環境的吃相我就接受不了了。
再不濟,你還能夠學習某不肯說起姓名的「閏土大叔」,寫些心靈雞湯作一個教父,也同樣能夠賺的盆滿鉢滿,畢竟人家沒誤導人。只是人家是真的不會技術,那就曲線救國而已。
總而言之,我關注了這個做者和她的搭檔 @小夢喲 挺久了,不知道這些做者爲何這麼拼命的想火起來,不惜重複發文章,不惜借用別人的課程成果而不聲明,這對社區的進步來講沒有任何好處。
關於面經,面經實際上是一個挺不錯的文章形式,它可讓你在不去參與面試的狀況下也能夠得知目前國內的大廠主要在技術上關注哪些重點。可是若是你用面經下面的簡略的答案去做爲你的學習材料,那我以爲就本末倒置了。正確的方式是去針對每個重難點,結合你本身目前的技術水平和方向去深刻學習和研究。
好比面試官問你 Vue 的原理,實際上是想考察你對日常使用的框架是否有探索底層原理的興趣和熱情,相信有這份熱情的人他的技術積累和潛力必定也不會差。可是不少人如今爲了應付面試,就直接按照本文所說的《前端常見的Vue面試題目彙總》這種文章一個個簡略版答案
去背(況且大廠面試官必定會針對每個點深刻挖掘,挖到你說不出來爲止),這樣真的是很不推薦的一種行爲。
若是你真的想掌握好 Vue 的原理,而且做爲你簡歷中的一個亮點,那麼你就本身打開源碼一點點花時間去研究,若是你目前的基礎不夠,那也能夠輔助以一些優秀的的視頻教程或者文章。可是我始終以爲,紙上得來終覺淺,若是你不能去深刻源碼一點點調試,你對它的認知總歸是比較淺層的。
我堅持在掘金髮文章其實有一個緣由,就是我也但願中文社區能慢慢發展出相似 medium
那樣高質量的前端交流社區(雖然它是付費制的,有難度),而掘金是我前端最開始就接觸到的社區,內心也頗有感情,看着首頁混雜着這種錯誤百出的低質量文章,我內心真的是百感交集,爲何明明是不少未經考證,甚至連本身都說服不了的觀點,也要整理成文章也要急着發出來吸引流量呢?
總之,真心但願掘金能少一些不負責任的水文,一些摘抄搬運官方文檔的東西。你們都認真的輸出本身去證明過,或者真正理解的總結,慢慢的讓掘金、甚至國內的前端氛圍可以造成一個良性氛圍,前端的明天愈來愈美好。