mysql 无论输入什么都是现实 not found_NotAPanda
前言
面試競(jìng)爭(zhēng)力越來(lái)越大,是時(shí)候擼一波Vue和React源碼啦;
本文從20個(gè)層面來(lái)對(duì)比Vue和React的源碼區(qū)別;
如果需要了解API的區(qū)別,請(qǐng)戳:
Vue 開(kāi)發(fā)必須知道的 36 個(gè)技巧
React 開(kāi)發(fā)必須知道的 34 個(gè)技巧
文章源碼:請(qǐng)戳,原創(chuàng)碼字不易,歡迎star!
1.Vue和React源碼區(qū)別
1.1 Vue源碼
來(lái)張Vue源碼編譯過(guò)程圖
圖片來(lái)源:分析Vue源碼實(shí)現(xiàn)
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ì)跳過(guò)靜態(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 依賴(lài)收集與監(jiān)聽(tīng)
這部分是數(shù)據(jù)響應(yīng)式系統(tǒng)
1.調(diào)用 observer(),作用是遍歷對(duì)象屬性進(jìn)行雙向綁定;
2.在 observer 過(guò)程中會(huì)注冊(cè)O(shè)bject.defineProperty的 get 方法進(jìn)行依賴(lài)收集,依賴(lài)收集是將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 是將同層的樹(shù)節(jié)點(diǎn)進(jìn)行比較,通過(guò)唯一的 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通過(guò)虛擬節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 并插入到它的父節(jié)點(diǎn)中;
5.createElm實(shí)質(zhì)是遍歷虛擬 dom,逆向解析成真實(shí) dom;
1.2 React 源碼
來(lái)張React源碼編譯過(guò)程圖
圖片來(lái)源:React源碼解析
1.2.1 React.Component
1.原型上掛載了setState和forceUpdate方法;
2.提供props,context,refs 等屬性;
3.組件定義通過(guò) 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)建四大類(lèi)組件,返回一個(gè) VNode;
5.四大類(lèi)組件封裝的過(guò)程中,調(diào)用了mountComponet方法,觸發(fā)生命周期,解析出 HTML;
1.2.3 組件類(lèi)型和生命周期
1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent組件沒(méi)有觸發(fā)生命周期;
2.ReactCompositeComponent類(lèi)型調(diào)用mountComponent方法,會(huì)觸發(fā)生命周期,處理 state 執(zhí)行componentWillMount鉤子,執(zhí)行 render,獲得 html,執(zhí)行componentDidMounted
1.2.4 data 更新 setState
細(xì)節(jié)請(qǐng)見(jiàn) 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.通過(guò) this 改變值,會(huì)觸發(fā) Object.defineProperty的 set,將依賴(lài)放入隊(duì)列,下一個(gè)事件循環(huán)開(kāi)始時(shí)執(zhí)行更新時(shí)才會(huì)進(jìn)行必要的DOM更新,是外部監(jiān)聽(tīng)處理更新;
3.differcompile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù),而且是采用雙向遍歷方法;
2.React 和 Vue 渲染過(guò)程區(qū)別
2.1 React
1.生成期(掛載):參照 1.2.1
2.更新: 參照1.1.3和 1.1.4
3.卸載:銷(xiāo)毀掛載的組件
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,用來(lái)存放除@hook:生命周期鉤子名稱(chēng)="綁定的函數(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,層層嵌套,形成一棵樹(shù)
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 的基本分類(lèi):EmptyVNode,TextVNode,ComponentVNNode,ElementVNNode,CloneVNode
6.創(chuàng)建 VNode方法一:
// 利用createDocumentFragment()創(chuàng)建虛擬 dom 片段
// 節(jié)點(diǎn)對(duì)象包含dom所有屬性和方法
// html
// 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ì)象來(lái)模擬 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)都去尋找自己的新位置; 一句話(huà)就是首位是老大,不移動(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ù)雜度的問(wèn)題轉(zhuǎn)換成 O(n) 復(fù)雜度;
4.component diff:
根據(jù)batchingStrategy.isBatchingUpdates值是否為 true;
如果true 同一類(lèi)型組件,按照 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è)引用,說(shuō)明是使用的同一個(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āi)源庫(kù)snabbdom,
snabbdom地址
2.也是同級(jí)比較,因?yàn)樵?compile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù);
3.Vue 的這個(gè) DOM Diff 過(guò)程就是一個(gè)查找排序的過(guò)程,遍歷 Virtual DOM 的節(jié)點(diǎn),在 Real DOM 中找到對(duì)應(yīng)的節(jié)點(diǎn),并移動(dòng)到新的位置上。不過(guò)這套算法使用了雙向遍歷的方式,加速了遍歷的速度,更多請(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 通過(guò)一個(gè)隊(duì)列機(jī)制來(lái)實(shí)現(xiàn) state 更新,當(dāng)執(zhí)行 setState() 時(shí),會(huì)將需要更新的 state 淺合并后,根據(jù)變量 isBatchingUpdates(默認(rèn)為 false)判斷是直接更新還是放入狀態(tài)隊(duì)列;
2.通過(guò)js的事件綁定程序 addEventListener 和使用setTimeout/setInterval 等 React 無(wú)法掌控的 API情況下isBatchingUpdates 為 false,同步更新。除了這幾種情況外batchedUpdates函數(shù)將isBatchingUpdates修改為 true;
3.放入隊(duì)列的不會(huì)立即更新 state,隊(duì)列機(jī)制可以高效的批量更新 state。而如果不通過(guò)setState,直接修改this.state 的值,則不會(huì)放入狀態(tài)隊(duì)列;
4.setState 依次直接設(shè)置 state 值會(huì)被合并,但是傳入 function 不會(huì)被合并;
讓setState接受一個(gè)函數(shù)的API的設(shè)計(jì)是相當(dāng)棒的!不僅符合函數(shù)式編程的思想,讓開(kāi)發(fā)者寫(xiě)出沒(méi)有副作用的函數(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í)行的。只要偵聽(tīng)到數(shù)據(jù)變化,Vue 將開(kāi)啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更;
3.如果同一個(gè) watcher 被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次;
4.也就是下一個(gè)事件循環(huán)開(kāi)始時(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)聽(tīng)則會(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;以此類(lèi)推,創(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)遍歷,判斷是否存在新集合中沒(méi)有但老集合中仍存在的節(jié)點(diǎn),發(fā)現(xiàn)存在這樣的節(jié)點(diǎn) D,因此刪除節(jié)點(diǎn) D;
4.總結(jié):
顯然加了 key 后操作步驟要少很多,性能更好;
但是都會(huì)存在一個(gè)問(wèn)題,上面場(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è)問(wèn)題分為兩個(gè)方面:
1.如果列表是純靜態(tài)展示,不會(huì) CRUD,這樣用 index 作為 key 沒(méi)得啥問(wèn)題;
2.如果不是const list = [1,2,3,4];
// list 刪除 4 不會(huì)有問(wèn)題,但是如果刪除了非 4 就會(huì)有問(wèn)題
// 如果刪除 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é)合起來(lái);
2.只有一個(gè)用createStore方法創(chuàng)建一個(gè) store;
3.action接收 view 發(fā)出的通知,告訴 Store State 要改變,有一個(gè) type 屬性;
4.reducer:純函數(shù)來(lái)處理事件,純函數(shù)指一個(gè)函數(shù)的返回結(jié)果只依賴(lài)于它的參數(shù),并且在執(zhí)行過(guò)程里面沒(méi)有副作用,得到一個(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之間,重寫(xiě)dispatch
5.compose 整合多個(gè)中間件
6.單一數(shù)據(jù)流;state 是可讀的,必須通過(guò) action 改變;reducer設(shè)計(jì)成純函數(shù);
7.2 Vuex
1.Vuex是吸收了Redux的經(jīng)驗(yàn),放棄了一些特性并做了一些優(yōu)化,代價(jià)就是VUEX只能和VUE配合;
2.store:通過(guò) 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é)果只依賴(lài)于它的參數(shù)(外面的變量不會(huì)改變自己),并且在執(zhí)行過(guò)程里面沒(méi)有副作用(自己不會(huì)改變外面的變量);
2.主要就是為了減小副作用,避免影響 state 值,造成錯(cuò)誤的渲染;
3.把reducer設(shè)計(jì)成純函數(shù),便于調(diào)試追蹤改變記錄;
9.Vuex的mutation和Redux的reducer中為什么不能做異步操作
1.在 vuex 里面 actions 只是一個(gè)架構(gòu)性的概念,并不是必須的,說(shuō)到底只是一個(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 存下來(lái),然后就可以隨便 time-travel 了。如果你開(kāi)著 devtool 調(diào)用一個(gè)異步的 action,你可以清楚地看到它所調(diào)用的 mutation 是何時(shí)被記錄下來(lái)的,并且可以立刻查看它們對(duì)應(yīng)的狀態(tài);
4.其實(shí)就是框架是這么設(shè)計(jì)的,便于調(diào)試追蹤改變記錄
10.雙向綁定和 vuex 是否沖突
1.在嚴(yán)格模式中使用Vuex,當(dāng)用戶(hù)輸入時(shí),v-model會(huì)試圖直接修改屬性值,但這個(gè)修改不是在mutation中修改的,所以會(huì)拋出一個(gè)錯(cuò)誤;
2.當(dāng)需要在組件中使用vuex中的state時(shí),有2種解決方案:在input中綁定value(vuex中的state),然后監(jiān)聽(tīng)input的change或者input事件,在事件回調(diào)中調(diào)用mutation修改state的值;
// 雙向綁定計(jì)算屬性
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ì)列的方式來(lái)控制 DOM 更新和 nextTick 回調(diào)先后執(zhí)行;
2.microtask 因?yàn)槠涓邇?yōu)先級(jí)特性,能確保隊(duì)列中的微任務(wù)在一次事件循環(huán)前被執(zhí)行完畢;
3.考慮兼容問(wèn)題,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ì)象是引用類(lèi)型,內(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ì)象的自有屬性全部來(lái)自 childVal, 但是通過(guò)原型鏈委托在了 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
}
14.Vue-router 的路由模式
1.三種:"hash" | "history" | "abstract";
2.hash(默認(rèn)),history 是瀏覽器環(huán)境,abstract是 node 環(huán)境;
3.hash: 使用 URL hash 值來(lái)作路由,是利用哈希值實(shí)現(xiàn)push、replace、go 等方法;
4.history:依賴(lài) HTML5 History API新增的 pushState() 和 replaceState(),需要服務(wù)器配置;
5.abstract:如果發(fā)現(xiàn)沒(méi)有瀏覽器的 API,路由會(huì)自動(dòng)強(qiáng)制進(jìn)入這個(gè)模式。
15.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;
}}
16.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置換策略)。如果沒(méi)有命中,就緩存下來(lái),如果超出緩存最大數(shù)量max,刪除cache中的第一項(xiàng)。
4.keep-alive是一個(gè)抽象組件:它自身不會(huì)渲染一個(gè) DOM 元素,也不會(huì)出現(xiàn)在父組件鏈中;
5.LRU算法:根據(jù)數(shù)據(jù)的歷史訪(fǎng)問(wèn)記錄來(lái)進(jìn)行淘汰數(shù)據(jù),其實(shí)就是訪(fǎng)問(wèn)過(guò)的,以后訪(fǎng)問(wèn)概率會(huì)高;
6.LRU 實(shí)現(xiàn):
新數(shù)據(jù)插入到鏈表頭部;
每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪(fǎng)問(wèn)),則將數(shù)據(jù)移到鏈表頭部;
當(dāng)鏈表滿(mǎn)的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄。
17.Vue 的 set 原理
1.由于 Object.observe()方法廢棄了,所以Vue 無(wú)法檢測(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 或者是原始類(lèi)型值,那么在非生產(chǎn)環(huán)境下會(huì)打印警告信息
// 這個(gè)api本來(lái)就是給對(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)) {
// 類(lèi)似$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è)前面講過(guò)
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
}
// 以上都不成立, 即開(kāi)始給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
18.簡(jiǎn)寫(xiě) Reduxfunction 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
}
}
19 react-redux是如何來(lái)實(shí)現(xiàn)的
源碼組成:
1.connect 將store和dispatch分別映射成props屬性對(duì)象,返回組件
2.context 上下文 導(dǎo)出Provider,,和 consumer
3.Provider 一個(gè)接受store的組件,通過(guò)context api傳遞給所有子組件
20. 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)戳;
總結(jié)
文章源碼:請(qǐng)戳,原創(chuàng)碼字不易,歡迎star!
您的鼓勵(lì)是我持續(xù)創(chuàng)作的動(dòng)力!查看原文
總結(jié)
以上是生活随笔為你收集整理的mysql 无论输入什么都是现实 not found_NotAPanda的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux服务之nginx服务篇一(概念
- 下一篇: 无法打开输入文件mysql_错误LNK1