『壹』 vue生命周期詳解
vue源碼中最終執行生命周期函數都是調用 callHook 方法, callHook 函數的邏輯很簡單,根據傳入的生命周期類型 hook ,去拿到 vm.$options[hook] 對應的回調函數數組,然後遍歷執行,執行的時候把 vm 作為函數執行的上下文。
1. new Vue(options) :創建一個vm實例;
2. mergeOptions(resolveConstructorOptions(vm.constructor), options, vm) :合並Vue構造函數里options和傳入的options或合並父子的options。比如:在mergeOptions函數中會調用mergeHook方法合並生命周期的鉤子函數,mergeHook方法原理是只有父時返回父,只有子時返回數組類型的子。父、子都存在時,將子添加在父的後面返回組合而成的數組。這也是父子均有鉤子函數的時候,先執行父的後執行子的的原因;
3. initLifecycle(vm)、initEvents(vm)、initRender(vm) :在創建的vm實例上初始化生命周期、事件、渲染相關的屬性;
4. callHook(vm, 'beforeCreate') :調用beforeCreate生命周期鉤子函數;
5. initInjections(vm)、initState(vm)、initProvide(vm) :初始化數據:inject、state、provide。initState 的作用是初始化 props、data、methods、watch、computed 等屬性;
6. callHook(vm, 'created') :調用created生命周期鉤子函數;
7. vm.$mount(vm.$options.el) : $mount 方法在多個文件中都有定義,如"src/platform/web/entry-runtime-with-compiler.js"、"src/platform/web/runtime/index.js"、"src/platform/weex/runtime/index.js"。因為 $mount 方法的實現是和平台、構建方式相關的。以"entry-runtime-with-compiler.js"為例,關鍵步驟是查看 vm.$options 中是否有render方法,如果沒有則會根據el和template屬性確定最終的template字元串,再調用 compileToFunctions 方法將template字元串轉為render方法,最後,調用原先原型上的$mount方法,即開始執行"lifecycle.js"中 mountComponent 方法;
8. callHook(vm, 'beforeMount') :調用beforeMount生命周期鉤子函數;
9. vm._render() => vm._update() => vm.__patch__() :先執行vm._render方法,即調用createElement生成虛擬DOM,即VNode ,每個VNode有children ,children 每個元素也是⼀個 VNode,這樣就形成了⼀個 VNode Tree;再調用vm._update方法進行首次渲染,vm._update方法核心是調用vm. patch 方法,這個方法跟vm.$mount一樣跟平台相關;vm. patch 方法則是根據生成的VNode Tree遞歸createElm方法創建真實Dom Tree掛載到Dom上;
10. callHook(vm, 'mount') :調用mount生命周期鉤子函數:VNode patch 到 Dom 之後會執行 'invokeInsertHook'函數,把 insertedVnodeQueue 中保存的mount鉤子函數執行一遍,insertedVnodeQueue隊列中的鉤子函數是在根據VNode Tree遞歸createElm方法創建真實Dom Tree過程生成的鉤子函數順序隊列,因此mounted鉤子函數的執行順序是先子後父;
11. data changes :數據更新,nextTick中執行 flushSchelerQueue 方法,該方法會執行watcher隊列中的watcher;
12. callHook(vm, 'beforeUpdate') :執行watcher時會執行watcher的before方法,即調用beforeUpdate生命周期鉤子函數;
13. Virtual DOM re-render and patch :重新render生成新的Virtual DOM,並且patch到DOM上;
14. callHook(vm, 'updated') :調用updated生命周期鉤子函數;
15. vm.$destroy() :啟動卸銷毀過程;
16. callHook(vm, 'beforeDestroy') :調用beforeDestroy生命周期鉤子函數;
17. Teardown watchers, childcomponents and event listeners :執行一系列銷毀動作,在 $destroy 的執行過程中,它又會執行 vm.__patch__(vm._vnode, null) 觸發它子組件的銷毀鉤子函數,這樣一層層的遞歸調用,所以 destroyed 鉤子函數執行順序是先子後父,和 mounted 過程一樣。
18. callHook(vm, 'destroyed ') :調用destroyed 生命周期鉤子函數。
『貳』 vue3源碼分析-實現props,emit,事件處理等
>
本期來實現, setup裡面使用props,父子組件通信props和emit等 ,所有的源碼請查看
在render函數中, 可以通過this,來訪問setup返回的內容,還可以訪問this.$el等
由於是測試dom,jest需要提前注入下面的內容,讓document裡面有app節點,下面測試用例類似在html中定義一個app節點哦
本功能的測試用例正式開始
上面的測試用例
解決這兩個需求:
針對上面的分析,需要在setupStatefulComponent中來創建proxy並且綁定到instance當中,並且setup的執行結果如果是對象,也已經存在instance中了,可以通過instance.setupState來進行獲取
通過上面的操作,從render中this.xxx獲取setup返回對象的內容就ok了,接下來處理el
需要在mountElement中,創建節點的時候,在vnode中綁定下,el,並且在setupStatefulComponent 中的代理對象中判斷當前的key
看似沒有問題吧,但是實際上是有問題的,請仔細思考一下, mountElement是不是比setupStatefulComponent 後執行,setupStatefulComponent執行的時候,vnode.el不存在,後續mountelement的時候,vnode就會有值,那麼上面的測試用例肯定是報錯的,$el為null
解決這個問題的關鍵,mountElement的載入順序是 render -> patch -> mountElement,並且render函數返回的subtree是一個vnode,改vnode中上面是mount的時候,已經賦值好了el,所以在patch後執行下操作
在vue中,可以使用onEvent來寫事件,那麼這個功能是怎麼實現的呢,咋們一起來看看
在本功能的測試用例中,可以分析以下內容:
解決問題:
這個功能比較簡單,在處理prop中做個判斷, 屬性是否滿足 /^on[A-Z]/i這個格式,如果是這個格式,則進行事件注冊,但是vue3會做事件緩存,這個是怎麼做到?
緩存也好實現,在傳入當前的el中增加一個屬性 el._vei || (el._vei = {}) 存在這里,則直接使用,不能存在則創建並且存入緩存
事件處理就ok啦
父子組件通信,在vue中是非常常見的,這里主要實現props與emit
根據上面的測試用例,分析props的以下內容:
解決問題:
問題1: 想要在子組件的setup函數中第一個參數, 使用props,那麼在setup函數調用的時候,把當前組件的props傳入到setup函數中即可 問題2: render中this想要問題,則在上面的那個代理中,在 加入一個判斷,key是否在當前instance的props中 問題3: 修改報錯,那就是只能讀,可以使用以前實現的 api shallowReadonly來包裹一下 既可
做完之後,可以發現咋們的測試用例是運行沒有毛病的
上面實現了props,那麼emit也是少不了的,那麼接下來就來實現下emit
根據上面的測試用例,可以分析出:
解決辦法: 問題1: emit 是setup的第二個參數, 那麼可以在setup函數調用的時候,傳入第二個參數 問題2: 關於emit的第一個參數, 可以做條件判斷,把xxx-xxx的形式轉成xxxXxx的形式,然後加入on,最後在props中取找,存在則調用,不存在則不調用 問題3:emit的第二個參數, 則使用剩餘參數即可
到此就圓滿成功啦!
『叄』 面試中的網紅Vue源碼解析之虛擬DOM,你知多少呢深入解讀diff演算法
眾所周知,在前端的面試中,面試官非常愛考dom和diff演算法。比如,可能會出現在以下場景
滴滴滴,面試官發來一個面試邀請。接受邀請📞
我們都知道, key 的作用在前端的面試是一道很普遍的題目,但是呢,很多時候我們都只浮於知識的表面,而沒有去深挖其原理所在,這個時候我們的競爭力就在這被拉下了。所以呢,深入學習原理對於提升自身的核心競爭力是一個必不可少的過程。
在接下來的這篇文章中,我們將講解面試中很愛考的虛擬DOM以及其背後的diff演算法。 請認真閱讀本文~文末有學習資源免費共享!!!
虛擬DOM是用JavaScript對象描述DOM的層次結構。DOM中的一切屬性都在虛擬DOM中有對應的屬性。本質上是JS 和 DOM 之間的一個映射緩存。
要點:虛擬 DOM 是 JS 對象;虛擬 DOM 是對真實 DOM 的描述。
diff發生在虛擬DOM上。diff演算法是在新虛擬DOM和老虛擬DOM進行diff(精細化比對),實現最小量更新,最後反映到真正的DOM上。
我們前面知道diff演算法發生在虛擬DOM上,而虛擬DOM是如何實現的呢?實際上虛擬DOM是有一個個虛擬節點組成。
h函數用來產生虛擬節點(vnode)。虛擬節點有如下的屬性:
1)sel: 標簽類型,例如 p、div;
2)data: 標簽上的數據,例如 style、class、data-*;
3)children :子節點;
4) text: 文本內容;
5)elm:虛擬節點綁定的真實 DOM 節點;
通過h函數的嵌套,從而得到虛擬DOM樹。
我們編寫了一個低配版的h函數,必須傳入3個參數,重載較弱。
形態1:h('div', {}, '文字')
形態2:h('div', {}, [])
形態3:h('div', {}, h())
首先定義vnode節點,實際上就是把傳入的參數合成對象返回。
[圖片上傳失敗...(image-7a9966-1624019394657)]
然後編寫h函數,根據第三個參數的不同進行不同的響應。
當我們進行比較的過程中,我們採用的4種命中查找策略:
1)新前與舊前:命中則指針同時往後移動。
2)新後與舊後:命中則指針同時往前移動。
3)新後與舊前:命中則涉及節點移動,那麼新後指向的節點,移到 舊後之後 。
4)新前與舊後:命中則涉及節點移動,那麼新前指向的節點,移到 舊前之前 。
命中上述4種一種就不在命中判斷了,如果沒有命中,就需要循環來尋找,移動到舊前之前。直到while(新前<=新後&&舊前<=就後)不成立則完成。
如果是新節點先循環完畢,如果老節點中還有剩餘節點(舊前和舊後指針中間的節點),說明他們是要被刪除的節點。
如果是舊節點先循環完畢,說明新節點中有要插入的節點。
1.什麼是Virtual DOM 和Snabbdom
2.手寫底層源碼h函數
3.感受Vue核心演算法之diff演算法
4.snabbdom之核心h函數的工作原理
1、零基礎入門或者有一定基礎的同學、大中院校學生
2、在職從事相關工作1-2年以及打算轉行前端的朋友
3、對前端開發有興趣人群