前言
面试竞争力越来越大,是时分撸一波Vue和React源码啦;
本文将前2个月面试总结成从20个层面来比照Vue和React的源码差异;
文章有点长,能够收藏,慢点品尝;
假如需求了解API的差异,请戳:
Vue 开发有必要知道的 36f M $ v @ 个技巧
Reac2 G {t 开发有必要知道的 34 个技巧
文章源码:请戳,原创码字不易,欢迎star!
1.VV ? S aue和React源码差异
1.1 Vue源码
来张Vue源码编译进程图
图片来历:分析Vue源码完成
1.1.1 挂载
初始化$mounted会挂载组件,不存在 render 函数时需求编译(compile);
1.1.2 compile
1.compile 分为 parse,optimize 和 generate,最终得到 render 函数;M R Q r O H } _ n
2.parse 调用 parr $ ? q XseL L p @ 3 B 2Html 办法,4 $ / V u = l办法中心是运用正则解析 template 的指令,class 和 stype,得到 AST;
3.optimize 效果符号 static 静态节点,后面 patch,diff会越过静态节点;
4.gener{ 0 N { ; qate 是将 AST 转化为 render 函数表达式,履行 vm._rend0 ) . r | 5er 办法将 render 表达式转化为VNode,得到 render 和 staticRenderFns 字符串;
5.vm._render 办法调用了 VNode 创立的办法createElement
// render函数表达式
(function() {
with(this){
return _c('div',{ //创立一个 div 元素
attrs:{"id":"app"} //div 增加特点 id
},[
_m(0), //静态节点 header,此处对应 staticRenderFns 数组索引为 0 的 render function
_v(" "), //空的文本节点
(message) //判别 message 是否存在
//假如存在,创立 p 元素,元素4 4 l q * .里边有文本,值为 toString(message)
?_c('p',[_v("\n "+_s(message)+"\n ")])
//假如不@ ) o T 1 ( l存在,创K 7 y `立 p 元素E O I Y n : R K,元素里边有文本,值为T 7 5 ? 6 No message.
:_c('p',[_v("\n No message.\n ")])
]
)
}
})
1.1.3 依靠搜集与监听
这部分是数据呼应式体系
1.调用 observer(),效果y 2 R _ H { r是遍历目标特点进行双向绑定;
2.在 obsI O ^erver 进程中会注册ObjectF ; ( R.defineProperty的 get 办法进行依靠搜集,依靠搜– % h C集是将Wat T E F M X / Bcher 目P R – Q标的实例放入 Dep 中;
3.Object.definee R } v F l * UProperh ( m V – e Fty的 set 会调用Dep 目标的 notify 办法通知它内部一切的 Watcher 目C P } K d ~ _ ~标调用对应的 update()进行视图更新;
4.实质是发布者订阅办法的运E ! o D X % ! V用
1.1.4 diff 和 patch
diff 算法比照差异和调用 update更新视图:
1.patch 的 differ 是将同层的树节点进行比较,经过仅有的 key 进行区分,时刻复杂度只需 O(n);
2.上面将到 set 被触发会调用 watcher 的 update()修正视图;
3.update 办法里边调用 patch()2 _ f D得到同级的 VNode 改动;
4.update 办法里边调用createElm经过虚拟节点创立实在的 DOM 并刺进到它的父节点中;
5.createj $ ~ M 7Elm实质是遍历虚拟 dom,逆向解析成实在 dom;
1.2 React 源码
来张React源码编译进程图
图片来历:React源码解析
1.2.1 Reacu Z X ,t.Component
1.原型上挂载了setState和forceUpdate办法;
2.供给props,cont e i @ w h k = Qext,refs 等特点;
3.组件界说经过 extends 关键字承继 Component;
1.2.2 挂载
1.render 办法调用了Reaw r W e % q ( ? Vct.createElement办法(实践是ReactE! n e a Flement* f , e P y办法);
2.ReactDOM.render(component,mountNode)的办法对自界说组件/原生DOM/字符串进行挂载;
3.调用了内部的ReactMount.render,从而履行ReactMount._renderSubtreeIntoContainer,便是将子DOM刺1 K Y进容器9 w _ r a H [ W;
4.ReactDOM.render()依据传入不同参数会创立四大类v N }组件,回来一个 VNode;
5.四大类组件封装的进程中,调用了mou] / p V p A . , ^ntComponet办法,触发生命周期,解析出 HTML;
1.2.3 组件; i $ @ S h类型和生命周期
1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent组件没有触发生命周期;
2.ReactComps _ Z ? ( oositeComponent类型调用mountComponent办法,会触发生命周期,处理 state 履行componentWillMount钩子,履行 render,获得 html,履m % T $ O ( K G行compoH ~ Y t , X F ]nentDidMounted
1.2.4 data 更新 setState
细B 8 f s d w 节请见 3.1
1.2.5 数) I A *据绑定
1.setState 更新 data 后,shouldCompo[ u , B k 6 KnentUpdate为 true会生成 VNode,为 falsW Z V q t 2 Ve 会结束;
2.VNode会调用 DOM diff,为 true 更新组件;
1.3 比照
React:
1.单向数[ W ] / : : ; h 5据流;
2.setSate 更新data 值后,组件自己处理;
3.differ 是首位是除删在外是固定不动的,然后顺次% D 5 0 R R遍历比照C ^ – t v = U $;
Vue:
1.v-model 能够完成双向数据流,但仅仅v-bind:value 和 v-on:input的语法糖;
2.经过 thiQ ; = L P 5s 改动值,会触发 Object.Q Q + Zdefi– 3 6 n s uneProperty的 set,将依靠放入行列,下一个事情循环开始时履行更新时才会进行必要的DOM更新,是外部监听处理更新;
3.differcompile 阶段的optimize符号了static 点,能够削减 differ 次数,并且是采用双向! k Q s :遍历办法;
2.React 和 Vue 烘托进程差异
2.1 React
1.生成期(挂载):参照 1.2.1
2.更新: 参照1.1.3和 1.1.4
3.卸载:毁掉挂载的组件
2.2 Vue
1.new Vue()初始化后initLifecyck I sle(vm),initEvents(vm),initRendeT F 9r(vm),callHook(vm,beforeCreate),initState(vm),callHook(vm,created);
A.i7 * U f nnitLifecycle, 树立父子组件联系,在当时实例上增加一些特点和生命周期标识。如:chi| % 4 |ldren、refT 1 , : c G V i us、_isMounted等;
B.initEvents,用来存放除@hook:生命周期钩子称号="绑定的函数"事情的目W j A标。如:$on、$emit等;
C.in@ ] h L z r * . fitRender,用于初始化$slotsX y g P、$attrs、$listJ ) m 7 K P !eners;
D.initState,是许多选项初始化的汇H p C总,包k i r Q 0 J b =括:props、methods、data、computed 和 watch 等;
E.callHook(vm,created)后才挂% H e M F载实例
2.compileToFunction:便是将 temj x = 9 z :plate 编译成 render 函数;
3.watcher: 便是履行1.2.3;
4.patc3 y . Q j d T N 3h:便是履行 1.2.4
3.AST 和 VNode 的异同
1.都是 JSON 目标;
2.AST 是HTML,JS,Jal d b * H p m _ Zva或其他言语的语法的映射目标,VNode 仅仅 DOM 的映射目标,AST 范围更广;
3.AST的每层的element,包括自身节点的信息(tag,attr等),一起parent,children分别指j ! A h ; ` ]向其父elem$ # –ent和子element,层层嵌套,形成一棵树
<div id="app">
<ul>
&3 { 3 F <li v-for="item in items">
itemid:{{iteq - i am.id}}
</lV . H Zi>
</ul>
&| 8 - a 6lt;/div>Y j * 9 + * w;
//转化为 AST 格式为
{
"type": 1,
"tag": "div",
"attrsList": [
{
"name": "id# Y ,",
"value": "app"
}
],
"attrsMap": {
"id": "app"
},
"children"h R ; b m S M: [
{
"type": 1,
"tag": "v E R L w ) c u Sul",
"attrsList": [],
"attrsMap": {},
"parent": {
"$ref": "$"
},
"childrenW b W L ,": [
{
"type": 1,
"tag": "li",
// children省略了许多特点,表示格式即可
}
],
"plain": true
}
],
"plain": false,
"attrs": [
{
"name": "id",
"value": "\"app\""
}
]
}
4.vnode便是一系列关键特点如标签名、数据、子节点的A 9 V x M J调集,能够以为是简W v – d化了的dom:
{
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>A - U !;
text: string | void;
elm: Node | void;
ns: string | void;
context:A Z l V t % Component | void;
...
}
5.VNode 的根本分类:EmptyVNode,Tex+ a X ,tVNode,Compoy BnentVNNode,ElementVNNode,CloneVNode
6.创立 VNode
办法一:
// 运用createDocumentFragment()创立虚拟 dom 片段
//& A J a j 节点目标包括dom一切特点和办法
// html
&b w M +lt;ul id="ul"></ul>
// js
con& h 2 1 w vst element = document.getElementById('ul');
const fragment = document.createDocumentFragment();
const browsers = ['Firefox', 'Chrome',A & B B J ! $ 2 'Oper. L : P o 9 9 h Pa', 'Safari', 'Internet Explorer'];
browsers.forEach(function(browser) {
const li = document.cry / qeateElement('li');
li.textContent = browser;
fragme0 d M O Tnt.appendChild(li);  // 此处往文档片段刺进子节点,不会引起回流 (适当于打包C c e ` t g z操作)
});
console.log(fragment)
element.appendChild~ T 9 C(fragment);  //^ 9 a 将打包好的文档片段刺进ul节点,只做了一次操作,时刻快,性能好
办法二:
// 用 JS 目标来模拟 VNode
function Element (tagName, props, childrC b 5 n q . & ? uen) {
console.log('this',this)
this/ J j.tagName = tagName
this.props = props
this.children = children
}
let ElementO =new Element('ul', {id: 'list'}, [
new Element('E j F ^ i J li', {class:3 X [ q n 3 t 'item'}, ['Item 1']),
n2 S ? v d ,ew Element('li', {class: 'item'}, ['Item 2']),
new Element('li', {class: 'item'}, ['Item 3H U F O n + . )'])
])
// 运用 render 烘托到页面
Elemena Q ! p a _ wt.prototype.render = function () {
const e, [ ] Q ( ^ j @ _l = document.createElement(this.tagName) // 依据tc $ F 5 v +agName构建
const props = this.props
for (const pro; q I & s l YpName in props) { // 设置节点的DOM特点
cons z D 4 ~ C ] 1 yt propValue = props[propName]
el.setAttribute(propName, propVa6 m Hlue)
}( { O r p ] E ! {
const children = this.cE S . D e P + ; Whildren || []
children.forEach(functionv B ! y 0 Z K I (child) {
const childEl = (child instanceof ElJ ~ 0 ? Mement)
? child.render() // 假如子节点也是虚拟DOM,递归构建DOM节点
: dob g N H l 1 V h xcument.createTextNode(child) // 假如字符串,只构建文本节点
el.appendChild(childEl)
})
return el
}
console.log('ElementO',ElementO)
var ulRoot = ElementO.render()
console.log('ulRoot',ulRoot)
document.body.aS [ f s [ppendChiu v ] 9 X ~ } N 4ld(ulRoot)
4.React 和Vue 的 differ 算法区
4.1 React
1.ViI + ; ! 4 c d |rtual DOM 中的首个节点不履行移动操作(除非它要被移除),以该节点为原点,其它节点都去寻Z ) 5 e找自己的新方位; 一句话= ` % % S {便是首位是老迈,不移动;
2.在 Virtual DOM 的次序中,每一个节点与前一个节点的先后次序与在 Real DOM 中的次序进行比较,假如次序相同,则不必移动,否则x 8 m 1就移动到前一个节点的前面或后面;
3.tree diff:~ = c p只会同级比较,假如是跨级的移动,会先删去节点 A,再创立对应的 A;将 O(n3) 复杂度的问题转换成 O(n) 复杂度^ l – c s =;
4.component diff:
依据batchingStrategy.isBatchingUpdl ^ c l z ates值是否为 true;
假如true 同G ^ F w一类型组件,按照 tree diffe| 3 n ! {r 比照;
假如 false将组件放入 dirtyCompoG 0 O _ Q Jnent,下体面节点全部替换,具体逻辑看 3.1 setSate
5.element differN m 5 ! [:
tree differ 下面有三种节点操作:INSERT_MARKUP(刺进F G : w 2 o 5 & #)、MOVE_EXISTING(移动)和 REMOVE_NODE(删去)
请戳
6K g n.代码完成
_updateChildren: function(nb Y AextNestedChildrenElements, transaction, context) {
var prevChildren = this._renderedChildren
var removedNodes = {}
vara A $ x J # + mountImages = []
// 获取新的子元素数组
var nextChildren = this._reconcilerUpdateChildren(
prevChildren,
nextNestedChildrenElements,
mountImages,
removedNodes. 8 x q ^ ; l,
transaction,
context
)
if (!nextChildren && !prevChildren) {
return
}
var updates = null
var name
var nextIndex = 0
var lastIndex = 0
var nextMountIndex = 0
var lastPlacedNods U # + s H ve = null
for (name in nex0 G N C F o ~ c 4tChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue
}
var prevChild = prevChildren && prevChildren[nameU B L K f]
var nextChild =r y : : H Z Y nextChildren[name]
if (prevChild === nextChiH ? ,ld) {
// 同一个引证,说明是运用的同一个co/ ] c % 8 1mponent,所以我们需求做移动的[ Y v _操作
// 移动已有的子节点
// NOTICE:这里依据nextIndex, lastInde0 u k P ) + _ t ex决议是否移动
updates = enqueue(
ut ^ 3 A ) _ $ H Tpdates,
this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
)
// 更新lastIndex
ln N 4 # C mastIndex = Math.max(prevChild._mountIndex, lastIndex)
// 更新component的.mountIndex特点
prevChild._mountInd| L G & _ - R / #ex = nextIndex
} el^ ^ l w q , z G +se {
if (prevChild) {
// 更新lastIndex
lastIndex = Math.max(prevChi6 s ~ qld._mountIndex, lastIndex)
}
// 增加新的子节点在指定的方d Y a : 3 o F位上
updates = enqueue(
updates,
this._mountChildAtIndex(
nextChild,
mountImages[nextMountIndex],
lastPlacedNode,
nextInM Q d }dex,
transaction,
contex! ? ct
)
)
nextMountIndr ^ E x 0 8 fex++
}
// 更新nextIndex
nextInQ , g I Mdex++
lastPlacedNode = ReactReconciler.getHostNode(nextChild)
}
// 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点
for (name in removedNodes) {7 ) % I 4 ; Q
if (removedNodes.hasOwnProperty(- a 2 = M Mname)[ ] P 7 ~ I)w 1 8 0 ` 7 k {
updates = enqueue(
updates,
this._unmountChild(prevChildren[name], removedNodes[name])
)
}
}
}
4.2 Vue
1.自主研发了一套Virtual DOM,是借鉴开源库snabbdom,
snabbdom地址
2.也是同级比较,因为在 compile 阶段的optimize符号了static 点,能够削减 differ 次数;
3.Vue 的这个 DOM Diff 进程便是一个查找排序的进程,遍历 Virtual DOM 的节点,在 Real DOM 中找l V t到对应的节点,并移动到新的方位上。不过这套算法运用了双A H 2 C ~向遍历的办法,加快了遍历的速度,更多请戳;
4.代码完成:
updateChildren (parentE Q @ 8 Ylm, oldCh, newCh) {
let oldStam 2 Y %rtIdx = 0, newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStart[ . G zVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
whia w n l s &le (. L j uoldStartIdx <= oldEndIdx &D n ` K g O;& newStartIdx <= newEndIdx) {
if (oldStartVnode == null) { //关于vnode.key的比较,会把oldVnode = null
oldStartVnode = oldCh[++oldStartIdx]
}else if (oldEndVnodeF l D 7 == null) {
os i X Q ; [ $ V lldEu ) % [ndVnode = oldCh[--oldEndIdx]
}else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
}else if (newEndVnm c P 9ode == null) {
newEndVnode = newCh[--newEndIdx]
}else if (samh I _ keVnode(oldStartVnode, newStartVnode)) {
patcI 7 S O % ^ D u chVnodez - @ T B _(oldStartVnode, neH / m o 8 [ K c 9wStartVnode)
oldStartO v , @ q F { Vnode = oldCh[++old7 s WStartIdx]
ni l * L @ewStartVnx A p ! Mode = newCh[++newStartIdx]
}else if (sameVno} v e M v Mde(oldEndVnode, newEndVnx w l 2 .ode)) {
patchVnode(oldEndVnode, newEndVnode)
oC } | , S 1 2ldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(ol/ & P * ]dStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStaV [ G I d MrtVnode.el, api.nextSibling(olW I # Q S b K 9 TdEndVnode.el)D _ S S - ])
oldStartVnode = oldCh[++oldStartIdx]
newEndVnodeW Z q U = newCh[--newEndIdx]
}el% 6 /seT D x + j z h ? if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode)
api.insertBefore(paM 6 = C LrentElm, oldE= B 9 n mndVnode.el, oldStartVnode.el)
oldEndVnod& g ` ( a Ee = oldCP x 1 # 8h[--oldEndIdx]
newStartV? P 6node = newCh[++newStartIdx]
}else {
// 运用key时的比较
if (oldKeyToIdx === undefined) {
oldKeyToIdV 9 o + ] ) W 4x = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
}
idxInOld = oldKeyToIdx[newStartVnode.key]
if (!idxInOld) {
api.insertBefore(p[ E : i 3 w %arentElm, createEle(newStartVnode).el, oldStartVnode.el)
newStartVnode = newCh[++newStartIdx]
}
else {
elmToMov, N + V X x ]e =K P p * oldCh[idxInOld]
if (elmToMove.sely ( J 6 N !== newStartVnode.sel) {
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode7 ! c | 2 E n c K.el)
}else {
patchVnode(elmToMove, newStartVW P Q d _ ^ % Inode)
oldCh[idxIs m n + 2nOld] = null
api.insertBefore g X c !(parentElm, elmToMove.el, oldStartVnode.el)
}
newStaU C $ f -rtVnode = newCh[++newStR % g @artIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
before` $ & k ` = newCh[newEndIdx + 1] ==& X e B [ null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
removeVnodZ S H - !es(parentElm,U u N . oldCh, oldStartIdx, oldEndIdx)
}
}
4.3 比照
相同点:
都是同层 differ,复杂度都为 O(n);
不同点:
1.React 首位是除删在外是固定不动的,然后顺次遍4 S t G W 2 ^历比照;
2.Vv n @ g , W 7 bue 的compile 阶段的optimize符号了static 点,能够削减 differ 次数,并且是采用双向遍历办法;
5.React 的 setState和 Vue 改动值的差异
5.1 setState
1.setState 经过一个行列机制来完成 state 更新,当履行 setState() 时,会将需求更新的 state 浅兼并后,依据变量 isBatchingUpdates(默以为 false)判别是直接更新还是放入状况行列;
2z G x.经过js的事情绑定程序 addEventListener 和运用setTimeout/setInterval 等 React 无法掌控的c m e API状况下isBatchingUpdates 为 false,同步更新。除了这几种状况外batch; z ) 4 M p D g ~edUpdates函0 : k . k , A ?数将isBatchingUpdates修正为 true;
3.放U H 7 2入行列的不会立即更新 state,行列机制能够高效的批量更新 state。而假如不经过setState,直接修正this.state 的值,则不会放入状况行列;
4.setState 顺次直接设置 state 值会被兼并,可是传入 function 不会被兼并;
让setSta] t A 8 / * P e Nte承受一P 9 4 D % q H f个函数的API的设计是适当棒的!不只契合函数式编程的思维,让开发者写出没有副效果的函数,并且我们并不去修正组件状况,仅仅把要改动的状况和成果回来给React,保护P J ] p x v ; U状况的D ( = B n活完全交给React去做。正是把流程的操控权交给了Re@ ` w Q 0af O F e I g O Ect,所以React才能和谐多个setState调用的联系
// 状况一
state={
count:0
}
handleClick() {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
couv x { j Mnt: this.state.count + 1
})
}
// count 值仍旧为1
// 状况二
increment(state, props) {
return {
count: state.count + 1
}
}
handleClick() {
this.setState(this.incremen7 i ) :t)
this.setState(this.increment)
this.setState(this.increment)
}
// count 值为 3
5.T / = W更新后履行四个钩子:shouleComponentUpdP e G * J Date,componentWillUpdate,render,componentv ~DidUpdate
5.2I T 5 ! Vue 的 this 改动
1.vue 自身保护 一个 更新行列,当你设置 this.a = ‘new value’,DOM 并不会马上更新;; , 6 Z E
2.在更新 DOM 时是异步履行的。只需侦听到数据改 . B { % [ 2 W动,Vue 将敞开一个行列,并缓冲在同一事情循环中发生的一切数据改动;
3.假如同一个 watcher 被屡次触发,只会被推入F w s到行列中一次;
4.也便是下一个事情循环开始时履行更新时才会进行必要的DOM更新和去重;
5.所% 1 Z , $ & @ / 6以 for7 * D { W y @ 2 i 循环 10000次 this.a =, l 0 H 5 i vue只会更新一次,而h _ l不会更新10000次;
6.data 改动后假如 computed 或/ I | ; } 4 watch 监听则会履行;
6. Vue的v-for 或 React 的map 中为什t , _么不要用? [ i F v k z index作为 key
6.1a q l 为什么要加 key
6.1.1 React
1.上面的 5.1 讲到 React 的 diC ^ w ] 8 – Yffer 中 element differ 有三种节点操作;
2.场景一不加 key:
新老调集进行 diff 差异化比照,发现 B != A,则# . W C ? W W ~ 5创立并刺进 B 至新调集,删去老调集 A;( w M p [ { p以此类推,创立并刺进 A、D 和 C,删去 B、C 和 D;
都是相同的节点,但由于方T c I h J f位发生改动,导致需求进行繁杂低效g Q w 4 T 2 0 i 的删去、创立操作,其实只需对这些节点进行方位移动即H W n B j + G 2可;
3.场景二加 key:
新建:从新调集中取得 E,判别老调集中不存在相同节点 E,则创立新节点 ElastIndex不做处理E的方位更新为新调集中的方位,nextIndel & rx++;
删去:当完成新调集中一切节点 diff 时,最终还需求对老调集进行循环遍历,判别是否存在新调集中没有但老调集中仍存在的节点,发现存在这样的节 C ] Q { f点 D,因而删去节点 D;
4.总结:
显然加了 key 后操作步骤要少许多,性能更好;
可是都会存在一个问题,上面场景二只需求移动首位,方位就可对应,可是由于首位是老迈不能动,所以应该尽量削减将最终一个节点移动到首位,更多请戳。
6.1.2 Vue
Vue 不加 key 场景分析:
1.场景一不加 key:
也会将运用了双向遍历的办法查* 4 b @ L f 找,发现 A,B,C,D都不等,先删去再创立;
2.场景二加 key:双向遍历的办法查– w 8 v ( W 3找只需求P A X x F M X o !创立E,删去D,改动 B、C、A的方位
6.2 为什么 key 不能为n U } F & ind| # s * J G i V nex
这个问题分为两个方面:
1.假如列表是纯静态展示,不会 CRUD,这样用 i5 @ t l w K ` ( sndex 作为 key 没得啥问题;
2.假如不是
const list = [1,2,3,4];
// list 删去 4 不会有问题,可是假如删去了非 4 就会有问题
// 假如删去 2
const listN= [1,3,4n S u . a ] } @]
// 这样index对应的值就改动了,整个 list 会重新烘托
3.所以 list 最好不要用 index 作为 key
7. Redux和 Vuex 设计思维
7.1 Redux
API:
1.Redux则是一个纯粹的状况办理体系,React运用React-Redux将它与React框架结合起来;
2.只需一个用createStorE Y be办法创立一个 store;
3.action接纳) ! – y P m view 宣布的通知,告知 Store State 要改动,有一个 tF G b O f pype 特点;
4.reducer:纯函数来处理事情,纯函数指一个函数的回来成果只依靠于它的参数,并且在j u p b m `履w = ) f g p – ^行进程里边没有副效果,得到一个新的 state;u 1 O 5 B m I
源Z s ? G = : ) ` #码组成:
1.createStore 创立库房,承受reducer作为参数
2.bindActionCreator 绑定store.dx 0 :ispatch和action 的联系
3.com= ^ i FbineReducers 兼并多个reducer; h V A 2 s
4.applyMiddlewah 1 Z J C 4 g # /re 洋葱模型的中间件,介于dispatch和actiJ + Son之间,重写dispatch
5.compose 整合多个中间件
6.单一数据流;state 是可读的,有必要经过 action 改动;reducer设计成纯函数;
7.2 Vuex
1.VuD j T S : (ex是吸收了Rev K M B = Ndux的经历,抛弃( L ; s e j ~ l !了一些特性并做了一些优化,价值便是VUEX只能和VUE合作;
2.store:经过 new Vuex.store创立 store,辅佐函数mapState;
3.getters:获取state,有辅佐函数 mapGetters;
4.~ w d /action:异步改动 state,像ajax,辅佐函数mapActions;
5.mutation:同步m f l | x . ) ~ =改动 state,辅佐函数mapMutations;
7.3 比照
1.ReduxH 1 | 1 m B /:C q y 4 q ; y view——>actions——>reducN A 5 ; Per——>state改g S A M 0动——>view改动(同步异步一样)
2.Vuex: view——>commit——>mutations——>state改动——>view改动(同步操作)
view——>d8 ? = u rispatch——>actions——` } 8 j d>mutations——>state改S + y动——P 3>view改动(异步操作)
8.ra A ) e W Q ] 2 0edux 为什么要把 reducer 设计成纯函数5 | ? 4 4 t B 0
1.纯函数概念:一个函数的回来成果只依靠于它的参数(外面的变量不会改动自己),并且在履行进程里边没有副效果(自己不会改动外面的变量);
2.首要便是为了5 – . I ) F n V减小副效果,防止影响 state 值,形成过错的烘托;
3.把reducer设计成纯函数,便于调试追寻改动记载;
9.Vuex的mutation和Redux的reducer中为什么不能做异步操作
1.在 vuex 里边 acN P H – Ktions 仅仅一个架构性的概念,并不是有必要的,说到底仅仅一个函数,你在里边想干嘛都能够,只需最终触发 mutation 就行;
2.vuex 真正限制你的只需 mutation 有必要是同步的这一点(在 reda * g j S . Z { lux 里边就好像 reducer 有必要同步回来下一个状况一样O N . `);
3.每一个 mutation 履行完成后都能g 8 V h y u够对应到一个新的状况(和 reducer 一样){ q h 0 D z d,这样 devtoz s G Iols 就能够打个 snapshot 存下来,然后就能够随意 time-travel 了。假如你. q J开着 devtool 调用一个异步的 action,你能够清楚地看到它所调用的 mutation 是何时被记载下来的,并且能够立刻查看它们对应的状况;
4.其实便是框架是这么设计的,便于调试追寻改动记载
10.K . 5 b l 4 W双向绑定和 vuex 是否冲突
1.在严格办法中运用Vuex,当用户输入时,v-model会试图直接修正特点值,但这个修正不是在mutation中修正的,所以会抛出一个过错;
2.当需求在组件中运用vuex中的s` { X W – Ftate时,有2种处理计划:
在input中绑定~ V g 7 ` ) {value(vuex中的state),然后监听input的change或者input事情,在事情回调中调用mutt o r R vati9 x qon修正state的值;
// 双向绑定核算特点
<input v-model="message">
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
11. Vue的nextTiB 8 $ o # : { ?ck原理
11.1 运用场景
什么时分会用到?
nextTick的运用原则首要便是处理单一事情更新数据后立即操作dom的场景。
11.2 原理
1.vue 用异步行列的办法来操控 DOM 更新和 nextTick 回调先后履行;
2.microtask 因为其高优先级特性,能确保行列中的微使命在一次事情循环前被履行结束;
3.考虑兼容问题,vue 做了 mi` v # R Z % ! =crotasH ] + Z o !k 向 macrotask 的降L N v h h N E + o级计划;
4.代码完成:
const simpleNextTick = function queueNextTick (cb) {
return Promise.r m @ $ @ P /esoly e 1 k f tve().then(() => {
cb()
})
}
simpleNextTick(() => {
console.log(this.$refs.test.innerText)
})
1R Q [ a A3. Vue 的data 有必要是函数而 React 的 state 是目标
13.1 Vue 的 data 有必要是函数
目标是引证类型,内存是存贮引证地址,那么子组件中的 data 特点值会互相污染,发生副效果;
假如是函数,函数的{}构成效果域,每个实例相互独立,不会相互影响;
13.2 React 的 state 是目标
因为 stV 6 M h ^ate 是界说在函数里边,效果域现已独立
14B ? o.Vue 的兼并战略
1.生命周期钩子:兼并为数o ` z ) $ [组
function mergeHook (
parentVal,
childVal
) {
return childVal
? paK 0 L @ - X / ]re. X O VntVal // 假如 childVal存在
? parentVa6 H u v 1 J Y 1l.concat(childVal) // 假如parentVal存在,直接兼并
: Array.isArray(childVal) // 假如paren6 R -tVal# ( 2 ` : v x不存在
? childVal // 假如chip 4 D p 1lidVal是数组,直接回来
: [childVal] // 包装成一个数组回来
: parentVal // 假如childVal 不存在 直接回来parentVal
}
// strats中增加特点,特点名为生命5 w L { l F周期各个钩子
config._lifecycleHooks.forEach(function (hooB | a Uk) {
strats[hook] = mergeHook // 设置每一个钩子函数的兼并战略
})
2.watch:兼A & ; . .并为数组,履行有先后次序;
3.assets(components、filters、directives):兼并为原型$ – X链式结构,兼并s c o P 8的战略便是回来一个兼并后的o H h t o .新目标,新目标的自有特点全部来自 childVal, 可是经过原型链托付在了 parentVal 上
function mergeAssets (parentVal, chin ( zldVal) { // parentVal: Object childVal: Object
var res = Object.create(pJ r k a * parentVal || null) // 原型托付
return childV6 Z (al
? extendi ^ l(res, childVal)
: res
}
con@ V W 3 F ( M @fig._assetTypes.forEach(function (type) {
strats[type + 's'] = mergeA? a ,ssets
})
4.data为function,需求兼并履行h _ 2 ]后的成果,便是履行 parentVal 和 childVal 的函数,然后再兼并函数回来的目标;
5.自界说兼并战略:
Vue.config.optionMergeStrategies.watch = function (toVal, fromVal) {
// return mergedVal
}
14.Vue-router 的路由办法
1.三种:”hasE Q P $ $ g g O Ah” | “histo& d U J K ] +ry” | “) w % p l i }abstract”;
2.hash(默许),history 是浏览器环境,abstract是 node 环境;
3.hy ^ ; b kash: 运用 URL hash 值来作路由,是运用哈希值完成push、replace、go 等办法;
4.history:依靠O d O ` v X s HTML5 History API新增的 pushState() 和 replaceState(E V ^ 8),需求服务器装备;
5.abstract:假如发现没有浏览器的 API,L u * k 6 [路由会主动强制进入这个办法。
15.Vue 的事情机制
class Vue {
constructor() {
// 事情通道调度中心
this._events = Object.create(nG R 7 h s . kull);
}
$on(event, fn) {
if (Array.isArray(event)) {
event.map(ite^ N b @ U cm => {
this.$on(item, fn);
});
} else {
(f ~ y - ; G G lthis._events[event] || (this._events[event] = [])).push(fn); }
return this;
}
$once(event, fn)y w Y ] {
function on() {
this.$off(event, on);
fn.apply(this, arguments);
}
on. & P | o S H $.fn = fn;
this.$on(event, on);
return this;q j M b Y 5
}
$off(event, fn) {
if (!arguments.length) {
this._events = Object.create(null);
return this;
}
if (Array.isArray(event)) {
event.map(ite; u a X 5 @m => {
this.$off(item, fn);
});
return this;
}
const cbs = this._events[event];
if] o ) (!cbs) {
return tm E ,hia I C fs;
}
if (!fn) {
this._even# ; # / t F : C wts[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);
brea3 # Y :k;
}
}
return this;
}
$emit(event) {
let cbs6 g 6 S F , o W ^ = 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 的完成原理和缓存战略
1.获取keep) ^ 4 b v G + z-alive第一个子组件;
2.依据includ? n I w _ H 4 je exclude名单进行匹配,决议是否缓存。假如不匹配,直接回来组件实例,假如匹配,到第3步t y b F;
3.依据组件id和tag生成缓存组件的key,再去判别cache中是否存在这个key,即是否射中缓存,假如射中,用缓存中的实例代替vnode实例,然后更新key在keys中的方位,(LRU置换战略)。假如没有射中,就缓存下来,假如超出缓存最大数量@ ` V d Umax,删去cache中的第一8 l S Q O @项。
4.kJ g (eep-alive是一个笼统组件:它自身不会烘托一个 DOM 元素,也不会出现在父组件链中;
5.LRU算法:依据数据的前史拜访记载来进行筛选数据,其实便是拜访过的,今后拜访概率会高;
6.LRU 完成:
新数据刺进到链表头部;
每逢缓存射中(即缓存数据被拜访),则将1 | t f 9 m 0数据移到链表头部;
当链表满的时分,将链表尾部的数据丢掉。
17.Vue 的 set 原理
1.由于 Object.observe()办法抛弃了,所以Vue 无法检测到目标特点的增加或删去;
2.原理完成:
判别是否是数组,是运用 splice 处理值;
判别是否是目标的特点,直接赋值;
不是数组,且不是目标特点,创立一个新特点,不是呼应数据直接! . o c n W ` ~ &赋值,是呼应数据调用defineReactive;
export function set (target: Array<any> | Object, key: any, val: any): any {
// 假如 set 函数的第一个参数是 undefined 或 null 或者是原始类型值,那么在非生产环境~ P y p P { O下会打印警告信息
// 这个api原本便是给目标与数组运用的
if (pU = 8 , 9 X urocess.env.NODE_ENV !== '] Y ;production' &&
(isUndef( ~ dtarget) || isPH y @rimitive(target))
) {
warn(`Cannot set reactive property on undex w I f | y |fined, null, or primitive value: ${(tf } m % J 1 0 5 Larget: any)}`)
}
if (Array.isArray(target) && c ; G isValidArrayIndex(key)) {
// 类似$vm.set(vm.$data.arr, 0, 3)
// 修正数组的长度, 防止索引>数组M J b ~ | D ^ D长度导致splcie()履行) W Z d , s d有误
target.length = Math.max(target.leng& ! / th, key)
// 运用数组的splice变异办法触发呼应式, 这个前面讲过
target.splice(key, 1, val)
retur* s G r j J 0 Fn val
}
// target为目标, key在tar, ] z 3get或者target.prototype上。
//K T p - s % 一起有必要不能在 Object.prototype 上
// 直接v I S w修正即可, 有c X = s 9 s *兴趣能够看issue: https://github.com/vuejt r A e { A %s/vue/issues/6845
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
//` & 0 M q z 6 ) 以上都不建立, 即开始1 * * f给target创立一个全新的特点
// 获取Observer实例
const ob = (target: any).__ob__
// Vue 实例目标具有 _isVue 特点, 即不允许给Vue 实例目标增加特点
// 也不允许Vue.set/$set 函数g F Z y o ~为根数据目标(vm.$data)增加特点
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'p1 k & # [ v Lroduction' && warn(
'Avoid adding reactive properties to a Vue instance or its root $datD y x d !a ' +
'at runtime - declare it upfront in t( k .he data option.'
)
return val
}
// target自身就不是呼应式7 Q s m Z数据, 直接赋值
if (!ob) {
target[key] = val
return val
}
// 进行呼应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
https://juejin.im/post/5e04411f6fb9a0166049a073#heading-18
18.简写 Redux
function createSt^ C r P - Qore(reducer) {
let state;
let listeners=[];
function getState() {
return state;
}
functiony X o X = w dispatch(action) {
state=reducer(state,action);
listene- 8 q q F ;rs.forEach(l=>l());
}
function subscribe(listenerv } T - r V H 0 ;) {
listeners.push(listener);
return function () {
const index] 8 a=listeners.indexOf(listener);
listeners.splio g N V P #ce(inddx,1);
}W b 8
}
dispatch({});
return {
getState,
dispatch,
subscribe
}
}
19 react-redux是如何来完成的
源码组成:
1.connect 将store和dispatch分别映射成props特点目标,回来组件
2.context 上下文 导出Provider,,和 consumeG ^ Yr
3.Provider 一个承受store的组件,经过context api传递给一切子组件
20. react16 的 fiber 了解
1.react 能够分为 di{ J q V Y 4 b L Kffer 阶段和 commB o it(操作 dom)阶段;
2.vB ` 6 ` /16 之前是向下递归算法,会阻塞;
3.v16 引入了代号为 fiber 的异步烘托架构;
4.fiber 中心完成了一个基于优先级和requestIdleCallback循环使命调度算法;
5.算s $ _ x k h R j法能够把使命拆分成小使命,能够随时终止和康复使命,能够依据优先级不同操控履行次序,更多请戳;
总结
文章源码:请戳,原创x r e码字不_ z X ! C易,欢迎star!
您的鼓励是我继续创造的动力!