守着b站看完直播

感慨良多,为什么存在头发多,才能还这么强的男人呢

整场下来,讲了许多
比如 重写了virtual dom,然后编译模板有了很大的优化
组件初始化会更加有效率C { m 5 : u #, 比( Q d F } B . k Y照 vue2 有1.3到2倍的功能提高& 8 q
然后服务器烘托来说更是有2到3倍的的提高,等等

其间我对重写的 virtual dom 和 模板编译 的优化觉得很猎奇
于是细究了一下

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节

1.静态node优化

首要是关于静态节点的优化
举个比如

<div>
&L t V y . v q _lt;span>hello</span>
<span>{{msg}}</span>
</div>

vue2生成的render函数

var ren1 $ o hder = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div",Y p P 6 [
_c("span* / }", [_vm._v("hello")]),
_c("span", [_vm._v(_vm._s(_vm.msg))])
])
}

vue3生成的render函数

import { createVNode as _createVNh q ^ode, toDisplayString as _toDisplayString, openBT L 7 & G t N 4 ilock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _cw h Z  w 8 ,reateBlock("div", null, [
_createVNode("span", nulQ ( B O U 4 Q 8l, "hello"),
_createVNodep | * 2 ; o p("span", null, _toH w l 1 D [ x N mDispla) w g A M %yString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST

vue2 生成的 vnode

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节

vue3 生成的 vnode
留意我符号1 I N n ] r 7 u [的,dynamicChildren

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节

很明显,vue3 符号了dynamicChildren 动态节点
接下来patch阶段,只会比较动态节点,静态的直接略过了
而vue2中,仍是会patch一切X E V T ? 3子节点去比对改变

2.节点改变类型细分

再继续接着上面的比如
假如咱们模板再改一下,加上 id pr/ D ) 6 ,ops

<div>
<span3 L / ^ i [ x ` g>hel[ M L ~ 8lo</s- / Y w r 4 C n Ipan>
<span :id="hello" attr="test">{{msg}}</span>
<V * C ] D Y x/div>

vue3生成的render函数如下

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _d ( X ? +createBb 9 wlock } from "vue"
export function render(_A { Y l actx, _cache) {
return7 k P P @  p (_openBlock(), _creatg z d % n 4 |eBlC z d ! t u v ) 0ock("div", null, [
_createVNode("span", null, "hello"),
_createVNode("span",6 c % w = 4 {
id: _ctx.hello,
attr: "test"
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["id"])
]I E y .))
}
// Check the console for the AST

vue3生成的vC D : | U ^ Jnode如下

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节

留意这几个特点
dynaA j ~micProps patchFlag
这儿直接符号出来了动态的特点称号,只要 id 是需求动态比} S + V j I I照的
并且patchFlag是 9 ,咱们来看看

export const en ` m M o um PatchF) c ,lags {
TEXT =C O g & h u C Q 2 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,3 k ( H k
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 &ly $ p Q B &t;< 7,
UNKEYED_FRAGMENT =4 c K V r j j # | 1 << 8,
NEED_PATx & 1 - d 0 W ? OCH = 1 << 9,
DYNAMIC_SLOTS = 1 <&z o ; @ 9 ?  ? .lt; 10,
HOISTED = -1,
BAIL = -2
}

96 6 z y代表的 是 TEXT(1) | PROPS(8)# : w g Y z 7 z y,便是文字和特y u U ! C | X点都有修正

这儿的枚举用的位掩码,有疑问的同学也能够搜下

然后咱们去p; ; @ . i uatch函数看看

// vuq $ | E ] d f ke3 的patch
const patchElement = (n1, n2, parentCompT Y b T [ & | onent, parentSuspense, isSVG, optimized) => {F ( } t R ^
const el = (n2.el = n1.el)b 0 c y 6 L % ];
let { patchFlag, dynamicChildren, dirs } = n2;
const oldPropsj ~ ) * ? = (n1 && n1.props) || EMPTY_OBJ;
const newPro4 I S / {ps = n2.props || EMPTY_OBJ;
let vnT 8 + % Y x # M IodeHook;
if ((vnoz W  : J -deHook = newProps.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHookY 9 6 q I T W 4 *, parentComponent, np M y ) m (2, n1);
}
if (dirs) {
invokeDirectiveHook(n2, n1, pap G H Q k MrentComponent, 'beforeUpdate');
}
if (} { x n b__HMR__ && parent3 d 2 P YComponent && parentComponent.renderUpdated) {
// HMR updated, force full diff
patc; J P 3 ~ ~ 7 Y &hFlag = 0;
optimized = false;
dynap j _ y } 9micChildren = null;
}
if (patchFlx E P aag > 0) {
// theA ! e { z g 4 ~ presence of a+ * $ w k , patchFlag means this element's render codX [ , Z Le was
// generated by the compiler and can take the fast path.
// in this path old ny z !ode and new node are gua] l y # } z { 1 Wranteed to have the same shape
// (i.e. at the exact same position in the source template)
if (patchFlag & 16 /*g V c X FULL_PROPS */) {
// elemb t }en $ Wt props contain dyna? L 6 ^ | G S 0mic keys, full diffp ! b { _ q needed
patchProps(el, n2, oldF U W hProps, newProps, parentComponent, parentSuspense, isSVG);
}
else {
// class
// this flag is matched when the element has dynamic class bindings.
if (patchFlag & 2 /M i v 0 s 1 c* CLASS */) {
if (oldProps.class !== neW = g 0 t +wProps.class) {
hostPatchProp(el, 'I 0 F Qclass', null, newProps.class, isSVG);
}
}
// style
// this flag is matched when the element, % x M has dynamic style bV  7 t S H Mindings
if (patchFlag & 4 /* STYLE */) {
hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG);
}
// propsT j K . D m ; z
// This flag is matched when the element has dynp ? # E ( b q =amic prop/attr bindings
// other than cU C xlass and style. The keys y ~ N x ^ | of dynamic prI U 2op/attrs are saved for
// faster iteration.
// Note dynamic keys like :[foo]="bar" will cause this* z W R # T ( @ optimization to
// bail out and go through a full diff because we need to unset the old key
if (patchF E 4lag & 8 /* PROPS */) {
// if the flag is present then dynamicPropE  l & 4 b # [ Qs must be non-null
const propsToUpdate = n2.dy O ) , b ) [ | ]namicProps;
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i];
coX 1 t H z D .nst prev = oldProps[key];
const next = newProps[key];
if (prev !== next) {
hostPatchProp(el, key, prev, next, isSVG, n1.children, parentComponent,e } j M @ ) 4 parentSuspense? X } e J z j 3, unmou H ) O m ? @untChildren);
}
}
}
}
// text
// This flag is matched when the elemen6 r - qt has only dynamic text chil7 C t ;dren.
if (patchFlag & 1 /* TE k 7 : oXT */) {
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children);
}
}
}
..L s ~ v B a M $ /.
};

