vue

vue源码逐行注释分析+40多m的vue源码程序流程图思维导图(vue source code line by line annotation analysis +40 + m vue source code process flow chart mind map)

Stars
6.8K

English document: https://github.com/ygs-code/vue/blob/master/README_EN.md

vue vuevue

vue QQ 302817612 [email protected] satr

vue vuemvvm12mvvmdomjq

1.

vue react jsx vuepaseHTMLastpaseHTMLwhile vuevueast

2.

vueObject.defineProperty obersve()value__ob___Obersve valueVondeObersve ob Obersve defineReactiveDepwactherObject.definpropty() getset getnew Depdepend()depwactherwatcherrunupdatevonde setdepnotify wactherrun

3.dom

vnodevuevnode astvonde _c('div' s('')) vonde domupdata patch vonde diffdom

4.diif

 vue2 diff  vnodekey diffdiffvnodevnode vnode vnode vnode vnode  ++   --vnodedom

vue 1.new Vue Vue.prototype._init $options initLifecycle

new Vue

 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
        )
 
 

ASTrender

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,    &lt; <   &gt;  >  &quot;  " &amp; &  &#10;\n  &#9;\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();
            }
        });
    }

Dep

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();
    }

Watcher

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

DOM

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;
    };

diff

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);
                }
            }
        }

ddif updateChildren

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