2020.11.01
1志鹃、請(qǐng)簡(jiǎn)述 Vue 首次渲染的過程。
首次渲染總結(jié):
- 在首次渲染之前,首先進(jìn)行Vue初始化,初始化實(shí)例成員和靜態(tài)成員
- 當(dāng)初始化結(jié)束之后,要調(diào)用Vue的構(gòu)造函數(shù)new Vue(),在構(gòu)造函數(shù)中調(diào)用了_init()方法,這個(gè)方法相當(dāng)于我們整個(gè)Vue的入口
- 在_init方法中,最終調(diào)用了$mount,一共有兩個(gè)$mount,第一個(gè)定義在entry-runtime-with-compiler.js文件中,也就是我們的入口文件$mount,這個(gè)$mount()的核心作用是幫我們把模板編譯成render函數(shù)泽西,但它首先會(huì)判斷一下當(dāng)前是否傳入了render選項(xiàng)曹铃,如果沒有傳入的話,它會(huì)去獲取我們的template選項(xiàng)捧杉,如果template選項(xiàng)也沒有的話陕见,他會(huì)把el中的內(nèi)容作為我們的模板,然后把模板編譯成render函數(shù)糠溜,它是通過compileToFunctions()函數(shù)淳玩,幫我們把模板編譯成render函數(shù)的,當(dāng)把render函數(shù)編譯好之后,它會(huì)把render函數(shù)存在我們的options.render中非竿。
- 接著會(huì)調(diào)用src/platforms/web/runtime/index.js文件中的$mount方法,在這個(gè)中首先會(huì)重新獲取el蜕着,因?yàn)槿绻沁\(yùn)行時(shí)版本的話,是不會(huì)走entry-runtime-with-compiler.js這個(gè)入口中獲取el,所以如果是運(yùn)行時(shí)版本的話承匣,我們會(huì)在runtime/index.js的$mount()中重新獲取el蓖乘。
- 接下來調(diào)用mountComponent(),這個(gè)方法在src/core/instance/lifecycle.js中定義的,在mountComponent()中韧骗,首先會(huì)判斷render選項(xiàng)嘉抒,如果沒有render選項(xiàng),但是我們傳入了模板袍暴,并且當(dāng)前是開發(fā)環(huán)境的話會(huì)發(fā)送一個(gè)警告些侍,目的是如果我們當(dāng)前使用運(yùn)行時(shí)版本的Vue,而且我們沒有傳入render,但是傳入了模版,告訴我們運(yùn)行時(shí)版本不支持編譯器。接下來會(huì)觸發(fā)beforeMount這個(gè)生命周期中的鉤子函數(shù)政模,也就是開始掛載之前岗宣。
- 然后定義了updateComponent(),在這個(gè)函數(shù)中淋样,調(diào)用vm._render和vm._update耗式,vm._render的作用是生成虛擬DOM,vm._update的作用是將虛擬DOM轉(zhuǎn)換成真實(shí)DOM趁猴,并且掛載到頁面上
- 創(chuàng)建Watcher對(duì)象刊咳,在創(chuàng)建Watcher時(shí),傳遞了updateComponent這個(gè)函數(shù)儡司,這個(gè)函數(shù)最終是在Watcher內(nèi)部調(diào)用的娱挨。在Watcher內(nèi)部會(huì)用了get方法,當(dāng)Watcher創(chuàng)建完成之后,會(huì)觸發(fā)生命周期中的mounted鉤子函數(shù)捕犬。在watcher 中的get方法(創(chuàng)建完watcher會(huì)調(diào)用一次)中让蕾,會(huì)調(diào)用updateComponent(),
- 掛載結(jié)束或听,最終返回Vue實(shí)例。
2笋婿、請(qǐng)簡(jiǎn)述 Vue 響應(yīng)式原理誉裆。
Vue的響應(yīng)式是從Vue的實(shí)例init()方法中開始的,在init()方法中先調(diào)用initState()初始化Vue實(shí)例的狀態(tài)缸濒,在initState方法中調(diào)用了initData()足丢, initData()是把data屬性注入到Vue實(shí)例上,并且調(diào)用observe(data)將data對(duì)象轉(zhuǎn)化成響應(yīng)式的對(duì)象庇配。
observe是響應(yīng)式的入口, 在observe(value)中斩跌,首先判斷傳入的參數(shù)value是否是對(duì)象,如果不是對(duì)象直接返回捞慌。再判斷value對(duì)象是否有_ob_這個(gè)屬性耀鸦,如果有說明做過了響應(yīng)式處理,則直接返回;如果沒有袖订,創(chuàng)建observer對(duì)象氮帐,并且返回observer`對(duì)象。
在創(chuàng)建observer對(duì)象時(shí)洛姑,給當(dāng)前的value對(duì)象定義不可枚舉的_ob_屬性上沐,記錄當(dāng)前的observer對(duì)象,然后再進(jìn)行數(shù)組的響應(yīng)式處理和對(duì)象的響應(yīng)式處理楞艾。數(shù)組的響應(yīng)式處理就是設(shè)置數(shù)組的幾個(gè)特殊的方法参咙,push、pop硫眯、shift等蕴侧,然后找到數(shù)組對(duì)象中的_ob_對(duì)象中的dep,調(diào)用dep的notify()方法,再遍歷數(shù)組中每一個(gè)成員舟铜,對(duì)每個(gè)成員調(diào)用observer()戈盈,如果這個(gè)成員是對(duì)象的話,也會(huì)轉(zhuǎn)換成響應(yīng)式對(duì)象谆刨。對(duì)象的響應(yīng)式處理塘娶,就是調(diào)用walk方法,walk方法就是遍歷對(duì)象的每一個(gè)屬性痊夭,對(duì)每個(gè)屬性調(diào)用defineReactive方法
defineReactive會(huì)為每一個(gè)屬性創(chuàng)建對(duì)應(yīng)的dep對(duì)象刁岸,讓dep去收集依賴,如果當(dāng)前屬性的值是對(duì)象她我,會(huì)調(diào)用observe虹曙。defineReactive中最核心的方法是getter 和 setter。getter 的作用是收集依賴番舆,收集依賴時(shí), 為每一個(gè)屬性收集依賴酝碳,如果這個(gè)屬性的值是對(duì)象,那也要為子對(duì)象收集依賴恨狈,最后返回屬性的值疏哗。在setter 中,先保存新值禾怠,如果新值是對(duì)象返奉,也要調(diào)用 observe ,把新設(shè)置的對(duì)象也轉(zhuǎn)換成響應(yīng)式的對(duì)象,然后派發(fā)更新(發(fā)送通知)吗氏,調(diào)用dep.notify()
收集依賴時(shí)芽偏,在watcher對(duì)象的get方法中調(diào)用pushTarget, 記錄Dep.target屬性;訪問data中的成員的時(shí)候收集依賴弦讽,defineReactive的getter中收集依賴污尉,把屬性對(duì)應(yīng)的 watcher 對(duì)象添加到dep的subs數(shù)組中,給childOb收集依賴,目的是子對(duì)象添加和刪除成員時(shí)發(fā)送通知十厢。
在數(shù)據(jù)發(fā)生變化的時(shí)候等太,會(huì)調(diào)用dep.notify()發(fā)送通知,dep.notify()會(huì)調(diào)用watcher對(duì)象的update()方法蛮放,update()中的調(diào)用的queueWatcher()會(huì)去判斷watcher是否被處理缩抡,如果這個(gè)watcher對(duì)象沒有的話添加到queue隊(duì)列中,并調(diào)用flushScheduleQueue()包颁,在flushScheduleQueue()中觸發(fā)beforeUpdate鉤子函數(shù)調(diào)用watcher.run():run()-->get() --> getter() --> updateComponent()瞻想; 然后清空上一次的依賴;觸發(fā)actived的鉤子函數(shù)娩嚼;觸發(fā)updated鉤子函數(shù)
3蘑险、請(qǐng)簡(jiǎn)述虛擬 DOM 中 Key 的作用和好處。
key值的作用岳悟,其實(shí)是:追蹤列表中哪些元素被添加佃迄、被修改、被移除的輔助標(biāo)志贵少。就是他可以幫助我們快速對(duì)比兩個(gè)虛擬dom對(duì)象呵俏,找到虛擬dom對(duì)象被修改的元素,然后僅僅替換掉被修改的元素滔灶,然后再生成新的真實(shí)dom
好處: 可以減少 dom 的操作普碎,減少 diff 和渲染所需要的時(shí)間,提升了性能录平。
4麻车、請(qǐng)簡(jiǎn)述 Vue 中模板編譯的過程。
- 模版編譯入口函數(shù)compileToFunctions,內(nèi)部首先從緩存加載編譯好的render函數(shù)斗这,如果緩存中沒有动猬,則調(diào)用compile,開始編譯
- 在compile 函數(shù)中表箭,首先合并選項(xiàng)options枣察,然后再調(diào)用baseCompile 編譯模版。 compile的核心是處理選項(xiàng)options燃逻, 真正處理是在basCompile中完成的
- 模版和合并好的選項(xiàng)傳遞給baseCompile, 這里面完成了模版編譯的核心三件事情。 parse()- 首先把模版字符串轉(zhuǎn)化為AST 對(duì)象臂痕,也就是抽象語法樹伯襟;optimize() - 然后對(duì)抽象語法樹進(jìn)行優(yōu)化,標(biāo)記ASTtree 中的靜態(tài)sub tree, 檢測(cè)到靜態(tài)子樹握童,設(shè)置為靜態(tài)姆怪,不需要在每次重新渲染的時(shí)候重新生成節(jié)點(diǎn),patch的過程中會(huì)過靜態(tài)子樹;generator() - 最后把優(yōu)化過的AST對(duì)象稽揭,轉(zhuǎn)化為字符串形式的代碼俺附。 執(zhí)行完成之后,會(huì)回到入口函數(shù)complieToFunctions
- compileToFunction, 會(huì)繼續(xù)把字符串代碼轉(zhuǎn)化為函數(shù)溪掀,通過調(diào)用createFunction事镣,當(dāng)render 和 staticRenderFns初始化完畢,最終會(huì)掛在到Vue實(shí)例的options對(duì)應(yīng)的屬性中