这儿能够看到方法内有许多依据特定Q Y % J的 patchFlag 去履行特定的操作

比照下 vue2 中 patchVnode 阶段
假如是w A 9 $ l普通节点,会通过内置的updX O 1 c M v Cate钩子全量进行新旧比照,然后更新

// vue2内置的钩子,这些module有update钩子的,都会全量履行
var baseModules = [
ref,
directives
];
var platformModules = [
attrs,
klass,
events,
domPro9 S 6 # w zps: H A I M _ s,
style,
tA ! Q V k q %ransition
];

假如是component,则会在prepab # X 9 h f 3tch阶段进行判别,有变化则会从头触发forceUpdate

显然 vue2 中有许多重复的无用比照

  // vue2 的 patch
function paT 8 q F htchVnode (oo g Z L - , 7 : 5ldVnode, vnode, insertedVnodeQueue, removeOnly) {
...
if (isDef(da1 4 , M H B 8 X xta) && isDef(i = data.hook) && isDef(i =7 w U p | p 4 l i.prepL = T l u . latch)) {
i(oldVnode, vnode);
}
var oldCh = olF  9 v J K V | qdVnode.children;
var ch = vnode.children;
if (isDef(data) && isP] * o tatchable(vnode)) {
for (? s M O S  A Z hi =, P d o P h V 0; i < cc j X M ~bs~ C } $ U b G.update.length; ++i) { cbs.update[i](ol w {dVnode, vG - , ) _ Y p O 9node); }
if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVj / xnode, vnode); }
}
...
}

3.diff优化

再来便是diff算法的: g U _ _ U i优化,尤大佬已注释的很详细……
下面咱们也来用一组数据做比如

    // old arr
