面試競爭力愈來愈大,是時候擼一波Vue和React源碼啦;
本文從20個層面來對比Vue和React的源碼區別;
若是須要了解API的區別,請戳:
Vue 開發必須知道的 36 個技巧
React 開發必須知道的 34 個技巧
文章源碼:請戳,原創碼字不易,歡迎star!html
來張Vue源碼編譯過程圖
圖片來源:分析Vue源碼實現vue
初始化$mounted會掛載組件,不存在 render 函數時須要編譯(compile);node
1.compile 分爲 parse,optimize 和 generate,最終獲得 render 函數; react
2.parse 調用 parseHtml 方法,方法核心是利用正則解析 template 的指令,class 和 stype,獲得 AST; git
3.optimize 做用標記 static 靜態節點,後面 patch,diff會跳過靜態節點; github
4.generate 是將 AST 轉化爲 render 函數表達式,執行 vm._render 方法將 render 表達式轉化爲VNode,獲得 render 和 staticRenderFns 字符串; 面試
5.vm._render 方法調用了 VNode 建立的方法createElementajax
// render函數表達式 (function() { with(this){ return _c('div',{ //建立一個 div 元素 attrs:{"id":"app"} //div 添加屬性 id },[ _m(0), //靜態節點 header,此處對應 staticRenderFns 數組索引爲 0 的 render function _v(" "), //空的文本節點 (message) //判斷 message 是否存在 //若是存在,建立 p 元素,元素裏面有文本,值爲 toString(message) ?_c('p',[_v("\n "+_s(message)+"\n ")]) //若是不存在,建立 p 元素,元素裏面有文本,值爲 No message. :_c('p',[_v("\n No message.\n ")]) ] ) } })
這部分是數據響應式系統
1.調用 observer(),做用是遍歷對象屬性進行雙向綁定; 算法
2.在 observer 過程當中會註冊Object.defineProperty的 get 方法進行依賴收集,依賴收集是將Watcher 對象的實例放入 Dep 中; vuex
3.Object.defineProperty的 set 會調用Dep 對象的 notify 方法通知它內部全部的 Watcher 對象調用對應的 update()進行視圖更新;
4.本質是發佈者訂閱模式的應用
diff 算法對比差別和調用 update更新視圖:
1.patch 的 differ 是將同層的樹節點進行比較,經過惟一的 key 進行區分,時間複雜度只有 O(n);
2.上面將到 set 被觸發會調用 watcher 的 update()修改視圖;
3.update 方法裏面調用 patch()獲得同級的 VNode 變化;
4.update 方法裏面調用createElm經過虛擬節點建立真實的 DOM 並插入到它的父節點中;
5.createElm實質是遍歷虛擬 dom,逆向解析成真實 dom;
來張React源碼編譯過程圖
圖片來源:React源碼解析
1.原型上掛載了setState和forceUpdate方法;
2.提供props,context,refs 等屬性;
3.組件定義經過 extends 關鍵字繼承 Component;
1.render 方法調用了React.createElement方法(實際是ReactElement方法);
2.ReactDOM.render(component,mountNode)的形式對自定義組件/原生DOM/字符串進行掛載;
3.調用了內部的ReactMount.render,進而執行ReactMount._renderSubtreeIntoContainer,就是將子DOM插入容器;
4.ReactDOM.render()根據傳入不一樣參數會建立四大類組件,返回一個 VNode;
5.四大類組件封裝的過程當中,調用了mountComponet方法,觸發生命週期,解析出 HTML;
1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent組件沒有觸發生命週期;
2.ReactCompositeComponent類型調用mountComponent方法,會觸發生命週期,處理 state 執行componentWillMount鉤子,執行 render,得到 html,執行componentDidMounted
細節請見 3.1
1.setState 更新 data 後,shouldComponentUpdate爲 true會生成 VNode,爲 false 會結束;
2.VNode會調用 DOM diff,爲 true 更新組件;
React:
1.單向數據流;
2.setSate 更新data 值後,組件本身處理;
3.differ 是首位是除刪除外是固定不動的,而後依次遍歷對比;
Vue:
1.v-model 能夠實現雙向數據流,但只是v-bind:value 和 v-on:input的語法糖;
2.經過 this 改變值,會觸發 Object.defineProperty的 set,將依賴放入隊列,下一個事件循環開始時執行更新時纔會進行必要的DOM更新,是外部監聽處理更新;
3.differcompile 階段的optimize標記了static 點,能夠減小 differ 次數,並且是採用雙向遍歷方法;
1.生成期(掛載):參照 1.2.1
2.更新: 參照1.1.3和 1.1.4
3.卸載:銷燬掛載的組件
1.new Vue()初始化後initLifecycle(vm),initEvents(vm),initRender(vm),callHook(vm,beforeCreate),initState(vm),callHook(vm,created);
A.initLifecycle, 創建父子組件關係,在當前實例上添加一些屬性和生命週期標識。如:children、refs、_isMounted等; B.initEvents,用來存放除@hook:生命週期鉤子名稱="綁定的函數"事件的對象。如:$on、$emit等; C.initRender,用於初始化$slots、$attrs、$listeners; D.initState,是不少選項初始化的彙總,包括:props、methods、data、computed 和 watch 等; E.callHook(vm,created)後才掛載實例
2.compileToFunction:就是將 template 編譯成 render 函數;
3.watcher: 就是執行1.2.3;
4.patch:就是執行 1.2.4
1.都是 JSON 對象;
2.AST 是HTML,JS,Java或其餘語言的語法的映射對象,VNode 只是 DOM 的映射對象,AST 範圍更廣;
3.AST的每層的element,包含自身節點的信息(tag,attr等),同時parent,children分別指向其父element和子element,層層嵌套,造成一棵樹
<div id="app"> <ul> <li v-for="item in items"> itemid:{{item.id}} </li> </ul> </div> //轉化爲 AST 格式爲 { "type": 1, "tag": "div", "attrsList": [ { "name": "id", "value": "app" } ], "attrsMap": { "id": "app" }, "children": [ { "type": 1, "tag": "ul", "attrsList": [], "attrsMap": {}, "parent": { "$ref": "$" }, "children": [ { "type": 1, "tag": "li", // children省略了不少屬性,表示格式便可 } ], "plain": true } ], "plain": false, "attrs": [ { "name": "id", "value": "\"app\"" } ] }
4.vnode就是一系列關鍵屬性如標籤名、數據、子節點的集合,能夠認爲是簡化了的dom:
{ tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; ... }
5.VNode 的基本分類:EmptyVNode,TextVNode,ComponentVNNode,ElementVNNode,CloneVNode
6.建立 VNode
方法一: // 利用createDocumentFragment()建立虛擬 dom 片斷 // 節點對象包含dom全部屬性和方法 // html <ul id="ul"></ul> // js const element = document.getElementById('ul'); const fragment = document.createDocumentFragment(); const browsers = ['Firefox', 'Chrome', 'Opera', 'Safari', 'Internet Explorer']; browsers.forEach(function(browser) { const li = document.createElement('li'); li.textContent = browser; fragment.appendChild(li); // 此處往文檔片斷插入子節點,不會引發迴流 (至關於打包操做) }); console.log(fragment) element.appendChild(fragment); // 將打包好的文檔片斷插入ul節點,只作了一次操做,時間快,性能好 方法二: // 用 JS 對象來模擬 VNode function Element (tagName, props, children) { console.log('this',this) this.tagName = tagName this.props = props this.children = children } let ElementO =new Element('ul', {id: 'list'}, [ new Element('li', {class: 'item'}, ['Item 1']), new Element('li', {class: 'item'}, ['Item 2']), new Element('li', {class: 'item'}, ['Item 3']) ]) // 利用 render 渲染到頁面 Element.prototype.render = function () { const el = document.createElement(this.tagName) // 根據tagName構建 const props = this.props for (const propName in props) { // 設置節點的DOM屬性 const propValue = props[propName] el.setAttribute(propName, propValue) } const children = this.children || [] children.forEach(function (child) { const childEl = (child instanceof Element) ? child.render() // 若是子節點也是虛擬DOM,遞歸構建DOM節點 : document.createTextNode(child) // 若是字符串,只構建文本節點 el.appendChild(childEl) }) return el } console.log('ElementO',ElementO) var ulRoot = ElementO.render() console.log('ulRoot',ulRoot) document.body.appendChild(ulRoot)
1.Virtual DOM 中的首個節點不執行移動操做(除非它要被移除),以該節點爲原點,其它節點都去尋找本身的新位置; 一句話就是首位是老大,不移動;
2.在 Virtual DOM 的順序中,每個節點與前一個節點的前後順序與在 Real DOM 中的順序進行比較,若是順序相同,則沒必要移動,不然就移動到前一個節點的前面或後面;
3.tree diff:只會同級比較,若是是跨級的移動,會先刪除節點 A,再建立對應的 A;將 O(n3) 複雜度的問題轉換成 O(n) 複雜度;
4.component diff:
根據batchingStrategy.isBatchingUpdates值是否爲 true;
若是true 同一類型組件,按照 tree differ 對比;
若是 false將組件放入 dirtyComponent,下面子節點所有替換,具體邏輯看 3.1 setSate
5.element differ:
tree differ 下面有三種節點操做:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)
請戳
6.代碼實現
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren var removedNodes = {} var mountImages = [] // 獲取新的子元素數組 var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context ) if (!nextChildren && !prevChildren) { return } var updates = null var name var nextIndex = 0 var lastIndex = 0 var nextMountIndex = 0 var lastPlacedNode = null for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue } var prevChild = prevChildren && prevChildren[name] var nextChild = nextChildren[name] if (prevChild === nextChild) { // 同一個引用,說明是使用的同一個component,因此咱們須要作移動的操做 // 移動已有的子節點 // NOTICE:這裏根據nextIndex, lastIndex決定是否移動 updates = enqueue( updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) ) // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex) // 更新component的.mountIndex屬性 prevChild._mountIndex = nextIndex } else { if (prevChild) { // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex) } // 添加新的子節點在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ) nextMountIndex++ } // 更新nextIndex nextIndex++ lastPlacedNode = ReactReconciler.getHostNode(nextChild) } // 移除掉不存在的舊子節點,和舊子節點和新子節點不一樣的舊子節點 for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ) } } }
1.自主研發了一套Virtual DOM,是借鑑開源庫snabbdom,
snabbdom地址
2.也是同級比較,由於在 compile 階段的optimize標記了static 點,能夠減小 differ 次數;
3.Vue 的這個 DOM Diff 過程就是一個查找排序的過程,遍歷 Virtual DOM 的節點,在 Real DOM 中找到對應的節點,並移動到新的位置上。不過這套算法使用了雙向遍歷的方式,加速了遍歷的速度,更多請戳;
4.代碼實現:
updateChildren (parentElm, oldCh, newCh) { let oldStartIdx = 0, newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx let idxInOld let elmToMove let before while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (oldStartVnode == null) { //對於vnode.key的比較,會把oldVnode = null oldStartVnode = oldCh[++oldStartIdx] }else if (oldEndVnode == null) { oldEndVnode = oldCh[--oldEndIdx] }else if (newStartVnode == null) { newStartVnode = newCh[++newStartIdx] }else if (newEndVnode == null) { newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] }else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode) api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode) api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else { // 使用key時的比較 if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表 } idxInOld = oldKeyToIdx[newStartVnode.key] if (!idxInOld) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) newStartVnode = newCh[++newStartIdx] } else { elmToMove = oldCh[idxInOld] if (elmToMove.sel !== newStartVnode.sel) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) }else { patchVnode(elmToMove, newStartVnode) oldCh[idxInOld] = null api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el) } newStartVnode = newCh[++newStartIdx] } } } if (oldStartIdx > oldEndIdx) { before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx) }else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } }
相同點:
都是同層 differ,複雜度都爲 O(n);
不一樣點:
1.React 首位是除刪除外是固定不動的,而後依次遍歷對比;
2.Vue 的compile 階段的optimize標記了static 點,能夠減小 differ 次數,並且是採用雙向遍歷方法;
1.setState 經過一個隊列機制來實現 state 更新,當執行 setState() 時,會將須要更新的 state 淺合併後,根據變量 isBatchingUpdates(默認爲 false)判斷是直接更新仍是放入狀態隊列;
2.經過js的事件綁定程序 addEventListener 和使用setTimeout/setInterval 等 React 沒法掌控的 API狀況下isBatchingUpdates 爲 false,同步更新。除了這幾種狀況外batchedUpdates函數將isBatchingUpdates修改成 true;
3.放入隊列的不會當即更新 state,隊列機制能夠高效的批量更新 state。而若是不經過setState,直接修改this.state 的值,則不會放入狀態隊列;
4.setState 依次直接設置 state 值會被合併,可是傳入 function 不會被合併;
讓setState接受一個函數的API的設計是至關棒的!不只符合函數式編程的思想,讓開發者寫出沒有反作用的函數,並且咱們並不去修改組件狀態,只是把要改變的狀態和結果返回給React,維護狀態的活徹底交給React去作。正是把流程的控制權交給了React,因此React才能協調多個setState調用的關係
// 狀況一 state={ count:0 } handleClick() { this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) } // count 值依舊爲1 // 狀況二 increment(state, props) { return { count: state.count + 1 } } handleClick() { this.setState(this.increment) this.setState(this.increment) this.setState(this.increment) } // count 值爲 3
5.更新後執行四個鉤子:shouleComponentUpdate,componentWillUpdate,render,componentDidUpdate
1.vue 自身維護 一個 更新隊列,當你設置 this.a = 'new value',DOM 並不會立刻更新;
2.在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動;
3.若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次;
4.也就是下一個事件循環開始時執行更新時纔會進行必要的DOM更新和去重;
5.因此 for 循環 10000次 this.a = i vue只會更新一次,而不會更新10000次;
6.data 變化後若是 computed 或 watch 監聽則會執行;
1.上面的 5.1 講到 React 的 differ 中 element differ 有三種節點操做;
2.場景一不加 key:
新老集合進行 diff 差別化對比,發現 B != A,則建立並插入 B 至新集合,刪除老集合 A;以此類推,建立並插入 A、D 和 C,刪除 B、C 和 D;
都是相同的節點,但因爲位置發生變化,致使須要進行繁雜低效的刪除、建立操做,其實只要對這些節點進行位置移動便可;
3.場景二加 key:
新建:重新集合中取得 E,判斷老集合中不存在相同節點 E,則建立新節點 ElastIndex不作處理E的位置更新爲新集合中的位置,nextIndex++;
刪除:當完成新集合中全部節點 diff 時,最後還須要對老集合進行循環遍歷,判斷是否存在新集合中沒有但老集合中仍存在的節點,發現存在這樣的節點 D,所以刪除節點 D;
4.總結:
顯然加了 key 後操做步驟要少不少,性能更好;
可是都會存在一個問題,上面場景二隻須要移動首位,位置就可對應,可是因爲首位是老大不能動,因此應該儘可能減小將最後一個節點移動到首位,更多請戳。
Vue 不加 key 場景分析:
1.場景一不加 key:
也會將使用了雙向遍歷的方式查找,發現 A,B,C,D都不等,先刪除再建立;
2.場景二加 key:雙向遍歷的方式查找只須要建立E,刪除D,改變 B、C、A的位置
這個問題分爲兩個方面:
1.若是列表是純靜態展現,不會 CRUD,這樣用 index 做爲 key 沒得啥問題;
2.若是不是
const list = [1,2,3,4]; // list 刪除 4 不會有問題,可是若是刪除了非 4 就會有問題 // 若是刪除 2 const listN= [1,3,4] // 這樣index對應的值就變化了,整個 list 會從新渲染
3.因此 list 最好不要用 index 做爲 key
API:
1.Redux則是一個純粹的狀態管理系統,React利用React-Redux將它與React框架結合起來;
2.只有一個用createStore方法建立一個 store;
3.action接收 view 發出的通知,告訴 Store State 要改變,有一個 type 屬性;
4.reducer:純函數來處理事件,純函數指一個函數的返回結果只依賴於它的參數,而且在執行過程裏面沒有反作用,獲得一個新的 state;
源碼組成:
1.createStore 建立倉庫,接受reducer做爲參數 2.bindActionCreator 綁定store.dispatch和action 的關係 3.combineReducers 合併多個reducers 4.applyMiddleware 洋蔥模型的中間件,介於dispatch和action之間,重寫dispatch 5.compose 整合多箇中間件 6.單一數據流;state 是可讀的,必須經過 action 改變;reducer設計成純函數;
1.Vuex是吸取了Redux的經驗,放棄了一些特性並作了一些優化,代價就是VUEX只能和VUE配合;
2.store:經過 new Vuex.store建立 store,輔助函數mapState;
3.getters:獲取state,有輔助函數 mapGetters;
4.action:異步改變 state,像ajax,輔助函數mapActions;
5.mutation:同步改變 state,輔助函數mapMutations;
1.Redux: view——>actions——>reducer——>state變化——>view變化(同步異步同樣) 2.Vuex: view——>commit——>mutations——>state變化——>view變化(同步操做) view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操做)
1.純函數概念:一個函數的返回結果只依賴於它的參數(外面的變量不會改變本身),而且在執行過程裏面沒有反作用(本身不會改變外面的變量);
2.主要就是爲了減少反作用,避免影響 state 值,形成錯誤的渲染;
3.把reducer設計成純函數,便於調試追蹤改變記錄;
1.在 vuex 裏面 actions 只是一個架構性的概念,並非必須的,說到底只是一個函數,你在裏面想幹嗎均可以,只要最後觸發 mutation 就行;
2.vuex 真正限制你的只有 mutation 必須是同步的這一點(在 redux 裏面就好像 reducer 必須同步返回下一個狀態同樣);
3.每個 mutation 執行完成後均可以對應到一個新的狀態(和 reducer 同樣),這樣 devtools 就能夠打個 snapshot 存下來,而後就能夠隨便 time-travel 了。若是你開着 devtool 調用一個異步的 action,你能夠清楚地看到它所調用的 mutation 是什麼時候被記錄下來的,而且能夠馬上查看它們對應的狀態;
4.其實就是框架是這麼設計的,便於調試追蹤改變記錄
1.在嚴格模式中使用Vuex,當用戶輸入時,v-model會試圖直接修改屬性值,但這個修改不是在mutation中修改的,因此會拋出一個錯誤;
2.當須要在組件中使用vuex中的state時,有2種解決方案:
在input中綁定value(vuex中的state),而後監聽input的change或者input事件,在事件回調中調用mutation修改state的值; // 雙向綁定計算屬性 <input v-model="message"> computed: { message: { get () { return this.$store.state.obj.message }, set (value) { this.$store.commit('updateMessage', value) } } }
何時會用到?
nextTick的使用原則主要就是解決單一事件更新數據後當即操做dom的場景。
1.vue 用異步隊列的方式來控制 DOM 更新和 nextTick 回調前後執行;
2.microtask 由於其高優先級特性,能確保隊列中的微任務在一次事件循環前被執行完畢;
3.考慮兼容問題,vue 作了 microtask 向 macrotask 的降級方案;
4.代碼實現:
const simpleNextTick = function queueNextTick (cb) { return Promise.resolve().then(() => { cb() }) } simpleNextTick(() => { console.log(this.$refs.test.innerText) })
對象是引用類型,內存是存貯引用地址,那麼子組件中的 data 屬性值會互相污染,產生反作用;
若是是函數,函數的{}構成做用域,每一個實例相互獨立,不會相互影響;
由於 state 是定義在函數裏面,做用域已經獨立
1.生命週期鉤子:合併爲數組
function mergeHook ( parentVal, childVal ) { return childVal ? parentVal // 若是 childVal存在 ? parentVal.concat(childVal) // 若是parentVal存在,直接合並 : Array.isArray(childVal) // 若是parentVal不存在 ? childVal // 若是chilidVal是數組,直接返回 : [childVal] // 包裝成一個數組返回 : parentVal // 若是childVal 不存在 直接返回parentVal } // strats中添加屬性,屬性名爲生命週期各個鉤子 config._lifecycleHooks.forEach(function (hook) { strats[hook] = mergeHook // 設置每個鉤子函數的合併策略 })
2.watch:合併爲數組,執行有前後順序;
3.assets(components、filters、directives):合併爲原型鏈式結構,合併的策略就是返回一個合併後的新對象,新對象的自有屬性所有來自 childVal, 可是經過原型鏈委託在了 parentVal 上
function mergeAssets (parentVal, childVal) { // parentVal: Object childVal: Object var res = Object.create(parentVal || null) // 原型委託 return childVal ? extend(res, childVal) : res } config._assetTypes.forEach(function (type) { strats[type + 's'] = mergeAssets })
4.data爲function,須要合併執行後的結果,就是執行 parentVal 和 childVal 的函數,而後再合併函數返回的對象;
5.自定義合併策略:
Vue.config.optionMergeStrategies.watch = function (toVal, fromVal) { // return mergedVal }
1.三種:"hash" | "history" | "abstract";
2.hash(默認),history 是瀏覽器環境,abstract是 node 環境;
3.hash: 使用 URL hash 值來做路由,是利用哈希值實現push、replace、go 等方法;
4.history:依賴 HTML5 History API新增的 pushState() 和 replaceState(),須要服務器配置;
5.abstract:若是發現沒有瀏覽器的 API,路由會自動強制進入這個模式。
class Vue { constructor() { // 事件通道調度中心 this._events = Object.create(null); } $on(event, fn) { if (Array.isArray(event)) { event.map(item => { this.$on(item, fn); }); } else { (this._events[event] || (this._events[event] = [])).push(fn); } return this; } $once(event, fn) { function on() { this.$off(event, on); fn.apply(this, arguments); } on.fn = fn; this.$on(event, on); return this; } $off(event, fn) { if (!arguments.length) { this._events = Object.create(null); return this; } if (Array.isArray(event)) { event.map(item => { this.$off(item, fn); }); return this; } const cbs = this._events[event]; if (!cbs) { return this; } if (!fn) { this._events[event] = null; return this; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } $emit(event) { let cbs = this._events[event]; if (cbs) { const args = [].slice.call(arguments, 1); cbs.map(item => { args ? item.apply(this, args) : item.call(this); }); } return this; }}
1.獲取keep-alive第一個子組件;
2.根據include exclude名單進行匹配,決定是否緩存。若是不匹配,直接返回組件實例,若是匹配,到第3步;
3.根據組件id和tag生成緩存組件的key,再去判斷cache中是否存在這個key,便是否命中緩存,若是命中,用緩存中的實例替代vnode實例,而後更新key在keys中的位置,(LRU置換策略)。若是沒有命中,就緩存下來,若是超出緩存最大數量max,刪除cache中的第一項。
4.keep-alive是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出如今父組件鏈中;
5.LRU算法:根據數據的歷史訪問記錄來進行淘汰數據,其實就是訪問過的,之後訪問機率會高;
6.LRU 實現:
新數據插入到鏈表頭部;
每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部;
當鏈表滿的時候,將鏈表尾部的數據丟棄。
1.因爲 Object.observe()方法廢棄了,因此Vue 沒法檢測到對象屬性的添加或刪除;
2.原理實現:
判斷是不是數組,是利用 splice 處理值;
判斷是不是對象的屬性,直接賦值;
不是數組,且不是對象屬性,建立一個新屬性,不是響應數據直接賦值,是響應數據調用defineReactive;
export function set (target: Array<any> | Object, key: any, val: any): any { // 若是 set 函數的第一個參數是 undefined 或 null 或者是原始類型值,那麼在非生產環境下會打印警告信息 // 這個api原本就是給對象與數組使用的 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { // 相似$vm.set(vm.$data.arr, 0, 3) // 修改數組的長度, 避免索引>數組長度致使splcie()執行有誤 target.length = Math.max(target.length, key) // 利用數組的splice變異方法觸發響應式, 這個前面講過 target.splice(key, 1, val) return val } // target爲對象, key在target或者target.prototype上。 // 同時必須不能在 Object.prototype 上 // 直接修改便可, 有興趣能夠看issue: https://github.com/vuejs/vue/issues/6845 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 以上都不成立, 即開始給target建立一個全新的屬性 // 獲取Observer實例 const ob = (target: any).__ob__ // Vue 實例對象擁有 _isVue 屬性, 即不容許給Vue 實例對象添加屬性 // 也不容許Vue.set/$set 函數爲根數據對象(vm.$data)添加屬性 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // target自己就不是響應式數據, 直接賦值 if (!ob) { target[key] = val return val } // 進行響應式處理 defineReactive(ob.value, key, val) ob.dep.notify() return val } https://juejin.im/post/5e04411f6fb9a0166049a073#heading-18
function createStore(reducer) { let state; let listeners=[]; function getState() { return state; } function dispatch(action) { state=reducer(state,action); listeners.forEach(l=>l()); } function subscribe(listener) { listeners.push(listener); return function () { const index=listeners.indexOf(listener); listeners.splice(inddx,1); } } dispatch({}); return { getState, dispatch, subscribe } }
源碼組成:
1.connect 將store和dispatch分別映射成props屬性對象,返回組件
2.context 上下文 導出Provider,,和 consumer
3.Provider 一個接受store的組件,經過context api傳遞給全部子組件
1.react 能夠分爲 differ 階段和 commit(操做 dom)階段;
2.v16 以前是向下遞歸算法,會阻塞;
3.v16 引入了代號爲 fiber 的異步渲染架構;
4.fiber 核心實現了一個基於優先級和requestIdleCallback循環任務調度算法;
5.算法能夠把任務拆分紅小任務,能夠隨時終止和恢復任務,能夠根據優先級不一樣控制執行順序,更多請戳;
文章源碼:請戳,原創碼字不易,歡迎star!您的鼓勵是我持續創做的動力!