Vue源码分析(8)--实例分析v-*指令

前言

本文是vue2.x源码分析的第八篇,主要看v-*指令的处理过程!

实例代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue</title>
  <script src="./vue.js" type="text/javascript" charset="utf-8" ></script>
</head>
<body>
  <div id="app">
    <!-- <div>{{pre}}</div> -->
    <!-- <div v-text='text'></div> -->
    <!-- <div v-html='html'></div> -->
    <!-- <div v-pre>{{pre}}</div> -->
    <!-- <div v-for='item in items' >{{item}}</div> -->
    <!-- <div v-cloak>{{cloak}}</div> -->
     <!-- <div v-once>{{once}}</div> -->
    <!-- <div v-show='show'>v-show</div> -->
    <!-- <div v-on:click='click'>click</div> -->
    <!-- <div v-bind:me='message'></div> -->
    <div v-model='model'></div>
  </div>
  <script>
    var vm=new Vue({
      el:'#app',
      name:'app',
      data:{
        items:[1,2,3],
        pre:'v-pre',
        once:'v-once-1',
        show:true,
        text:'v-text测试',
        html:'<span>v-html测试</span>',
        cloak:"v-cloak",
        message:'message',
        model:'model'
      },
      methods:{
        click:function(){
            console.log('click')
        }
      }
    });
  </script>
</body>
</html>

根据AST的不同,将v-*指令系列分为三部分

1 插值

基本的插值AST有9个基本属性


插值.jpg

2 v-if,v-on,v-pre,v-once,v-for,v-bind

这6个指令会在9个基本属性上添加自己的属性(有的会去掉部分基本属性)


v-if.jpg

v-on.jpg

v-pre.jpg

v-once.jpg

v-for.jpg

v-bind.jpg

3 v-show,v-model,v-text,v-html,v-cloak

这5个指令都会加上directives,hasBindings两个属性


v-model.jpg
v-show.jpg

v-text.jpg

v-html.jpg

v-cloak.jpg

此外,自定义的指令如v-focus等的处理过程与这5个指令类似

4 invokeCreateHooks(vnode, insertedVnodeQueue)

在上一节中提到createElm函数的四步中,第三步其实是很重要的

  function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
      cbs.create[i$1](emptyNode, vnode);
    }
    i = vnode.data.hook; // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) { i.create(emptyNode, vnode); }
      if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
    }
  }

这里的cbs如下:

    activate:Array(1)
    create:Array(8)
    destroy:Array(2)
    remove:Array(1)
    update:Array(7)
    __proto__:Object

