• 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的时候需要去除这个逗号并添加一个中括号完整表达式。

      先这样吧,下次搞。

      

  • 相关阅读:
    Android SD卡读写文件
    Android 是什么
    Canvas 类
    Java IO流之字节流 FileInputStream
    Android中asset文件夹和raw文件夹区别
    随手收藏
    Java IO流
    Android私有文件资源文件的存取
    ubuntu 下的jdk安装
    Paint类
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7285974.html
Copyright © 2020-2023  润新知