Vue技术探究Vue.js专区

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

2017-07-08  本文已影响176人  风之化身呀

前言

本文是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()执行时机过早,尚未插入父元素,不在文档流中
上一篇下一篇

猜你喜欢

热点阅读