其中的create是含有8个函数的数组,这8个函数依次是

    //1、处理data对象中的attrs,
    function updateAttrs(oldVnode, vnode) {
        if (!oldVnode.data.attrs && !vnode.data.attrs) {
            return
        }
        var key, cur, old;
        var elm = vnode.elm;
        var oldAttrs = oldVnode.data.attrs || {};
        var attrs = vnode.data.attrs || {};
        // clone observed objects, as the user probably wants to mutate it
        if (attrs.__ob__) {
            attrs = vnode.data.attrs = extend({}, attrs);
        }
        for (key in attrs) {
            cur = attrs[key];
            old = oldAttrs[key];
            if (old !== cur) {
                setAttr(elm, key, cur);
            }
        }
        // #4391: in IE9, setting type can reset value for input[type=radio]
        /* istanbul ignore if */
        if (isIE9 && attrs.value !== oldAttrs.value) {
            setAttr(elm, 'value', attrs.value);
        }
        for (key in oldAttrs) {
            if (attrs[key] == null) {
                if (isXlink(key)) {
                    elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
                } else if (!isEnumeratedAttr(key)) {
                    elm.removeAttribute(key);
                }
            }
        }
    }
    //2 处理data对象中的class和staticClass
     function updateClass(oldVnode, vnode) {
        var el = vnode.elm;
        var data = vnode.data;
        var oldData = oldVnode.data;
        if (!data.staticClass && !data.class && (!oldData || (!oldData.staticClass && !oldData.class))) {
            return
        }
        var cls = genClassForVnode(vnode);
        // handle transition classes
        var transitionClass = el._transitionClasses;
        if (transitionClass) {
            cls = concat(cls, stringifyClass(transitionClass));
        }
        // set the class
        if (cls !== el._prevClass) {
            el.setAttribute('class', cls);
            el._prevClass = cls;
        }
    }
    //3 处理data对象中的on,v-on就是在这儿处理的
    function updateDOMListeners(oldVnode, vnode) {
        if (!oldVnode.data.on && !vnode.data.on) {
            return
        }
        var on = vnode.data.on || {};
        var oldOn = oldVnode.data.on || {};
        target$1 = vnode.elm;
        normalizeEvents(on);
        updateListeners(on, oldOn, add$1, remove$2, vnode.context);
    }
    //4 处理data对象中的domProps,v-on就是在这儿处理的,v-text和v-html就是在这儿处理
     function updateDOMProps(oldVnode, vnode) {
        if (!oldVnode.data.domProps && !vnode.data.domProps) {
            return
        }
        var key, cur;
        var elm = vnode.elm;
        var oldProps = oldVnode.data.domProps || {};
        var props = vnode.data.domProps || {};
        // clone observed objects, as the user probably wants to mutate it
        if (props.__ob__) {
            props = vnode.data.domProps = extend({}, props);
        }
        for (key in oldProps) {
            if (props[key] == null) {
                elm[key] = '';
            }
        }
        for (key in props) {
            cur = props[key];
            // ignore children if the node has textContent or innerHTML,
            // as these will throw away existing DOM nodes and cause removal errors
            // on subsequent patches (#3360)
            if (key === 'textContent' || key === 'innerHTML') {
                if (vnode.children) {
                    vnode.children.length = 0;
                }
                if (cur === oldProps[key]) {
                    continue
                }
            }
            if (key === 'value') {
                // store value as _value as well since
                // non-string values will be stringified
                elm._value = cur;
                // avoid resetting cursor position when value is the same
                var strCur = cur == null ? '' : String(cur);
                if (shouldUpdateValue(elm, vnode, strCur)) {
                    elm.value = strCur;
                }
            } else {
                elm[key] = cur;
            }
        }
    }
    //5 处理data对象中的style和staticStyle
    function updateStyle(oldVnode, vnode) {
        var data = vnode.data;
        var oldData = oldVnode.data;
        if (!data.staticStyle && !data.style && !oldData.staticStyle && !oldData.style) {
            return
        }
        var cur, name;
        var el = vnode.elm;
        var oldStaticStyle = oldVnode.data.staticStyle;
        var oldStyleBinding = oldVnode.data.style || {};
        // if static style exists, stylebinding already merged into it when doing normalizeStyleData
        var oldStyle = oldStaticStyle || oldStyleBinding;
        var style = normalizeStyleBinding(vnode.data.style) || {};
        vnode.data.style = style.__ob__ ? extend({}, style) : style;
        var newStyle = getStyle(vnode, true);
        for (name in oldStyle) {
            if (newStyle[name] == null) {
                setProp(el, name, '');
            }
        }
        for (name in newStyle) {
            cur = newStyle[name];
            if (cur !== oldStyle[name]) {
                // ie9 setting to null has no effect, must use empty string
                setProp(el, name, cur == null ? '' : cur);
            }
        }
    }
    //6 处理data对象中的show,与transition有关
        function _enter(_, vnode) {
            if (!vnode.data.show) {
                enter(vnode);
            }
        }
    //7 处理data对象中的ref
        create: function create(_, vnode) {
            registerRef(vnode);
        }
        //其中registerRef函数如下:
            function registerRef(vnode, isRemoval) {
                var key = vnode.data.ref;
                if (!key) {
                    return
                }
                var vm = vnode.context;
                var ref = vnode.componentInstance || vnode.elm;
                var refs = vm.$refs;
                if (isRemoval) {
                    if (Array.isArray(refs[key])) {
                        remove(refs[key], ref);
                    } else if (refs[key] === ref) {
                        refs[key] = undefined;
                    }
                } else {
                    if (vnode.data.refInFor) {
                        if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {
                            refs[key].push(ref);
                        } else {
                            refs[key] = [ref];
                        }
                    } else {
                        refs[key] = ref;
                    }
                }
            }
    //8 处理data对象中的directives
     function updateDirectives(oldVnode, vnode) {
        if (oldVnode.data.directives || vnode.data.directives) {
            _update(oldVnode, vnode);
        }
    }
        //其中_updata函数如下:
        function _update(oldVnode, vnode) {
            var isCreate = oldVnode === emptyNode;
            var isDestroy = vnode === emptyNode;
            var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
            var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
            var dirsWithInsert = [];
            var dirsWithPostpatch = [];
            var key, oldDir, dir;
            for (key in newDirs) {
                oldDir = oldDirs[key];
                dir = newDirs[key];
                if (!oldDir) {
                    // new directive, bind
                    callHook$1(dir, 'bind', vnode, oldVnode);
                    if (dir.def && dir.def.inserted) {
                        dirsWithInsert.push(dir);
                    }
                } else {
                    // existing directive, update
                    dir.oldValue = oldDir.value;
                    callHook$1(dir, 'update', vnode, oldVnode);
                    if (dir.def && dir.def.componentUpdated) {
                        dirsWithPostpatch.push(dir);
                    }
                }
            }
            if (dirsWithInsert.length) {
                var callInsert = function() {
                    for (var i = 0; i < dirsWithInsert.length; i++) {
                        callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
                    }
                };
                if (isCreate) {
                    mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert);
                } else {
                    callInsert();
                }
            }
            if (dirsWithPostpatch.length) {
                mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function() {
                    for (var i = 0; i < dirsWithPostpatch.length; i++) {
                        callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
                    }
                });
            }
            if (!isCreate) {
                for (key in oldDirs) {
                    if (!newDirs[key]) {
                        // no longer present, unbind
                        callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
                    }
                }
            }
        }

patch在createElm调用完后,还调用了invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);这个就是执行指令中的inserted函数

对于内置的指令v-show和v-model,vue已经为其准备好了bind等函数,若要自定义指令,则需自己定义bind,inserted,update,updateComponent,unbind函数
如:

    // 注册一个全局自定义指令 v-focus
    Vue.directive('focus', {
      // 当绑定元素插入到 DOM 中。
      inserted: function (el) {
        el.focus()
      }
    })
    //若简写成这样
    // 注册一个全局自定义指令 v-focus
    Vue.directive('focus', function (el) {
        el.focus()
    })
    则vue会自动将该函数视为bind和update函数,此时不会取得focus效果,因为el.focus()执行时机过早,尚未插入父元素,不在文档流中
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容