「面試」你不知道的 React 和 Vue 的 20 個(gè)區(qū)別

作者:火狼1
原文地址:https://juejin.im/post/5ef55acde51d4534bf67a878
1.Vue和React源碼區(qū)別
1.1 Vue源碼

1.1.1 掛載
初始化$mounted會(huì)掛載組件,不存在 render 函數(shù)時(shí)需要編譯(compile);
1.1.2 compile
1.compile 分為 parse,optimize 和 generate,最終得到 render 函數(shù);
2.parse 調(diào)用 parseHtml 方法,方法核心是利用正則解析 template 的指令,class 和 stype,得到 AST;
3.optimize 作用標(biāo)記 static 靜態(tài)節(jié)點(diǎn),后面 patch,diff會(huì)跳過靜態(tài)節(jié)點(diǎn);
4.generate 是將 AST 轉(zhuǎn)化為 render 函數(shù)表達(dá)式,執(zhí)行 vm._render 方法將 render 表達(dá)式轉(zhuǎn)化為VNode,得到 render 和 staticRenderFns 字符串;
5.vm._render 方法調(diào)用了 VNode 創(chuàng)建的方法createElement
//?render函數(shù)表達(dá)式
(function()?{
??with(this){
????return?_c('div',{???//創(chuàng)建一個(gè)?div?元素
??????attrs:{"id":"app"}??//div?添加屬性?id
??????},[
????????_m(0),??//靜態(tài)節(jié)點(diǎn)?header,此處對(duì)應(yīng)?staticRenderFns?數(shù)組索引為?0?的?render?function
????????_v("?"),?//空的文本節(jié)點(diǎn)
????????(message)?//判斷?message?是否存在
?????????//如果存在,創(chuàng)建?p?元素,元素里面有文本,值為?toString(message)
?????????_c('p',[_v("\n????"+_s(message)+"\n??")])
????????//如果不存在,創(chuàng)建?p?元素,元素里面有文本,值為?No?message.
????????:_c('p',[_v("\n????No?message.\n??")])
??????]
????)
??}
})
1.1.3 依賴收集與監(jiān)聽
這部分是數(shù)據(jù)響應(yīng)式系統(tǒng)
1.調(diào)用 observer(),作用是遍歷對(duì)象屬性進(jìn)行雙向綁定;
2.在 observer 過程中會(huì)注冊(cè)O(shè)bject.defineProperty的 get 方法進(jìn)行依賴收集,依賴收集是將Watcher 對(duì)象的實(shí)例放入 Dep 中;
3.Object.defineProperty的 set 會(huì)調(diào)用Dep 對(duì)象的 notify 方法通知它內(nèi)部所有的 Watcher 對(duì)象調(diào)用對(duì)應(yīng)的 update()進(jìn)行視圖更新;
4.本質(zhì)是發(fā)布者訂閱模式的應(yīng)用
1.1.4 diff 和 patch
diff 算法對(duì)比差異和調(diào)用 update更新視圖:
1.patch 的 differ 是將同層的樹節(jié)點(diǎn)進(jìn)行比較,通過唯一的 key 進(jìn)行區(qū)分,時(shí)間復(fù)雜度只有 O(n);
2.上面將到 set 被觸發(fā)會(huì)調(diào)用 watcher 的 update()修改視圖;
3.update 方法里面調(diào)用 patch()得到同級(jí)的 VNode 變化;
4.update 方法里面調(diào)用createElm通過虛擬節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 并插入到它的父節(jié)點(diǎn)中;
5.createElm實(shí)質(zhì)是遍歷虛擬 dom,逆向解析成真實(shí) dom;
1.2 React 源碼