["a", "b"W 5 D P, "c", "d", "e", "f", "g", "h"]
// new arr
[Y : P"a", & 1 o"b", "d", "f", "c", "e", "x",! & 2 7 C ` V "y", "g", "h"]

其实看注释前四步都好了解
第1步:自始至终开端比q + b A ~ i较,z @ 8 t[a,b]是sameVnode,进入patch,Q N m { 0 E到 [c] 停止;
第2步:从尾到Q 1 k k头开端比较,[h,g]是sameVnode,进入patch,到 [f] 停止;
第3步:判别旧数据是否现已比较结束,剩下的阐明是新增的,需求mount(本例中没有)
第4步:判别新数据是否现已比+ o E较结束,剩下i ` z 7 ! j + 3的阐明是删去的,S F j / 2需求unmount(本例中没有)M V , T ~ h 5 & O

第5步:进入到这儿,阐明是乱序了,这一步就开端稍显复杂
5.1 首要建一个还未比较的新数据index的Map,keyToNewIndexMap[d:2,f:3,c:4,e:5,x:6,y:7]
5.2

  • 依据未比较完的数据长度,建一个填充 0 的数组 [0,0,0,0,0]
  • 然后循环一遍剩下数据,找到未比较的数据的索引newIndexToOldIndexMap[4(d),6(f),3(c),5(e),0,j . 4 s n & – +0]
  • 假如没有在剩下数据里找到,阐明是删去的,unmount掉
  • 找到了,和之前G # w的patch一下

5.3 其实到这一步,Q k U _现已很好办了,从尾到头循环一下newIndexToOldIndexMap
是 0 的,阐明是新增的数据,就 mY o j 2 & 6ount 进去
非 0 的,阐明在旧数据里,咱们只要把它们移动到对应index的前面就行了

如下:

  • 把 c 移动( , #到 e 之前
  • 把 f 移动到 c 之前
  • 把 d 移动到 f 之前

可是心细的小明同学现已发现了,c 移动到 e 之前$ Y 9是剩下的
因为等 f 和 d 都移动之后,c 天然就到 e 之前了
所以,vue3中还做了一件事情,依据newIndexToOldIndexMap找到最长递增子序列
咱们的 [4(d),6(f),3(c),5(e),0,0] 很明显能找到 [3,5]U x U 是数组中的最长递增子序列
于是乎 [3,5] 都不需求移动

我来画个图加深下回忆(忽略我的字迹)) ~ _ ^ Q q

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节

做完这步s I y操作之后,咱们的diff算法就结束了
当然还有许多细节,留意看源码如下留意看尤大符号的序号 1? a I ~ 5.3

    const patchKeyedChildren = (c1, c2, con) p P { mtainer, parentAnchor, parentComponent, parentSuspense, isSVG= + _, optimized) => {
let i = 0;
const l2 = c2.length;
let e1 = c1.length - 1; // prev endin- I  S m `g index
let e2 = l2 - 1; // next ending index
// 1. syny t B /c from start
// (ap Q q b) c
// (a b) d e
while (i <w H x y i 1 Z e;= e1 && i <= e2) {
const n1 = c1[i];
co[ ] a ` ` ^nst n2 = (c2[i] = o3 V a b a iptimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i]));
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container,.  y ! { , . ? parentAncho^ V V u Yr, parentComponent, parentSuspense, isSVG,3 T d 2 s V h optimized);
}
else {
break;
}
i++;
}
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 &&amG ` 5p; i <= e2) {
const n1 =u y - / K Z 1 ; E c1[e1];
const n2 = (c2[e2] = o, X 8 yptimized
? cloneIfMounted(5 v o l i 7 X = qc2[e2])
: normalizeVNode(c2[Q H % je2]));
if (isSameVNo: 2 H * O H A C `deType(n1, n2)) {
patch(n1, n2, container, parentAnchor, parentCome Y T ~ J U Oponent, paI x j N #rentSuspense, isSVG, optimized);
}
else {
break;
}
e/ P @ 8 & {1--;
e2--;
}
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1)O { k 9 {
if (i <= e2) {
const nextPos = e2 + 1;
const anchor = nextPos &z j Z lt; l2 ? c2[nextPos].el : parentAnchor;
while (i <= e2)s b  e {
pA w U Oatch(null, (c2[i] = optimized
? cloneIfMounted(c2[i])
: no7 = trmalizeVNode(c2[i% A l X ` m 0])), containerH u R p !, anchor, parentComponent, parentSuspense, isSVG);
i++;
}
}
}
// 4. common sequence + unmount
// (a b) c
// (a b)
/9  2/ i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0,e . b G n E Z e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parent$ : ( O B x ~Component, parenL 7 e R  m [ XtSuspense, true);
i+H z 2 r L 9 ++;
}
}
// 5. unknown sequence
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]:] a 8 w ` { k , { a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
else {
const s1 = i; // prev startin= 9 l d F - $g index
const s2 = i;x . ` ~ O 5 // next starting index
// 5.1 build key:index map for ne` D P { [ i l GwChildren
const keyToNewIndexMap = new Ma2 z h [ a L k G *p();
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNod7 y S n re(c2[i]));
if (nextChild.key != null) {
if ((proZ ] N q ) 0 h J Kcess.env.NODE_ENV !== 'p~ % x  Q 2 T t oroducti5 [  ] 0 9 Ron') && keyToNewIndexMap.has(nextChild.key)) {
warn(`Duplicate kx o n 8eys found during update:`, JSON.stringify(nextChild.key), `Make sure keyA = Xs are unique.`);
}
keyToNewIndexMap.set(nextChio 7 i _ ^ kld.key, i);
}
}
// 5.2 loop through old children lefv : S ] v s 7t to be patched and try to patch
// matching nodes & remove nodes that are no loO . Y b W /nger present
let j;
let pam Q Jtched = 0;
const toBePatched = e2 - s2 + 1;
let moved = false;
// used to track whether any node has moved
let maxC T RNewIndexSoFar = 0;) K ^ L
// works as Map<newIndex, oldIndex>
// Note that ol3 k %  _ z % bdIndex is offset by +1
// and oldIndex = 0 iP Q  + / s & ts a special value indicating the new node has
// no corresponding old nodi a 0 V a J *e.
// used for determining longest sta9 = ? G ( B  k Ible subsequence
const newIndexToOldIndexMap = new Array(toBePatched);
for (i = 0; i < toBePatched; i++)
new6 = J G , S m 4 MIndexToOldIndexMap[i: [ 9 U J] = 0;
for (i = s1; i <= e1; i++) {
cs I J x o ) onst prevChild = c1[i];
if (patc; M mhed >= toBePatched) {
// all new children have been patched so th? 7 4 c B f z V Bis can only be a removal
unmount(prevChild, parentComponent, parentSuspenG P 4 9 h ] p Rse, true);
continue;
}
leE ] Ft newIndex;
if (prevChild.key != null) {
newIndex =, * w keyToNewIndD k ! 4 gexMap.get(prevChild.key);
}
else {
// key-less node, trV 4 I j v $ ?y to locw q 7 ; _ate a key-less node of the same type
fol T B w $r (j = s2; j <= e2; j++) {
if ({ J Y nnewIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2B . l y p S[j]^ o X J i  n *)) {
newIndex = j;
break;
}
}
}
if (newIndex =k  x G . O== undefined) {
unmount(prevChild, parentComponent, parentSuspense, true);
}
else {
newIndexToOldIndP / T 7 a T TexMap[newIndex - s2] = i + 1;
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex;
}
else {
moved = true;
}
patch(M X ) I : H |prev^ i 3 y UChild, c2[newIndex], container, null, parentComponent, pF E T Q : 0 arentSuspensr M v V ? { Ve, isSVG, optimized);
patched++? B   $ ? X j;
}
}
// 5.3 move and mount
// gen. 0 !erate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = mov X 5 ] zved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR;
j = increasingNewIndexSequence.length - 1;
// looping backwards s0 8 c -o that we can use last patched node as anchor
for (i = toBePatched -F W ` r 1; i >= 0; i--) {
const nec G B I 4xtIndex = s2 + i;
const nextChild = c2[nextIndex];
const anchor = neB O 9xtIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(null, nextChild, container, anchor, parentComponent, parentSuspense, isSVG);
}
else if (moved) {
// move if:
// There is no stable subsequence (e.g. a r N | R + )everse_ b N R / r 5)
// OR current node is not among the stabl( # X ;e sequence
if (j < 0 || i !== increasingNewInd| * + p @exSequence[j])b 4 O {
move(nextChild, container, anchor, 2 /* REORDER *. * - 0 @ f } H/);
}
else {
j--;
}
}
}
}
};

比照 vue2.0 的diff算法

说到这儿,咱们能够来回忆下 vue2.0 的diff算法
仍是这组数据

    // old arr
["a", "b", "c", "d", "e", 9 ] ! / K e"f", "g", "h"]
// new arr
["- ! w _az - ? w *", "b", "d", "f", "c", "e_ . N !", "x", "y", "g", "h"]

vue2整体上也差不多,可是它只要q ; _一个双指针的循环
首要比较新旧的头,直到第一个非 sameVNOde
然后从尾开端比较b R } 4,直到第一个非 sameVNOde
然后{ 5 w ` 2 ) k G }会做头尾,尾头的比较,这种是考虑到会左移和右移操作
上面的步骤做完,会发现和vue3相同,只剩下这些G V 6 ]没有比较

["d", "f", "c", "e", "x", "y"]

接着会测验从 “d” 开端去旧数据里找到 index
然后移动到旧数据. O g Z还未比较数据的头部
于是乎:

  • 把 d 移动到 c 之前d % t . a h 2 . ;
  • 把 f 移动到 c 之前
  • 下轮循环发现新旧都是 c 于是 patch 完之后继续
  • 下轮循环发现新旧都8 5 & 1 } _ z是 e 于是 patch 完之后继续
  • 发现 x 不在旧数据中,createElm(x)
  • W x f ] ^ Q现 x 不在旧数据中,createElY ` @ H ^ r 2 & Xm(y)

能够发现,! n 9 – i 8 t 2vue2 在 diff 算法处理无序数据的判别是在最终
每次处理之前,会依次判别之前一切的 if
B – Fvue3中,会找到一切需求移动的节点,直接移动
还有一点 vue3 中 关于首尾替换的额外判别好像也取消了

3.事情优化

再来讲到了关于事情的优化
比如:

<div>
<span  @click="onClick">hello</span>
</div>

假如开启了cacheHW # a r , j { ! [andlers
则会缓存咱们的事情,事情的变化不会引起从头烘托

import { createVNode as _createVNode, openBlock as _op: j 4 4 F M 3enBlock, createBlock as _createBlock } from "vue"
expo3 ~ grt function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("span",t ~ ) D m {
onClicki [ : ^ ( S 4: _cache[1] || (_cm S w Qache[1] = $event => (_ctx.onClick($event)))
}, "hello")
]))( P x H { T 8 ^ U
}
// Check the console for the AST

这个很直观的,咱们能在 vue2 中看到
其实每次更新,render函数跑完之后
vnode绑定的事情都是一个全新生成的function,就算它们内部的代码是相同的
所以events的update钩子,9 Y P o几乎每次都能命中,然后更新一6 v g下函数的引用

function updateListeners (
on,
oldOn,
add,
remove$$1,
vm
) {
var name, def, cur, old, event;
for (name in on) {
def = cur = on[n; V ) h A Q L lame];
old = oldOn[name];
event = normalizeEvent(name);
debugger
/* istanbul igM o ~ Snore if */
if (isUnJ * c ` 2 @def(cur)) {
process.env.NODE_ENV !== 'production' && warn(
"Invalid handler for event "" + (eventJ { I / O.name) + "": got " + String(cur),
vm
);
} else if (isUndefo * A t a(old)) {
if (isUnde9 0 U 3 ]f(cur.fns)| [ d & = m) {
cur = onM . o[name] = createFnInvoker(cur);
}
add- | U E ] b | v Z(event.name, cur, event.once, ev6 t z 0 *ent.capture, event.a B l ` _pasP ! k Isive, event.params);
} else if (cur !== old) {
// 这儿 这儿 这儿
old.fns = cur;
on[nz P 9 I m 5 ; same] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture);
}
}
}

其他的..

还提到了SSR优化,我之前也发: X e u { r现了,vue3! l Y & H有一个新的ssr烘托函数createSSRApp
{ I j + , i q次有E Z l – A # t时间细心看看
当然老生常谈说了许多关于Composition API,这个是真的香
然后便是Telepo_ 9 R 2 8 T / Rrt Suspense 这两个组件
在 react 中现已有相似的组件,不过在vJ ) ; G T N B ^ hue中的实现还未来得及看

细致分析,尤雨溪直播中提到 vue3.0 diff 算法优化细节