vue源码逐行注释分析+40多m的vue源码程序流程图思维导图(vue source code line by line annotation analysis +40 + m vue source code process flow chart mind map)
English document: https://github.com/ygs-code/vue/blob/master/README_EN.md
vue vuevue
vue QQ 302817612 [email protected] satr
vue vuemvvm12mvvmdomjq
vue react jsx vuepaseHTMLastpaseHTMLwhile vuevueast
vueObject.defineProperty obersve()value__ob___Obersve valueVondeObersve ob Obersve defineReactiveDepwactherObject.definpropty() getset getnew Depdepend()depwactherwatcherrunupdatevonde setdepnotify wactherrun
vnodevuevnode astvonde _c('div' s('')) vonde domupdata patch vonde diffdom
vue2 diff vnodekey diffdiffvnodevnode vnode vnode vnode vnode ++ --vnodedom
vue 1.new Vue Vue.prototype._init $options initLifecycle
Vue.prototype._init = function (options) { //
//... code
initLifecycle(vm); //
initEvents(vm); //
initRender(vm); //
callHook(vm, 'beforeCreate'); //beforeCreate
initInjections(vm); // resolve injections before data/props / // inject
initState(vm); // //
initProvide(vm); // resolve provide after data/props / provide
callHook(vm, 'created'); //created
//... code
// ast
vm.$mount(vm.$options.el);
}
vm.$mount render templateel.outerHTML , html
Vue.prototype.$mount = function (el, hydrating) {
//... code
el = el && query(el); //dom
if (!options.render) {
if (template) {
}else if (template.nodeType) {
template = template.innerHTML;
} else if (el) {
template = getOuterHTML(el);
}
// render ast
var ref = compileToFunctions(
template, //
{
shouldDecodeNewlines: shouldDecodeNewlines, //flase //IE
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, //true chromea[href]
delimiters: options.delimiters, //{{mgs}} delimiters: ['${', '}'] ${mgs}
comments: options.comments // true HTML
},
this
);
//... code
//$mount $mountdom
return mount.call(
this,
el, //dom
hydrating //undefined
)
Vue.prototype.$mount
compileToFunctions
createCompiler
createCompilerCreator
baseCompile
parse
parseHTML
parseHTML while (html) { //html vuevueast
function parseHTML(
html, //
options //
) {
var stack = []; // parseHTML
var expectHTML = options.expectHTML; //true
var isUnaryTag$$1 = options.isUnaryTag || no; // 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr'
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no; // // 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
var index = 0;
var last, //
lastTag; //
console.log(html)
while (html) { //html
last = html; //
// Make sure we're not in a plaintext content element like script/style /
if (
!lastTag || //lastTag
!isPlainTextElement(lastTag) // script,style,textarea
) {
var textEnd = html.indexOf('<'); //
if (textEnd === 0) { //
// Comment:
if (comment.test(html)) { // <!--,
var commentEnd = html.indexOf('-->'); //
if (commentEnd >= 0) { //0
console.log(html.substring(4, commentEnd))
if (options.shouldKeepComment) { //shouldKeepComment
//
options.comment(html.substring(4, commentEnd));
}
// while
advance(commentEnd + 3);
continue
}
}
//ie
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) { // <![ <![endif]--> ie <!--[if IE 8]><link href="ie8only.css" rel="stylesheet"><![endif]-->
//ie
var conditionalEnd = html.indexOf(']>');
if (conditionalEnd >= 0) {
// while
advance(conditionalEnd + 2);
continue
}
}
// Doctype:
//html <!DOCTYPE html>
var doctypeMatch = html.match(doctype);
if (doctypeMatch) {
// while
advance(doctypeMatch[0].length);
continue
}
// End tag:
//</ ^<\\/((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*)[^>]*>
var endTagMatch = html.match(endTag);
if (endTagMatch) {
var curIndex = index;
// while
advance(endTagMatch[0].length);
console.log(endTagMatch)
console.log(curIndex, index)
//parseHTMLstacktagName
//options.end
//stackcurrentParent
parseEndTag(
endTagMatch[1],
curIndex,
index
);
continue
}
// Start tag:
//
//
var startTagMatch = parseStartTag();
if (startTagMatch) {
//
//parseHTML
//options.start parse stack
handleStartTag(startTagMatch);
//tagpre,textarea
if (shouldIgnoreFirstNewline(lastTag, html)) {
//
advance(1);
}
continue
}
}
var text = (void 0),
rest = (void 0),
next = (void 0);
if (textEnd >= 0) {
rest = html.slice(textEnd); // var textEnd = html.indexOf('<'); //
console.log(rest)
while (
!endTag.test(rest) && //</
!startTagOpen.test(rest) && // <
!comment.test(rest) && // <!--
!conditionalComment.test(rest) // <![
) {
console.log(rest);
// < in plain text, be forgiving and treat it as text
// <
next = rest.indexOf('<', 1); //<
if (next < 0) {
break
}
textEnd += next; //
rest = html.slice(textEnd); // < < <
}
text = html.substring(0, textEnd); // <
//while
advance(textEnd);
}
if (textEnd < 0) { // <
text = html; //text
html = ''; //html while
}
if (options.chars && text) {
options.chars(text);
}
} else {
// script,style,textarea
var endTagLength = 0;
var stackedTag = lastTag.toLowerCase();
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length;
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
}
//tagpre,textarea
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1);
}
if (options.chars) {
options.chars(text);
}
return ''
});
index += html.length - rest$1.length;
html = rest$1;
parseEndTag(stackedTag, index - endTagLength, index);
}
if (html === last) {
options.chars && options.chars(html);
if ("development" !== 'production' && !stack.length && options.warn) {
options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
}
break
}
}
// Clean up any remaining tags
//parseHTMLstacktagName
//options.end
//stackcurrentParent
parseEndTag();
//while
function advance(n) {
index += n; //
html = html.substring(n); //
}
//
function parseStartTag() {
var start = html.match(startTagOpen); // < ^<((?:[a-zA-Z_][\\w\\-\\.]*\\:)?[a-zA-Z_][\\w\\-\\.]*)
console.log(start)
console.log(start[0].length)
if (start) {
var match = {
tagName: start[1], //
attrs: [], //
start: index //
};
//
advance(start[0].length);
var end, attr;
while (
!(end = html.match(startTagClose)) // >
&& (attr = html.match(attribute)) //
) {
console.log(html)
//
advance(attr[0].length);
match.attrs.push(attr); //
}
if (end) {
match.unarySlash = end[1]; ///> unarySlash / > unarySlash
console.log(end)
//
advance(end[0].length);
match.end = index; //
return match
}
}
}
//
//parseHTML
//options.start parse stack
function handleStartTag(match) {
/*
* match = {
tagName: start[1], //
attrs: [], //
start: index //
match:index //
unarySlash:'' ///> unarySlash / > unarySlash
};
* */
var tagName = match.tagName; //
var unarySlash = match.unarySlash; ///> unarySlash / > unarySlash
console.log(expectHTML)
console.log('lastTag==')
console.log(lastTag)
console.log(tagName)
if (expectHTML) { //true
if (
lastTag === 'p' //p
/*
'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
'title,tr,track'
*/
&& isNonPhrasingTag(tagName)
) {
//parseHTMLstacktagName
//options.end
//stackcurrentParent
parseEndTag(lastTag);
}
if (
canBeLeftOpenTag$$1(tagName) && // 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
lastTag === tagName // <li><li> <li></li> <li>li<li> <li>li</li><li> </li>
) {
//parseHTMLstacktagName
//options.end
//stackcurrentParent
parseEndTag(tagName);
}
}
var unary = isUnaryTag$$1(tagName) || // 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr'
!!unarySlash; ///>
var l = match.attrs.length;
var attrs = new Array(l); //
for (var i = 0; i < l; i++) {
var args = match.attrs[i]; //
// hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
//FF bug:https://bugzilla.mozilla.org/show_bug.cgi?id=369778
if (
IS_REGEX_CAPTURING_BROKEN && //
args[0].indexOf('""') === -1
) {
if (args[3] === '') {
delete args[3];
}
if (args[4] === '') {
delete args[4];
}
if (args[5] === '') {
delete args[5];
}
}
var value = args[3] || args[4] || args[5] || '';
var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
? options.shouldDecodeNewlinesForHref // true chromea[href]
: options.shouldDecodeNewlines; //flase //IE
attrs[i] = { //
name: args[1], //
//
value: decodeAttr(value, shouldDecodeNewlines) //html js, < < > > " " & & \n 	\t
};
}
console.log('==!unary==')
console.log(!unary)
if (!unary) { //
// parseHTML
stack.push({ //
tag: tagName, //
lowerCasedTag: tagName.toLowerCase(), //
attrs: attrs //
});
//
lastTag = tagName;
console.log('== parseHTML handleStartTag stack==')
console.log(stack)
}
//
if (options.start) {
// astdom v-for v-for foraliasiterator1iterator2dom
//v-ifeldom v-ifv-elesv-else-if
//v-once dom
//elmuted eventsnativeEventsdirectives key refslotNameslotScopeslotcomponentinlineTemplate
// currentParent element
//parse stack
options.start(
tagName, //
attrs, //
unary, //
match.start, //
match.end //
);
}
}
//parseHTMLstacktagName
//options.end
//stackcurrentParent
function parseEndTag(
tagName, //
start, //
end //
) {
var pos,
lowerCasedTagName;
if (start == null) { //
start = index; //
}
if (end == null) { //
end = index; //
}
if (tagName) { //
lowerCasedTagName = tagName.toLowerCase(); //
}
// Find the closest opened tag of the same type
if (tagName) {
// stack
for (pos = stack.length - 1; pos >= 0; pos--) {
//
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break
}
}
} else {
// If no tag name is provided, clean shop
//
pos = 0;
}
if (pos >= 0) { //stackpos
// Close all the open elements, up the stack
console.log(pos)
for (var i = stack.length - 1; i >= pos; i--) {
if ("development" !== 'production' && //stacktagName
(i > pos || !tagName) &&
options.warn
) {
options.warn(
("tag <" + (stack[i].tag) + "> has no matching end tag.")
);
}
if (options.end) {
console.log(options.end)
//options.end
//stackcurrentParent
options.end(
stack[i].tag,//
start, //
end //
);
}
}
// Remove the open elements from the stack
//
// console.log(stack[pos].tag)
// parseHTML
stack.length = pos;
//
lastTag = pos && stack[pos - 1].tag;
console.log(stack)
console.log(lastTag)
} else if (lowerCasedTagName === 'br') {
if (options.start) {
// astdom v-for v-for foraliasiterator1iterator2dom
//v-ifeldom v-ifv-elesv-else-if
//v-once dom
//elmuted eventsnativeEventsdirectives key refslotNameslotScopeslotcomponentinlineTemplate
// currentParent element
//parse stack
options.start(
tagName,
[], true,
start,
end
);
}
} else if (lowerCasedTagName === 'p') {
if (options.start) {
// astdom v-for v-for foraliasiterator1iterator2dom
//v-ifeldom v-ifv-elesv-else-if
//v-once dom
//elmuted eventsnativeEventsdirectives key refslotNameslotScopeslotcomponentinlineTemplate
// currentParent element
//parse stack
options.start(
tagName,
[], false,
start,
end);
}
if (options.end) {
//
//stackcurrentParent
options.end(
tagName,
start,
end
);
}
}
console.log(lastTag)
}
}
var onRE = /^@|^v-on:/;// @v-on:
var dirRE = /^v-|^@|^:/; // v-@:
var forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/; // in of
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/; //, [{ , , }] ,+
var stripParensRE = /^\(|\)$/g; // ()
var argRE = /:(.*)$/; //:
var bindRE = /^:|^v-bind:/; // :v-bind
var modifierRE = /\.[^.]+/g; // data.object.info.age ['.object''.info' , '.age']
var decodeHTMLCached = cached(he.decode); // domtextContent
defineReactive prop state set initInjections
var dep = new Dep(); Depget
//dep
dep.depend();
if (childOb) { //dep
childOb.dep.depend();
if (Array.isArray(value)) { //
dependArray(value); //dep
}
}
set
//observe
//
childOb = !shallow && observe(newVal); // dep.notify();
/**
* Define a reactive property on an Object.
*
*
* definePropertysetnotify()subscribers
* get set
*/
function defineReactive(obj, //
key,//key
val, //
customSetter, //
shallow //__ob__
) {
//
var dep = new Dep();
//
var property = Object.getOwnPropertyDescriptor(obj, key);
var _property = Object.getOwnPropertyNames(obj); //
console.log(property);
console.log(_property);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
console.log('arguments.length=' + arguments.length)
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
console.log(val)
//value __ob__ dep,dep value__ob__ val new Observer
var childOb = !shallow && observe(val);
//
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
var value = getter ? getter.call(obj) : val;
if (Dep.target) { //Dep.target DepWatcher
//dep
dep.depend();
if (childOb) { //dep
childOb.dep.depend();
if (Array.isArray(value)) { //
dependArray(value); //dep
}
}
}
return value
},
set: function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare
*
* */
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
//set
setter.call(obj, newVal);
} else {
//
val = newVal;
}
console.log(newVal)
//observe
childOb = !shallow && observe(newVal);
//
dep.notify();
}
});
}
vuegetdepsetdepdepnotify dep Watcher depWatcher
//Dep
var Dep = function Dep() {
//uid 0
this.id = uid++;
/* Watcher */
this.subs = [];
};
Dep.prototype.addSub = function addSub(sub) {
/* subsWatcher */
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub(sub) {
/* subsWatcher */
remove(this.subs, sub);
};
//this$1.deps[i].depend();
//Watcher Watcher.newDeps.push(dep); dep
Dep.prototype.depend = function depend() {
//dep target Watcher depdep
if (Dep.target) {
//
Dep.target.addDep(this);
}
};
/* Watcher */
Dep.prototype.notify = function notify() {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
//
subs[i].update();
}
};
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
//
//
//
Dep.target = null;
var targetStack = [];
function pushTarget(_target) {
//target Watcher depdep
if (Dep.target) { // Deptarget
//pushTarget
targetStack.push(Dep.target);
}
Dep.target = _target;
}
//
function popTarget() {
// pushTarget
Dep.target = targetStack.pop();
}
WatcherDepupdate updaterender
rendervnode vnodepatchvnodevnodevnodedom
* *
*
*$watch() api
* vueupdateComponent
*/
var Watcher = function Watcher(
vm, //vm dom
expOrFn, //viwe
cb, //,
options, //
isRenderWatcher//
) {
console.log('====Watcher====')
this.vm = vm;
//
if (isRenderWatcher) { // Watcher vm._watcher
vm._watcher = this;
}
// Watchervue
vm._watchers.push(this);
// options
if (options) { //
this.deep = !!options.deep; //
this.user = !!options.user; //
this.lazy = !!options.lazy; // ssr
this.sync = !!options.sync; //
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb; //
this.id = ++uid$1; // uid for batching uid id
this.active = true; //
this.dirty = this.lazy; // for lazy watchers
this.deps = []; //
this.newDeps = []; //
//
this.depIds = new _Set();
this.newDepIds = new _Set();
//
this.expression = expOrFn.toString();
// parse expression for getter
//getter
if (typeof expOrFn === 'function') {
//
this.getter = expOrFn;
} else {
//keepAlive
//path
if (bailRE.test(path)) { // true var bailRE = /[^\w.$]/; // $ true
return
}
// // path
// var segments = path.split('.');
// return function (obj) {
//
// for (var i = 0; i < segments.length; i++) {
// //
// if (!obj) {
// return
// }
// //key segments obj key
// obj = obj[segments[i]];
// }
// //
// return obj
// }
// $ true
this.getter = parsePath(expOrFn);
if (!this.getter) { //
this.getter = function () {
};
"development" !== 'production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy ? // lazy
undefined :
this.get(); //getter
};
Watcherget getWatcher updateDep updaterun runcb cbrender
vueDOM,Vnodedomdom
dom
/*
* vue vnode
*
* */
var VNode = function VNode(
tag, /**/
data, /*VNodeDataVNodeData*/
children, //
text, //
elm, /*dom */
context, /**/
componentOptions, /*option*/
asyncFactory/**/) {
/**/
this.tag = tag;
/*VNodeDataVNodeData*/
this.data = data;
/**/
this.children = children;
/**/
this.text = text;
/*dom*/
this.elm = elm;
/**/
this.ns = undefined;
/* vm*/
this.context = context;
this.fnContext = undefined;
this.fnOptions = undefined;
this.fnScopeId = undefined;
/*key*/
this.key = data && data.key;
/*option*/
this.componentOptions = componentOptions;
/**/
this.componentInstance = undefined;
/**/
this.parent = undefined;
/*HTMLinnerHTMLtruetextContentfalse*/
this.raw = false;
/**/
this.isStatic = false;
/**/
this.isRootInsert = true;
/**/
this.isComment = false;
/**/
this.isCloned = false;
/*v-once*/
this.isOnce = false;
/**/
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
};
patch sameVnode patchVnode updateChildren
patch sameVnode
//sameVnode(oldVnode, vnode)22diff
function sameVnode(a, b) {
return (
a.key === b.key && ( //akey bkey
(
a.tag === b.tag && // atag btag
a.isComment === b.isComment && // ab
isDef(a.data) === isDef(b.data) && //a.data b.data tag
sameInputType(a, b) //ab
) || (
isTrue(a.isAsyncPlaceholder) && //
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
sameVnode patchVnode ,
patchVnode vnode key domoldCh !== ch updateChildren diff
// dom
function patchVnode(
oldVnode, // dom
vnode, // dom
insertedVnodeQueue, // dom
removeOnly
) {
if (oldVnode === vnode) { //
return
}
var elm = vnode.elm = oldVnode.elm; //dom
// isAsyncPlaceholder
if (isTrue(oldVnode.isAsyncPlaceholder)) {
// undefinednull
if (isDef(vnode.asyncFactory.resolved)) {
// ssr
hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
} else {
vnode.isAsyncPlaceholder = true;
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
//
//vnode
//
//hot-reload api
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance;
return
}
var i;
var data = vnode.data;
//
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode);
}
var oldCh = oldVnode.children;
var ch = vnode.children;
// tag
if (isDef(data) && isPatchable(vnode)) {
//
for (i = 0; i < cbs.update.length; ++i) {
cbs.update[i](oldVnode, vnode);
}
//
if (isDef(i = data.hook) && isDef(i = i.update)) {
i(oldVnode, vnode);
}
}
//dom
if (isUndef(vnode.text)) {
// dom
if (isDef(oldCh) && isDef(ch)) {
//
if (oldCh !== ch) {
// diff
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
}
} else if (isDef(ch)) { // dom
// dom
if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '');
}
// vnode
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) { // dom dom
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) { // dom
nodeOps.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
//
nodeOps.setTextContent(elm, vnode.text);
}
if (isDef(data)) {
//
if (isDef(i = data.hook) && isDef(i = i.postpatch)) {
i(oldVnode, vnode);
}
}
}
diifvue2 diff vnodekey diffdiffvnodevnode vnode vnode vnode vnode ++ --vnode patchVnodedomvnodeupdateChildrenvnodedom
// ddif
function updateChildren(
parentElm, // dom
oldCh, // dom
newCh, // dom
insertedVnodeQueue,
removeOnly
) {
var oldStartIdx = 0; // dom
var newStartIdx = 0; // dom
var oldEndIdx = oldCh.length - 1; // dom
var newEndIdx = newCh.length - 1;// dom
var oldStartVnode = oldCh[0]; // dom
var newStartVnode = newCh[0]; // dom
var oldEndVnode = oldCh[oldEndIdx]; // dom
var newEndVnode = newCh[newEndIdx];// dom
var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
var canMove = !removeOnly;
{
// key
checkDuplicateKeys(newCh);
}
/*
diff
diff
*/
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
//
//
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
//
//
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) { //sameVnode(oldVnode, vnode)22diff
// dom
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
//
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) { //sameVnode(oldVnode, vnode)22diff
// dom
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
//
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right //sameVnode(oldVnode, vnode)22diff
//
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
//
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
//
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
//
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
// key key
if (isUndef(oldKeyToIdx)) {
// key key key
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
// vnode key
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
// vnode key
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
// vnode key dom
if (isUndef(idxInOld)) { // New element
// dom
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
// dom
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
//
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
} else {
// same key but different element. treat as new element
// dom
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
// dom
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
} else if (newStartIdx > newEndIdx) {
// dom
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
vue.js,
https://pan.baidu.com/s/10IxV6mQ2TIwkRACKu2T0ng 1fnu
vue.js vuevue.js, vue.jsvue.js demosatr