面試競爭力愈來愈大,是時候擼一波Vue和React源碼啦;
本文將前2個月面試總結成從20個層面來對比Vue和React的源碼區別;
文章有點長,能夠收藏,慢點品嚐; 若是須要了解API的區別,請戳:
Vue 開發必須知道的 36 個技巧
React 開發必須知道的 34 個技巧
文章源碼:請戳,原創碼字不易,歡迎star!html
來張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源碼編譯過程圖
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! 您的鼓勵是我持續創做的動力!