1.2.1 React.Component
1.原型上掛載了setState和forceUpdate方法;
2.提供props,context,refs 等屬性;
3.組件定義通過 extends 關(guān)鍵字繼承 Component;
1.2.2 掛載
1.render 方法調(diào)用了React.createElement方法(實(shí)際是ReactElement方法);
2.ReactDOM.render(component,mountNode)的形式對(duì)自定義組件/原生DOM/字符串進(jìn)行掛載;
3.調(diào)用了內(nèi)部的ReactMount.render,進(jìn)而執(zhí)行ReactMount._renderSubtreeIntoContainer,就是將子DOM插入容器;
4.ReactDOM.render()根據(jù)傳入不同參數(shù)會(huì)創(chuàng)建四大類組件,返回一個(gè) VNode;
5.四大類組件封裝的過程中,調(diào)用了mountComponet方法,觸發(fā)生命周期,解析出 HTML;
1.2.3 組件類型和生命周期
1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent組件沒有觸發(fā)生命周期;
2.ReactCompositeComponent類型調(diào)用mountComponent方法,會(huì)觸發(fā)生命周期,處理 state 執(zhí)行componentWillMount鉤子,執(zhí)行 render,獲得 html,執(zhí)行componentDidMounted
1.2.4 data 更新 setState
細(xì)節(jié)請(qǐng)見 3.1
1.2.5 數(shù)據(jù)綁定
1.setState 更新 data 后,shouldComponentUpdate為 true會(huì)生成 VNode,為 false 會(huì)結(jié)束;2.VNode會(huì)調(diào)用 DOM diff,為 true 更新組件;
1.3 對(duì)比
React:
1.單向數(shù)據(jù)流;
2.setSate 更新data 值后,組件自己處理;3.differ 是首位是除刪除外是固定不動(dòng)的,然后依次遍歷對(duì)比;
Vue:
1.v-model 可以實(shí)現(xiàn)雙向數(shù)據(jù)流,但只是v-bind:value 和 v-on:input的語(yǔ)法糖;
2.通過 this 改變值,會(huì)觸發(fā) Object.defineProperty的 set,將依賴放入隊(duì)列,下一個(gè)事件循環(huán)開始時(shí)執(zhí)行更新時(shí)才會(huì)進(jìn)行必要的DOM更新,是外部監(jiān)聽處理更新;
3.differcompile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù),而且是采用雙向遍歷方法;
2.React 和 Vue 渲染過程區(qū)別
2.1 React
1.生成期(掛載):參照 1.2.12.更新: 參照1.1.3和 1.1.43.卸載:銷毀掛載的組件
2.2 Vue
1.new Vue()初始化后initLifecycle(vm),initEvents(vm),initRender(vm),callHook(vm,beforeCreate),initState(vm),callHook(vm,created);
?A.initLifecycle, 建立父子組件關(guān)系,在當(dāng)前實(shí)例上添加一些屬性和生命周期標(biāo)識(shí)。如:children、refs、_isMounted等;??
?B.initEvents,用來存放除@hook:生命周期鉤子名稱="綁定的函數(shù)"事件的對(duì)象。如:$on、$emit等;??
?C.initRender,用于初始化$slots、$attrs、$listeners;?
?D.initState,是很多選項(xiàng)初始化的匯總,包括:props、methods、data、computed 和 watch 等;??
?E.callHook(vm,created)后才掛載實(shí)例?
2.compileToFunction:就是將 template 編譯成 render 函數(shù);
3.watcher: 就是執(zhí)行1.2.3;
4.patch:就是執(zhí)行 1.2.4
3.AST 和 VNode 的異同
1.都是 JSON 對(duì)象;
2.AST 是HTML,JS,Java或其他語(yǔ)言的語(yǔ)法的映射對(duì)象,VNode 只是 DOM 的映射對(duì)象,AST 范圍更廣;
3.AST的每層的element,包含自身節(jié)點(diǎn)的信息(tag,attr等),同時(shí)parent,children分別指向其父element和子element,層層嵌套,形成一棵樹
"app">
??
????- "item?in?items">
??????itemid:{{item.id}}
????
??
//轉(zhuǎn)化為?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就是一系列關(guān)鍵屬性如標(biāo)簽名、數(shù)據(jù)、子節(jié)點(diǎn)的集合,可以認(rèn)為是簡(jiǎn)化了的dom:
{
??tag:?string?|?void;
??data:?VNodeData?|?void;
??children:??Array;
??text:?string?|?void;
??elm:?Node?|?void;
??ns:?string?|?void;
??context:?Component?|?void;
??...
}
5.VNode 的基本分類:EmptyVNode,TextVNode,ComponentVNNode,ElementVNNode,CloneVNode
6.創(chuàng)建 VNode
方法一:
//?利用createDocumentFragment()創(chuàng)建虛擬?dom?片段
//?節(jié)點(diǎn)對(duì)象包含dom所有屬性和方法
//?html
"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);??//?此處往文檔片段插入子節(jié)點(diǎn),不會(huì)引起回流?(相當(dāng)于打包操作)
});
console.log(fragment)
element.appendChild(fragment);??//?將打包好的文檔片段插入ul節(jié)點(diǎn),只做了一次操作,時(shí)間快,性能好
方法二:
//?用?JS?對(duì)象來模擬?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?渲染到頁(yè)面
Element.prototype.render?=?function?()?{
??const?el?=?document.createElement(this.tagName)?//?根據(jù)tagName構(gòu)建
??const?props?=?this.props
??for?(const?propName?in?props)?{?//?設(shè)置節(jié)點(diǎn)的DOM屬性
????const?propValue?=?props[propName]
????el.setAttribute(propName,?propValue)
??}
??const?children?=?this.children?||?[]
??children.forEach(function?(child)?{
????const?childEl?=?(child?instanceof?Element)
????????child.render()?//?如果子節(jié)點(diǎn)也是虛擬DOM,遞歸構(gòu)建DOM節(jié)點(diǎn)
??????:?document.createTextNode(child)?//?如果字符串,只構(gòu)建文本節(jié)點(diǎn)
????el.appendChild(childEl)
??})
??return?el
}
console.log('ElementO',ElementO)
var?ulRoot?=?ElementO.render()
console.log('ulRoot',ulRoot)
document.body.appendChild(ulRoot)
4.React 和Vue 的 differ 算法區(qū)
4.1 React
1.Virtual DOM 中的首個(gè)節(jié)點(diǎn)不執(zhí)行移動(dòng)操作(除非它要被移除),以該節(jié)點(diǎn)為原點(diǎn),其它節(jié)點(diǎn)都去尋找自己的新位置; 一句話就是首位是老大,不移動(dòng);
2.在 Virtual DOM 的順序中,每一個(gè)節(jié)點(diǎn)與前一個(gè)節(jié)點(diǎn)的先后順序與在 Real DOM 中的順序進(jìn)行比較,如果順序相同,則不必移動(dòng),否則就移動(dòng)到前一個(gè)節(jié)點(diǎn)的前面或后面;
3.tree diff:只會(huì)同級(jí)比較,如果是跨級(jí)的移動(dòng),會(huì)先刪除節(jié)點(diǎn) A,再創(chuàng)建對(duì)應(yīng)的 A;將 O(n3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度;
4.component diff:
根據(jù)batchingStrategy.isBatchingUpdates值是否為 true;如果true 同一類型組件,按照 tree differ 對(duì)比;如果 false將組件放入 dirtyComponent,下面子節(jié)點(diǎn)全部替換,具體邏輯看 3.1 setSate
5.element differ:
tree differ 下面有三種節(jié)點(diǎn)操作:INSERT_MARKUP(插入)、MOVE_EXISTING(移動(dòng))和 REMOVE_NODE(刪除)?請(qǐng)戳
6.代碼實(shí)現(xiàn)
_updateChildren:?function(nextNestedChildrenElements,?transaction,?context)?{
???var?prevChildren?=?this._renderedChildren
??var?removedNodes?=?{}
??var?mountImages?=?[]
??//?獲取新的子元素?cái)?shù)組
??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)?{
??????//?同一個(gè)引用,說明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作
??????//?移動(dòng)已有的子節(jié)點(diǎn)
??????// NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng)
??????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)
??????}
??????//?添加新的子節(jié)點(diǎn)在指定的位置上
??????updates?=?enqueue(
????????updates,
????????this._mountChildAtIndex(
??????????nextChild,
??????????mountImages[nextMountIndex],
??????????lastPlacedNode,
??????????nextIndex,
??????????transaction,
??????????context
????????)
??????)
??????nextMountIndex++
????}
????//?更新nextIndex
????nextIndex++
????lastPlacedNode?=?ReactReconciler.getHostNode(nextChild)
??}
??//?移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn)
??for?(name?in?removedNodes)?{
????if?(removedNodes.hasOwnProperty(name))?{
??????updates?=?enqueue(
????????updates,
????????this._unmountChild(prevChildren[name],?removedNodes[name])
??????)
????}
??}
??}
4.2 Vue
1.自主研發(fā)了一套Virtual DOM,是借鑒開源庫(kù)snabbdom,?snabbdom地址
2.也是同級(jí)比較,因?yàn)樵?compile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù);
3.Vue 的這個(gè) DOM Diff 過程就是一個(gè)查找排序的過程,遍歷 Virtual DOM 的節(jié)點(diǎn),在 Real DOM 中找到對(duì)應(yīng)的節(jié)點(diǎn),并移動(dòng)到新的位置上。不過這套算法使用了雙向遍歷的方式,加速了遍歷的速度,?更多請(qǐng)戳;
4.代碼實(shí)現(xiàn):
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)?{???//對(duì)于vnode.key的比較,會(huì)把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時(shí)的比較
??????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)
??}
}
4.3 對(duì)比
相同點(diǎn):
都是同層 differ,復(fù)雜度都為 O(n);
不同點(diǎn):
1.React 首位是除刪除外是固定不動(dòng)的,然后依次遍歷對(duì)比;
2.Vue 的compile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù),而且是采用雙向遍歷方法;
5.React 的 setState和 Vue 改變值的區(qū)別
5.1 setState
1.setState 通過一個(gè)隊(duì)列機(jī)制來實(shí)現(xiàn) state 更新,當(dāng)執(zhí)行 setState() 時(shí),會(huì)將需要更新的 state 淺合并后,根據(jù)變量 isBatchingUpdates(默認(rèn)為 false)判斷是直接更新還是放入狀態(tài)隊(duì)列;
2.通過js的事件綁定程序 addEventListener 和使用setTimeout/setInterval 等 React 無法掌控的 API情況下isBatchingUpdates 為 false,同步更新。除了這幾種情況外batchedUpdates函數(shù)將isBatchingUpdates修改為 true;
3.放入隊(duì)列的不會(huì)立即更新 state,隊(duì)列機(jī)制可以高效的批量更新 state。而如果不通過setState,直接修改this.state 的值,則不會(huì)放入狀態(tài)隊(duì)列;
4.setState 依次直接設(shè)置 state 值會(huì)被合并,但是傳入 function 不會(huì)被合并;
讓setState接受一個(gè)函數(shù)的API的設(shè)計(jì)是相當(dāng)棒的!不僅符合函數(shù)式編程的思想,讓開發(fā)者寫出沒有副作用的函數(shù),而且我們并不去修改組件狀態(tài),只是把要改變的狀態(tài)和結(jié)果返回給React,維護(hù)狀態(tài)的活完全交給React去做。正是把流程的控制權(quán)交給了React,所以React才能協(xié)調(diào)多個(gè)setState調(diào)用的關(guān)系
//?情況一
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.更新后執(zhí)行四個(gè)鉤子:shouleComponentUpdate,componentWillUpdate,render,componentDidUpdate
5.2 Vue 的 this 改變
1.vue 自身維護(hù) 一個(gè) 更新隊(duì)列,當(dāng)你設(shè)置 this.a = 'new value',DOM 并不會(huì)馬上更新;
2.在更新 DOM 時(shí)是異步執(zhí)行的。只要偵聽到數(shù)據(jù)變化,Vue 將開啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更;
3.如果同一個(gè) watcher 被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次;
4.也就是下一個(gè)事件循環(huán)開始時(shí)執(zhí)行更新時(shí)才會(huì)進(jìn)行必要的DOM更新和去重;
5.所以 for 循環(huán) 10000次 this.a = i vue只會(huì)更新一次,而不會(huì)更新10000次;
6.data 變化后如果 computed 或 watch 監(jiān)聽則會(huì)執(zhí)行;
6. Vue的v-for 或 ?React 的map 中為什么不要用 index作為 key
6.1 為什么要加 key
6.1.1 React
1.上面的 5.1 講到 React 的 differ 中 element differ 有三種節(jié)點(diǎn)操作;
2.場(chǎng)景一不加 key:
新老集合進(jìn)行 diff 差異化對(duì)比,發(fā)現(xiàn) B != A,則創(chuàng)建并插入 B 至新集合,刪除老集合 A;以此類推,創(chuàng)建并插入 A、D 和 C,刪除 B、C 和 D;
都是相同的節(jié)點(diǎn),但由于位置發(fā)生變化,導(dǎo)致需要進(jìn)行繁雜低效的刪除、創(chuàng)建操作,其實(shí)只要對(duì)這些節(jié)點(diǎn)進(jìn)行位置移動(dòng)即可;
3.場(chǎng)景二加 key:
新建:從新集合中取得 E,判斷老集合中不存在相同節(jié)點(diǎn) E,則創(chuàng)建新節(jié)點(diǎn) ElastIndex不做處理E的位置更新為新集合中的位置,nextIndex++;
刪除:當(dāng)完成新集合中所有節(jié)點(diǎn) diff 時(shí),最后還需要對(duì)老集合進(jìn)行循環(huán)遍歷,判斷是否存在新集合中沒有但老集合中仍存在的節(jié)點(diǎn),發(fā)現(xiàn)存在這樣的節(jié)點(diǎn) D,因此刪除節(jié)點(diǎn) D;
4.總結(jié):
顯然加了 key 后操作步驟要少很多,性能更好;
但是都會(huì)存在一個(gè)問題,上面場(chǎng)景二只需要移動(dòng)首位,位置就可對(duì)應(yīng),但是由于首位是老大不能動(dòng),所以應(yīng)該盡量減少將最后一個(gè)節(jié)點(diǎn)移動(dòng)到首位,?更多請(qǐng)戳。
6.1.2 Vue
Vue 不加 key 場(chǎng)景分析:
1.場(chǎng)景一不加 key:
也會(huì)將使用了雙向遍歷的方式查找,發(fā)現(xiàn) A,B,C,D都不等,先刪除再創(chuàng)建;
2.場(chǎng)景二加 key:雙向遍歷的方式查找只需要?jiǎng)?chuàng)建E,刪除D,改變 B、C、A的位置
6.2 為什么 key 不能為 index
這個(gè)問題分為兩個(gè)方面:
1.如果列表是純靜態(tài)展示,不會(huì) CRUD,這樣用 index 作為 key 沒得啥問題;
2.如果不是
const?list?=?[1,2,3,4];
//?list?刪除?4?不會(huì)有問題,但是如果刪除了非?4?就會(huì)有問題
//?如果刪除?2
const?listN=?[1,3,4]
//?這樣index對(duì)應(yīng)的值就變化了,整個(gè)?list?會(huì)重新渲染
3.所以 list 最好不要用 index 作為 key
7. Redux和 Vuex 設(shè)計(jì)思想
7.1 Redux
API:
1.Redux則是一個(gè)純粹的狀態(tài)管理系統(tǒng),React利用React-Redux將它與React框架結(jié)合起來;
2.只有一個(gè)用createStore方法創(chuàng)建一個(gè) store;
3.action接收 view 發(fā)出的通知,告訴 Store State 要改變,有一個(gè) type 屬性;
4.reducer:純函數(shù)來處理事件,純函數(shù)指一個(gè)函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過程里面沒有副作用,得到一個(gè)新的 state;
源碼組成:
1.createStore?創(chuàng)建倉(cāng)庫(kù),接受reducer作為參數(shù)??
2.bindActionCreator?綁定store.dispatch和action?的關(guān)系??
3.combineReducers?合并多個(gè)reducers??
4.applyMiddleware?洋蔥模型的中間件,介于dispatch和action之間,重寫dispatch
5.compose?整合多個(gè)中間件
6.單一數(shù)據(jù)流;state?是可讀的,必須通過?action?改變;reducer設(shè)計(jì)成純函數(shù);
7.2 Vuex
1.Vuex是吸收了Redux的經(jīng)驗(yàn),放棄了一些特性并做了一些優(yōu)化,代價(jià)就是VUEX只能和VUE配合;
2.store:通過 new Vuex.store創(chuàng)建 store,輔助函數(shù)mapState;
3.getters:獲取state,有輔助函數(shù) mapGetters;
4.action:異步改變 state,像ajax,輔助函數(shù)mapActions;
5.mutation:同步改變 state,輔助函數(shù)mapMutations;
7.3 對(duì)比
1.Redux:view——>actions——>reducer——>state變化——>view變化(同步異步一樣)
2.Vuex:view——>commit——>mutations——>state變化——>view變化(同步操作)?
??view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操作)
8.redux 為什么要把 reducer 設(shè)計(jì)成純函數(shù)
1.純函數(shù)概念:一個(gè)函數(shù)的返回結(jié)果只依賴于它的參數(shù)(外面的變量不會(huì)改變自己),并且在執(zhí)行過程里面沒有副作用(自己不會(huì)改變外面的變量);
2.主要就是為了減小副作用,避免影響 state 值,造成錯(cuò)誤的渲染;
3.把reducer設(shè)計(jì)成純函數(shù),便于調(diào)試追蹤改變記錄;
9.Vuex的mutation和Redux的reducer中為什么不能做異步操作
1.在 vuex 里面 actions 只是一個(gè)架構(gòu)性的概念,并不是必須的,說到底只是一個(gè)函數(shù),你在里面想干嘛都可以,只要最后觸發(fā) mutation 就行;
2.vuex 真正限制你的只有 mutation 必須是同步的這一點(diǎn)(在 redux 里面就好像 reducer 必須同步返回下一個(gè)狀態(tài)一樣);
3.每一個(gè) mutation 執(zhí)行完成后都可以對(duì)應(yīng)到一個(gè)新的狀態(tài)(和 reducer 一樣),這樣 devtools 就可以打個(gè) snapshot 存下來,然后就可以隨便 time-travel 了。如果你開著 devtool 調(diào)用一個(gè)異步的 action,你可以清楚地看到它所調(diào)用的 mutation 是何時(shí)被記錄下來的,并且可以立刻查看它們對(duì)應(yīng)的狀態(tài);
4.其實(shí)就是框架是這么設(shè)計(jì)的,便于調(diào)試追蹤改變記錄
10.雙向綁定和 vuex 是否沖突
1.在嚴(yán)格模式中使用Vuex,當(dāng)用戶輸入時(shí),v-model會(huì)試圖直接修改屬性值,但這個(gè)修改不是在mutation中修改的,所以會(huì)拋出一個(gè)錯(cuò)誤;
2.當(dāng)需要在組件中使用vuex中的state時(shí),有2種解決方案:
在input中綁定value(vuex中的state),然后監(jiān)聽input的change或者input事件,在事件回調(diào)中調(diào)用mutation修改state的值;??
//?雙向綁定計(jì)算屬性
"message">
computed:?{
??message:?{
????get?()?{
??????return?this.$store.state.obj.message
????},
????set?(value)?{
??????this.$store.commit('updateMessage',?value)
????}
??}
}
11. Vue的nextTick原理
11.1 使用場(chǎng)景
什么時(shí)候會(huì)用到?
nextTick的使用原則主要就是解決單一事件更新數(shù)據(jù)后立即操作dom的場(chǎng)景。
11.2 原理
1.vue 用異步隊(duì)列的方式來控制 DOM 更新和 nextTick 回調(diào)先后執(zhí)行;
2.microtask 因?yàn)槠涓邇?yōu)先級(jí)特性,能確保隊(duì)列中的微任務(wù)在一次事件循環(huán)前被執(zhí)行完畢;
3.考慮兼容問題,vue 做了 microtask 向 macrotask 的降級(jí)方案;
4.代碼實(shí)現(xiàn):
const?simpleNextTick?=?function?queueNextTick?(cb)?{???
????return?Promise.resolve().then(()?=>?{
??????cb()
????})
}
simpleNextTick(()?=>?{
??console.log(this.$refs.test.innerText)
})
13. Vue 的data 必須是函數(shù)而 React 的 state 是對(duì)象
13.1 Vue 的 data 必須是函數(shù)
對(duì)象是引用類型,內(nèi)存是存貯引用地址,那么子組件中的 data 屬性值會(huì)互相污染,產(chǎn)生副作用;
如果是函數(shù),函數(shù)的{}構(gòu)成作用域,每個(gè)實(shí)例相互獨(dú)立,不會(huì)相互影響;
13.2 React 的 state 是對(duì)象
因?yàn)?state 是定義在函數(shù)里面,作用域已經(jīng)獨(dú)立
14.Vue 的合并策略
1.生命周期鉤子:合并為數(shù)組
function?mergeHook?(
??parentVal,
??childVal?
)?{
??return?childVal
??????parentVal?//?如果?childVal存在
????????parentVal.concat(childVal)?//?如果parentVal存在,直接合并
??????:?Array.isArray(childVal)?//?如果parentVal不存在
??????????childVal??//?如果chilidVal是數(shù)組,直接返回
????????:?[childVal]?//?包裝成一個(gè)數(shù)組返回
????:?parentVal??//?如果childVal?不存在?直接返回parentVal?
}
//?strats中添加屬性,屬性名為生命周期各個(gè)鉤子
config._lifecycleHooks.forEach(function?(hook)?{
??strats[hook]?=?mergeHook?//?設(shè)置每一個(gè)鉤子函數(shù)的合并策略
})
2.watch:合并為數(shù)組,執(zhí)行有先后順序;
3.assets(components、filters、directives):合并為原型鏈?zhǔn)浇Y(jié)構(gòu),合并的策略就是返回一個(gè)合并后的新對(duì)象,新對(duì)象的自有屬性全部來自 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,需要合并執(zhí)行后的結(jié)果,就是執(zhí)行 parentVal 和 childVal 的函數(shù),然后再合并函數(shù)返回的對(duì)象;
5.自定義合并策略:
Vue.config.optionMergeStrategies.watch?=?function?(toVal,?fromVal)?{
??//?return?mergedVal
}
15.Vue-router 的路由模式
1.三種:"hash" | "history" | "abstract";
2.hash(默認(rèn)),history 是瀏覽器環(huán)境,abstract是 node 環(huán)境;
3.hash: 使用 URL hash 值來作路由,是利用哈希值實(shí)現(xiàn)push、replace、go 等方法;
4.history:依賴 HTML5 History API新增的 pushState() 和 replaceState(),需要服務(wù)器配置;
5.abstract:如果發(fā)現(xiàn)沒有瀏覽器的 API,路由會(huì)自動(dòng)強(qiáng)制進(jìn)入這個(gè)模式。
16.Vue 的事件機(jī)制
class?Vue?{??
??constructor()?{????
????//??事件通道調(diào)度中心????
????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;??
}}
17.keep-alive 的實(shí)現(xiàn)原理和緩存策略
1.獲取keep-alive第一個(gè)子組件;
2.根據(jù)include exclude名單進(jìn)行匹配,決定是否緩存。如果不匹配,直接返回組件實(shí)例,如果匹配,到第3步;
3.根據(jù)組件id和tag生成緩存組件的key,再去判斷cache中是否存在這個(gè)key,即是否命中緩存,如果命中,用緩存中的實(shí)例替代vnode實(shí)例,然后更新key在keys中的位置,(LRU置換策略)。如果沒有命中,就緩存下來,如果超出緩存最大數(shù)量max,刪除cache中的第一項(xiàng)。
4.keep-alive是一個(gè)抽象組件:它自身不會(huì)渲染一個(gè) DOM 元素,也不會(huì)出現(xiàn)在父組件鏈中;
5.LRU算法:根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù),其實(shí)就是訪問過的,以后訪問概率會(huì)高;
6.LRU 實(shí)現(xiàn):新數(shù)據(jù)插入到鏈表頭部;每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表頭部;當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄。
18.Vue 的 set 原理
1.由于 Object.observe()方法廢棄了,所以Vue 無法檢測(cè)到對(duì)象屬性的添加或刪除;
2.原理實(shí)現(xiàn):
判斷是否是數(shù)組,是利用 splice 處理值;
判斷是否是對(duì)象的屬性,直接賦值;
不是數(shù)組,且不是對(duì)象屬性,創(chuàng)建一個(gè)新屬性,不是響應(yīng)數(shù)據(jù)直接賦值,是響應(yīng)數(shù)據(jù)調(diào)用defineReactive;
export?function?set?(target:?Array?|?Object,?key:?any,?val:?any):?any?{
//?如果?set?函數(shù)的第一個(gè)參數(shù)是?undefined?或?null?或者是原始類型值,那么在非生產(chǎn)環(huán)境下會(huì)打印警告信息
//?這個(gè)api本來就是給對(duì)象與數(shù)組使用的
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)
??//?修改數(shù)組的長(zhǎng)度,?避免索引>數(shù)組長(zhǎng)度導(dǎo)致splcie()執(zhí)行有誤
??target.length?=?Math.max(target.length,?key)
??//?利用數(shù)組的splice變異方法觸發(fā)響應(yīng)式,?這個(gè)前面講過
??target.splice(key,?1,?val)
??return?val
}
// target為對(duì)象, key在target或者target.prototype上。
//?同時(shí)必須不能在?Object.prototype?上
//?直接修改即可,?有興趣可以看issue:?https://github.com/vuejs/vue/issues/6845
if?(key?in?target?&&?!(key?in?Object.prototype))?{
??target[key]?=?val
??return?val
}
//?以上都不成立,?即開始給target創(chuàng)建一個(gè)全新的屬性
//?獲取Observer實(shí)例
const?ob?=?(target:?any).__ob__
//?Vue?實(shí)例對(duì)象擁有?_isVue?屬性,?即不允許給Vue?實(shí)例對(duì)象添加屬性
//?也不允許Vue.set/$set?函數(shù)為根數(shù)據(jù)對(duì)象(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本身就不是響應(yīng)式數(shù)據(jù),?直接賦值
if?(!ob)?{
??target[key]?=?val
??return?val
}
//?進(jìn)行響應(yīng)式處理
defineReactive(ob.value,?key,?val)
ob.dep.notify()
return?val
}
https://juejin.im/post/5e04411f6fb9a0166049a073#heading-18
19.簡(jiǎn)寫 Redux
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
????}
}
20. react-redux是如何來實(shí)現(xiàn)的
源碼組成:
1.connect 將store和dispatch分別映射成props屬性對(duì)象,返回組件
2.context 上下文 導(dǎo)出Provider,,和 consumer
3.Provider 一個(gè)接受store的組件,通過context api傳遞給所有子組件
21. react16 的 fiber 理解
1.react 可以分為 differ 階段和 commit(操作 dom)階段;
2.v16 之前是向下遞歸算法,會(huì)阻塞;
3.v16 引入了代號(hào)為 fiber 的異步渲染架構(gòu);
4.fiber 核心實(shí)現(xiàn)了一個(gè)基于優(yōu)先級(jí)和requestIdleCallback循環(huán)任務(wù)調(diào)度算法;
5.算法可以把任務(wù)拆分成小任務(wù),可以隨時(shí)終止和恢復(fù)任務(wù),可以根據(jù)優(yōu)先級(jí)不同控制執(zhí)行順序,?更多請(qǐng)戳;
