Vue源码后记-其余内置指令(1)

  把其余的内置指令也搞完吧,来一个全家桶。

 

  案例如下:

    <body>
        <div id='app'>
            <div v-if="vIfIter" v-bind:style="styleObject">
                <input v-show="vShowIter" v-model='vModel' />
                <span v-once>{{msg}}</span>
                <div v-html="html"></div>
            </div>
            <div class='on'>empty Node</div>
        </div>
    </body>
    <script src='./vue.js'></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                vIfIter: true,
                vShowIter: true,
                vModel: 1,
                styleObject: {
                    color: 'red'
                },
                msg: 'Hello World',
                html: '<span>v-html</span>'
            },
        });
    </script>

  基本上内置指令都有,由于v-on涉及事件,也就是methods,这个后面再说,这里暂时只处理指令。另外添加了一个纯净的节点,可以跑一下ref和optimize。

 

  跳过前面所有无聊的流程,直接进入parseHTML,切割方面也没什么看头,最外层div切割完,会进入v-if那个标签,即:

    <div v-if="vIfIter" v-bind:style="styleObject">

 

  正常切割后,如图所示:

  attrs存放着该标签的2个属性,分别为v-if与v-bind:style,简单的切割后,会调用handleStart进一步处理,其中就包含一系列process函数:

    function start(tag, attrs, unary) {
        // code...

        if (inVPre) {
            processRawAttrs(element);
        } else {
            processFor(element);
            processIf(element);
            processOnce(element);
            processKey(element);

            element.plain = !element.key && !attrs.length;

            processRef(element);
            processSlot(element);
            processComponent(element);
            for (var i$1 = 0; i$1 < transforms.length; i$1++) {
                transforms[i$1](element, options);
            }
            processAttrs(element);
        }

        // code...
    }

  这里对for、if、once等内置指令进行2次处理,for之前专门分析过一节,所以不管,首先看看if:

    // el为之前的切割对象
    function processIf(el) {
        // 将v-if从attrsList中移除 因为会影响render函数的生成
        var exp = getAndRemoveAttr(el, 'v-if');
        if (exp) {
            // el.if => vIfIter
            el.if = exp;
            addIfCondition(el, {
                exp: exp,
                block: el
            });
        }
        // 处理else与else-if 
        else {
            // code...
        }
    }

    // 保存节点display状态
    function addIfCondition(el, condition) {
        if (!el.ifConditions) {
            el.ifConditions = [];
        }
        el.ifConditions.push(condition);
    }

  案例只有v-if,else和else-if有兴趣自己去玩吧,处理完后得到这么一个对象:,与v-for类似,有一个属性专门保存对象的值,另外有个codition保存状态。

 

  接下来处理v-bind:style属性,处理函数在下面的transforms数组中,一个负责class,一个负责style,看一个就行了。

    function transformNode$1(el, options) {
        var warn = options.warn || baseWarn;
        // 获取静态style属性并添加在staticStyle属性上
        var staticStyle = getAndRemoveAttr(el, 'style');
        if (staticStyle) {
            // warning...
            el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
        }
        // 获取动态绑定的style
        var styleBinding = getBindingAttr(el, 'style', false /* getStatic */ );
        if (styleBinding) {
            el.styleBinding = styleBinding;
        }
    }

    // 该函数专门用来处理v-bind绑定的属性
    // name => style 
    // getStatic => false
    function getBindingAttr(el, name, getStatic) {
        // 处理缩写:
        var dynamicValue =
            getAndRemoveAttr(el, ':' + name) ||
            getAndRemoveAttr(el, 'v-bind:' + name);
        // dynamicValue => styleObject
        if (dynamicValue != null) {
            return parseFilters(dynamicValue)
        } else if (getStatic !== false) {
            var staticValue = getAndRemoveAttr(el, name);
            if (staticValue != null) {
                return JSON.stringify(staticValue)
            }
        }
    }

  这里分别对静态的style动态(v-bind:style)的style分别进行处理,静态的直接抽取出来JSON化保存到一个属性。

  动态的根据简写或全名来进行获取,获取到对应的值,这里是styleObject,然后调用parseFilters进行过滤。

  这个函数当初抄源码那个恶心哦。。。

  这里放一下这个函数:

    // 处理过滤器
    function parseFilters(exp) {
        var inSingle = false;
        var inDouble = false;
        var inTemplateString = false;
        var inRegex = false;
        var curly = 0;
        var square = 0;
        var paren = 0;
        var lastFilterIndex = 0;
        var c, prev, i, expression, filters;
        // 遍历字符串
        for (i = 0; i < exp.length; i++) {
            // prev => 上一个字符
            // c => 当前字符
            prev = c;
            c = exp.charCodeAt(i);
            if (inSingle) {
                if (c === 0x27 && prev !== 0x5C) {
                    inSingle = false;
                }
            } else if (inDouble) {
                if (c === 0x22 && prev !== 0x5C) {
                    inDouble = false;
                }
            } else if (inTemplateString) {
                if (c === 0x60 && prev !== 0x5C) {
                    inTemplateString = false;
                }
            } else if (inRegex) {
                if (c === 0x2f && prev !== 0x5C) {
                    inRegex = false;
                }
            }
            // 单独出现|符号 且大中小括号分别配对 
            else if (
                c === 0x7C && // |
                exp.charCodeAt(i + 1) !== 0x7C &&
                exp.charCodeAt(i - 1) !== 0x7C &&
                !curly && !square && !paren
            ) {
                if (expression === undefined) {
                    // 截取expresion为|符号前面的字符串
                    lastFilterIndex = i + 1;
                    expression = exp.slice(0, i).trim();
                } else {
                    pushFilter();
                }
            } else {
                switch (c) {
                    case 0x22:
                        inDouble = true;
                        break // "
                    case 0x27:
                        inSingle = true;
                        break // '
                    case 0x60:
                        inTemplateString = true;
                        break // `
                    case 0x28:
                        paren++;
                        break // (
                    case 0x29:
                        paren--;
                        break // )
                    case 0x5B:
                        square++;
                        break // [
                    case 0x5D:
                        square--;
                        break // ]
                    case 0x7B:
                        curly++;
                        break // {
                    case 0x7D:
                        curly--;
                        break // }
                }
                // 正则表达式
                if (c === 0x2f) { // /
                    var j = i - 1;
                    var p = (void 0);
                    // find first non-whitespace prev char
                    for (; j >= 0; j--) {
                        p = exp.charAt(j);
                        if (p !== ' ') {
                            break
                        }
                    }
                    if (!p || !validDivisionCharRE.test(p)) {
                        inRegex = true;
                    }
                }
            }
        }

        if (expression === undefined) {
            expression = exp.slice(0, i).trim();
        } else if (lastFilterIndex !== 0) {
            // 这里截取过滤函数
            pushFilter();
        }

        function pushFilter() {
            (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
            lastFilterIndex = i + 1;
        }

        if (filters) {
            for (i = 0; i < filters.length; i++) {
                expression = wrapFilter(expression, filters[i]);
            }
        }

        return expression
    }

    function wrapFilter(exp, filter) {
        var i = filter.indexOf('(');
        if (i < 0) {
            // _f: resolveFilter
            return ("_f(\"" + filter + "\")(" + exp + ")")
        } else {
            var name = filter.slice(0, i);
            var args = filter.slice(i + 1);
            return ("_f(\"" + name + "\")(" + exp + "," + args)
        }
    }

  这个函数很长很长,主要是格式化绑定的值。由官网的实例可知,模板语法支持形式诸如{{message | filter}}或者v-bind:id='str | filter',甚至支持正则语法。而这个函数就是处理这种复杂值的。

  而筛选器涉及到options的参数filter,不在本篇的内置指令讨论之内,所以暂时跳过。这里会直接返回传进去的字符串,即:,然后顺便作为属性绑定到vm对象上。

  切割完v-if的div标签,接下来是:

  <input v-show="vShowIter" v-model='vModel' />

  该标签属于自闭合标签。依照惯例,依次用正则分割出两个attr,象征性放个图:

  接下来也会进入handleStartTag函数,处理分割出的各种属性。

  在处理v-show与v-model时,并没有专门的process函数,这些内置指令被统一用一个processAttrs处理,这里看看是如何被处理的:

    // 处理其余的v-指令
    function processAttrs(el) {
        var list = el.attrsList;
        var i, l, name, rawName, value, modifiers, isProp;
        for (i = 0, l = list.length; i < l; i++) {
            name = rawName = list[i].name;
            value = list[i].value;
            // dirRE => /^v-|^@|^:/
            // 专业匹配v- @ :三剑客
            if (dirRE.test(name)) {
                el.hasBindings = true;
                // 匹配一些后缀 诸如事件的.self/.prevent等
                modifiers = parseModifiers(name);
                // 截取到后缀后去掉
                if (modifiers) {
                    name = name.replace(modifierRE, '');
                }
                // bindRE => /^:|^v-bind:/
                // 处理v-bind绑定的属性
                if (bindRE.test(name)) { // v-bind
                    // code...
                }
                // onRE => /^@|^v-on:/ 
                else if (onRE.test(name)) {
                    // 绑定事件处理器
                    name = name.replace(onRE, '');
                    addHandler(el, name, value, modifiers, false, warn$2);
                }
                // 普通指令
                else {
                    // 截取v-后面的字符串
                    name = name.replace(dirRE, '');
                    // argRE => /:(.*)$/
                    var argMatch = name.match(argRE);
                    var arg = argMatch && argMatch[1];
                    if (arg) {
                        name = name.slice(0, -(arg.length + 1));
                    }
                    addDirective(el, name, rawName, value, arg, modifiers);
                    // v-for的别名跟v-model重复
                    if ("development" !== 'production' && name === 'model') {
                        checkForAliasModel(el, value);
                    }
                }
            } else {
                // 处理普通的属性绑定
                // warning:<div id="{{ val }}"> => use <div :id="val">
                addAttr(el, name, JSON.stringify(value));
            }
        }
    }

    // name => show、model
    function parseModifiers(name) {
        // modifierRE => /\.[^.]+/g
        var match = name.match(modifierRE);
        if (match) {
            var ret = {};
            match.forEach(function(m) {
                ret[m.slice(1)] = true;
            });
            return ret
        }
    }

    // el => 
    function addDirective(el, name, rawName, value, arg, modifiers) {
        (el.directives || (el.directives = [])).push({
            name: name,
            rawName: rawName,
            value: value,
            arg: arg,
            modifiers: modifiers
        });
    }

  这里分别处理三种情况:v-、:、@,分别是内置指令、属性绑定、事件绑定,分别调用不同的方法处理并添加对应的属性到vm上。

  内置指令处理完会生成一个directives的数组属性绑定到vm上,并将切割后的属性对象作为数组元素,如图:

 

  进行下一个tag切割,即:

    <span v-once>{{msg}}</span>

  这里的内置属性为v-once,有一个processOnce函数处理这个指令:

    function processOnce(el) {
        var once$$1 = getAndRemoveAttr(el, 'v-once');
        if (once$$1 != null) {
            el.once = true;
        }
    }

  太简单,注释都懒得写了。

  处理{{msg}}的过程就不写了,在跑源码的时候用的就是这个形式。

 

  下一个tag:

    <div v-html="html"></div>

  这个指令没有特殊函数处理,被丢到了processAttrs函数,然后通过addDirective添加到directives数组中,如图:

 

  至此,所有的内置指令相关的标签都解析完了,还剩一个纯净的DOM节点:

    <div class='on'>empty Node</div>

  正常切割完节点后开始解析属性,此处的class并没有用v-bind进行绑定,所以在调用transformNode方法处理class属性时,会被认定为static属性,如下:

    function transformNode(el, options) {
        var warn = options.warn || baseWarn;
        // 获取静态的class => on
        var staticClass = getAndRemoveAttr(el, 'class');
        // warning...
        if (staticClass) {
            // JSON化后作为属性添加到对象上
            el.staticClass = JSON.stringify(staticClass);
        }
        var classBinding = getBindingAttr(el, 'class', false /* getStatic */ );
        if (classBinding) {
            el.classBinding = classBinding;
        }
    }

  弄完,大概是个这样子:

  

  AST转化完后会进入optimize阶段,可以稍微讲下这个地方,首先会对所有节点进行标记:

    function optimize(root, options) {
        if (!root) {
            return
        }
        isStaticKey = genStaticKeysCached(options.staticKeys || '');
        isPlatformReservedTag = options.isReservedTag || no;
        // 标记是否静态节点
        markStatic$1(root);
        // 标记根节点
        markStaticRoots(root, false);
    }

    function markStatic$1(node) {
        // 标记当前节点是否为静态节点
        node.static = isStatic(node);
        if (node.type === 1) {
            if (!isPlatformReservedTag(node.tag) &&
                node.tag !== 'slot' &&
                node.attrsMap['inline-template'] == null
            ) {
                return
            }
            for (var i = 0, l = node.children.length; i < l; i++) {
                // 遍历子节点递归判断
                var child = node.children[i];
                markStatic$1(child);
                // 如果子节点是非静态 那么父节点也是非静态
                if (!child.static) {
                    node.static = false;
                }
            }
        }
    }

    function isStatic(node) {
        // {{...}}大括号表达式
        if (node.type === 2) { // expression
            return false
        }
        // 纯文本节点 
        if (node.type === 3) { // text
            return true
        }
        // 子节点没有hasBindings、v-for、v-if、非slot/component标签、组件、静态属性判断
        return !!(node.pre || (!node.hasBindings && // no dynamic bindings
            !node.if && !node.for && // not v-if or v-for or v-else
            !isBuiltInTag(node.tag) && // not a built-in
            isPlatformReservedTag(node.tag) && // not a component
            !isDirectChildOfTemplateFor(node) &&
            Object.keys(node).every(isStaticKey)
        ))
    }

  这里一层一层递归进行标记,所有的AST对象会添加一个static属性,只有最后那个纯净节点的static标记为true(其实有3个子节点,多出来的是排版形成的回车换行符):

  当一个节点被标记为静态节点,之后的虚拟DOM在通过diff算法比较差异时会跳过该节点以提升效率,这就是AST的优化。

  静态节点标记完后,还有最后一步,调用markStaticRoots函数进行二次优化,并会对v-if做特殊处理:

    function markStaticRoots(node, isInFor) {
        if (node.type === 1) {
            if (node.static || node.once) {
                node.staticInFor = isInFor;
            }
            // For a node to qualify as a static root, it should have children that
            // are not just static text. Otherwise the cost of hoisting out will
            // outweigh the benefits and it's better off to just always render it fresh.
            if (node.static && node.children.length && !(
                    node.children.length === 1 &&
                    node.children[0].type === 3
                )) {
                node.staticRoot = true;
                return
            } else {
                node.staticRoot = false;
            }
            if (node.children) {
                for (var i = 0, l = node.children.length; i < l; i++) {
                    markStaticRoots(node.children[i], isInFor || !!node.for);
                }
            }
            // v-if
            if (node.ifConditions) {
                walkThroughConditionsBlocks(node.ifConditions, isInFor);
            }
        }
    }

  这里的优化直接看那段注释就可以了,大概意思是:如果一个节点的子节点只有一个表达式,那就没有必要当做非静态节点。

  看一下v-if的处理函数:

    function walkThroughConditionsBlocks(conditionBlocks, isInFor) {
        for (var i = 1, len = conditionBlocks.length; i < len; i++) {
            markStaticRoots(conditionBlocks[i].block, isInFor);
        }
    }

  看毛,直接跳出来了,这里的ifCondition只有一个值,所以跳过了。

 

  优化完,会进行generate,把AST转化为函数:

    function generate(ast, options) {
        // save previous staticRenderFns so generate calls can be nested
        // code...

        staticRenderFns = prevStaticRenderFns;
        onceCount = prevOnceCount;
        return {
            render: ("with(this){return " + code + "}"),
            staticRenderFns: currentStaticRenderFns
        }
    }

    // 处理静态、v-once、v-for、v-if、template/slot
    function genElement(el) {
        if (el.staticRoot && !el.staticProcessed) {
            return genStatic(el)
        } else if (el.once && !el.onceProcessed) {
            return genOnce(el)
        } else if (el.for && !el.forProcessed) {
            return genFor(el)
        } else if (el.if && !el.ifProcessed) {
            return genIf(el)
        } else if (el.tag === 'template' && !el.slotTarget) {
            return genChildren(el) || 'void 0'
        } else if (el.tag === 'slot') {
            return genSlot(el)
        } else {
            // component or element
            // code...
            return code
        }
    }

  案例中的v-if、v-once在这里都会被特殊处理,首先看一下v-if:

    function genIf(el) {
        el.ifProcessed = true; // avoid recursion
        return genIfConditions(el.ifConditions.slice())
    }

    function genIfConditions(conditions) {
        if (!conditions.length) {
            return '_e()'
        }
        // 取出v-if对应的表达式 => vIfIter
        var condition = conditions.shift();
        if (condition.exp) {
            return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions)))
        } else {
            return ("" + (genTernaryExp(condition.block)))
        }

        // v-if with v-once should generate code like (a)?_m(0):_m(1)
        // 处理v-once与v-if同时出现的情况
        function genTernaryExp(el) {
            return el.once ? genOnce(el) : genElement(el)
        }
    }

  函数首先会将ifCondition标记为true,防止递归处理子节点时候又跳到这个函数,接下来会判断该节点是否同时有v-once,这里没有,调用genElement处理其余属性。

  在genData中,会对class与style进行处理,其中也包括v-bind绑定的属性:

    function genData$2(el) {
        var data = '';
        // 静态style
        if (el.staticStyle) {
            data += "staticStyle:" + (el.staticStyle) + ",";
        }
        // v-bind:style => styleObject
        if (el.styleBinding) {
            data += "style:(" + (el.styleBinding) + "),";
        }
        return data
    }

  除去v-if,其余处理被包装为一个字符串,如图:

  

  这里对v-if的render函数包装会暂时停下来,优先递归处理子节点,简单看一下函数:

    function genChildren(el, checkSkip) {
        var children = el.children;
        if (children.length) {
            var el$1 = children[0];
            // 单纯的v-for子节点
            if (children.length === 1 &&
                el$1.for &&
                el$1.tag !== 'template' &&
                el$1.tag !== 'slot') {
                return genElement(el$1)
            }
            // 对每一个子节点做判断
            var normalizationType = checkSkip ? getNormalizationType(children) : 0;
            return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
        }
    }

  子节点这里做了一点优化,如果只是单纯的v-for,就不做类型区别判断,直接生成render函数,就像之前解析v-for的案例一样,所以上一篇是没有跑这个的。

  这一次不太一样,有3个子节点,加上两个空白换行,共有5个。

  跳过v-for的判断,会进入子节点类型分类: 

    // determine the normalization needed for the children array.
    // 0: no normalization needed
    // 1: simple normalization needed (possible 1-level deep nested array)
    // 2: full normalization needed
    function getNormalizationType(children) {
        var res = 0;
        for (var i = 0; i < children.length; i++) {
            var el = children[i];
            if (el.type !== 1) {
                continue
            }
            // el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
            if (needsNormalization(el) ||
                (el.ifConditions && el.ifConditions.some(function(c) {
                    return needsNormalization(c.block);
                }))) {
                res = 2;
                break
            }
            // !isPlatformReservedTag$1 => isHTMLTag(tag) || isSVG(tag) => 非内置标签
            if (maybeComponent(el) ||
                (el.ifConditions && el.ifConditions.some(function(c) {
                    return maybeComponent(c.block);
                }))) {
                res = 1;
            }
        }
        return res
    }

  函数的作用可以直接看注释,不懂也没关系,这里简单解释一下,该函数将子节点分为三类:

  1、默认普通子节点

  2、包含v-for属性或者是template/slot的模板标签

  3、非内置标签,即自定义组件

  三类子节点分别对应res的0、1、2。

  这里都是普通子节点,res返回0,跳出来后返回的字符串后面就不会拼接一个类型,直接拼接空字符。

    ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))

  这里接下来会调用genNode进行子节点处理:

    function genNode(node) {
        if (node.type === 1) {
            return genElement(node)
        } else {
            return genText(node)
        }
    }

  该函数对不同类型的子节点做不同处理,首先是input标签,进入genElement函数,之前有看过该函数,这里有一点不一样的是会进入genDirectives,处理内置指令v-show、v-model:

    function genDirectives(el) {
        var dirs = el.directives;
        if (!dirs) {
            return
        }
        var res = 'directives:[';
        var hasRuntime = false;
        var i, l, dir, needRuntime;
        for (i = 0, l = dirs.length; i < l; i++) {
            dir = dirs[i];
            needRuntime = true;
            // html/model/text || bind/cloak
            var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
            if (gen) {
                // compile-time directive that manipulates AST.
                // returns true if it also needs a runtime counterpart.
                needRuntime = !!gen(el, dir, warn$3);
            }
            if (needRuntime) {
                hasRuntime = true;
                // 很长很长的拼接
                res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
            }
        }
        if (hasRuntime) {
            return res.slice(0, -1) + ']'
        }
    }

  函数意思很简单,取出对应的内置指令对象,判断是否存在gen函数,然后进行长拼接,第一个v-show不存在gen函数,所以直接拼接,结果如图:

  这里针对第一个指令进行了拼接,接下来还有一个v-model,这个指令存在对应的gen函数,所以流程会多一步:

    function model(el, dir, _warn) {
        warn$1 = _warn;
        var value = dir.value;
        var modifiers = dir.modifiers;
        var tag = el.tag;
        var type = el.attrsMap.type;

        {
            // 有傻逼会用:type绑定input的类型吗???
            // 另外type=file是无法用v-model监听的
        }

        // 针对不同的input类型做处理
        if (tag === 'select') {
            genSelect(el, value, modifiers);
        } else if (tag === 'input' && type === 'checkbox') {
            genCheckboxModel(el, value, modifiers);
        } else if (tag === 'input' && type === 'radio') {
            genRadioModel(el, value, modifiers);
        } else if (tag === 'input' || tag === 'textarea') {
            genDefaultModel(el, value, modifiers);
        } else if (!config.isReservedTag(tag)) {
            genComponentModel(el, value, modifiers);
            // component v-model doesn't need extra runtime
            return false
        } else {
            // 不支持v-model的标签
        }

        // ensure runtime directive metadata
        return true
    }

  这里首先做了错误预判,type属性的无法动态绑定的,file上传的类型v-mode也不起作用,然后针对不同类型的input标签,包括select/checkbox/radio/text/textarea做处理。

  由于只是个没有type的<input/>,所以默认为text并进入genDefaultModel分支:

    // value => vModel
    function genDefaultModel(el, value, modifiers) {
        var type = el.attrsMap.type;
        var ref = modifiers || {};
        var lazy = ref.lazy;
        var number = ref.number;
        var trim = ref.trim;
        var needCompositionGuard = !lazy && type !== 'range';
        var event = lazy ?
            'change' :
            type === 'range' ?
            RANGE_TOKEN :
            'input';
        // 获取对应input标签的值
        // 包含处理后缀为trim、number
        var valueExpression = '$event.target.value';
        if (trim) {
            valueExpression = "$event.target.value.trim()";
        }
        if (number) {
            valueExpression = "_n(" + valueExpression + ")";
        }

        var code = genAssignmentCode(value, valueExpression);
        if (needCompositionGuard) {
            code = "if($event.target.composing)return;" + code;
        }

        addProp(el, 'value', ("(" + value + ")"));
        addHandler(el, event, code, null, true);
        if (trim || number || type === 'number') {
            addHandler(el, 'blur', '$forceUpdate()');
        }
    }

  这里生成了获取对应input值得表达式,正常为$event.target.value,$event代表原生的事件,如果有trim/number后缀,会自动调用去空白与数字化函数。

  接着调用genAssignmentCode,这里涉及一个idx属性,没搞懂具体是什么情况会出现:

    // value => vModel
    function genAssignmentCode(value, assignment) {
        // {exp:vModel,idx:null}
        var modelRs = parseModel(value);
        if (modelRs.idx === null) {
            return (value + "=" + assignment)
        } else {
            // code...
        }
    }

    function parseModel(val) {
        str = val;
        len = str.length;
        index$1 = expressionPos = expressionEndPos = 0;

        if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
            return {
                exp: val,
                idx: null
            }
        }

        // code...
    }

  针对本例中简单的属性设置,会直接返回一个对象。

  此时code为一个字符串表示式:,很简单就是获取目标节点的值。

  下面有一个关于composing的判断,这个属性在MDN是这样解释的:

  

  简单来讲,这是一个只读属性,发生在compositionstart事件之后,compositionend事件之前,这两个事件类似于keyup、keydown,该属性指的是IDE输入过程中,如图:

  

  这里ddd是一个编辑中的状态,触发了composing事件,此时v-model是不响应的,所以可以看到拼接的字符串如下所示:

  

  即:如果文字在编辑中,那么直接返回。

 

  接下来会调用addProp函数:

    addProp(el, 'value', ("(" + value + ")"));

  一句代码函数,看了就懂是干嘛用的:

    // el => dom
    // name => value
    // value => (vModel)
    function addProp(el, name, value) {
        (el.props || (el.props = [])).push({
            name: name,
            value: value
        });
    }

  判断是否有props属性,并添加一个对象到属性中,值得注意的是,这里的值用一个括号包装起来了,所以是(vModel)。

 

  下面是给dom绑定一个事件:

    // event => input
    // code => if(...)...
    addHandler(el, event, code, null, true);

    function addHandler(el, name, value, modifiers, important, warn) {
        // warn prevent and passive modifier
        // code...

        // 检测事件是否带有capture/once/paasive后缀
        // code...

        var events;
        // 没有修饰符生成一个空对象
        if (modifiers && modifiers.native) {
            delete modifiers.native;
            events = el.nativeEvents || (el.nativeEvents = {});
        } else {
            events = el.events || (el.events = {});
        }
        // 生成一个事件对象
        var newHandler = {
            value: value,
            modifiers: modifiers
        };
        var handlers = events[name];
        /* istanbul ignore if */
        if (Array.isArray(handlers)) {
            important ? handlers.unshift(newHandler) : handlers.push(newHandler);
        } else if (handlers) {
            events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
        } else {
            events[name] = newHandler;
        }
    }

  这里会在当前dom节点的ast上添加一个events属性,值为之前生成的表达式字符串:

  处理完这个,最后会对trim、number、type=number做特殊处理,每次响应进行强制更新,格式化输入值。

 

  至此,v-model指令处理完毕,回到genDirectives拼接到了v-show字符串的后面:

  

  只要有属性,hasRuntime就会被置为true,因为拼接的字符串最后是逗号,在return的时候需要去除这个逗号并添加一个中括号完整表达式。

 

  先这样吧,下次搞。

  

posted @ 2017-08-14 16:52  书生小龙  阅读(565)  评论(0编辑  收藏  举报