parse函数(vue源码分析4)

parse函数(vue源码分析4)

theme: juejin
highlight: arduino-light

以下代码和分析过程需要结合vue.js源码查看,通过打断点逐一比对。

1. 执行流程

我们先梳理下执行流程:

// 执行1 (末尾的mount)
Vue.prototype.$mount = function (
        el,
        hydrating
        ) {
    
    
                var ref = compileToFunctions(  //执行的位置
                    template,
                    {
    
    
                        shouldDecodeNewlines: shouldDecodeNewlines,
                        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, 
                        delimiters: options.delimiters, 
                        comments: options.comments 
                    },
                    this
        }
    };
// 执行2
var compileToFunctions = ref$1.compileToFunctions;
// 执行3
var ref$1 = createCompiler(baseOptions);
// 执行4
var createCompiler = createCompilerCreator(
        function baseCompile(
            template,
            options
        ) {
    
    
            //最终走到这里,返回ast树
            var ast = parse(template.trim(), options); 
     });

最后走到目标函数,parse。

2. ast

为了直接的了解到parse做了什么,我们先看一下它返回ast的结构,以下是一个input标签的ast树:

AST的每层的element,包含自身节点的信息(tag,attr等),同时parent,children分别指向其父element和子element,层层嵌套,形成一棵树。

{
    
    
    "type": 1,
    "tag": ""input"",
    "attrsList":[
        {
    
    
            "name": "class",
            "value": "full-input"
        },
        {
    
    
            "name": "type",
            "value": "text"
        },
        {
    
    
            "name": "v-model",
            "value": "message"
        },
        {
    
    
            "name": "placeholder",
            "value": "请输入"
        }
    ],
    "attrsMap": {
    
    
        "class": "full-input",
        "type": "text",
        "v-model": "message",
        "placeholder": "请输入"
    },
    "parent": {
    
    
        "type": 1,
        "tag": "div",
        "attrsList": [
            {
    
    
                "name": "id",
                "value": "app"
            }
        ],
        "attrsMap": {
    
    
            "id": "app"
        },
        "children": [
            {
    
    
                "type": 2,
                "expression": "\" \"+_s(message)+\"\\n        \"",
                "tokens": [
                    " ",
                    {
    
    
                        "@binding": "message"
                    },
                    "\n        "
                ],
                "text": " {
    
    { message }}\n        "
            }
        ],
        "plain": false,
        "attrs": [
            {
    
    
                "name": "id",
                "value": "\"app\""
            }
        ]
      }
}

3. 简析parse

parse函数非常的长,我们先把主要代码列一下,看下它的结构和逻辑

它主要是把template编译成Ast树。

我们先缩减代码:

function parse(
        template, 
        options
    ) {
    
    
        // 标签相关的判断
        platformIsPreTag = options.isPreTag || no;
        ...

        //定义AST模型对象
        var stack = [];
        ...
       
        function closeElement(element) {
    
    ...} //克隆节点
        // 主要的解析方法
        parseHTML(
            template, 
            {
    
    
                //工具函数
                warn: warn$2, 
                ...
                start: function start(
                    tag, 
                    attrs,  
                    unary  
                ) {
    
     ... },
                end: function end() {
    
     ... },
                chars: function chars(text) {
    
    ...},//把text添加到属性节点,ast模板数据
                comment: function comment(text) {
    
    ...}//把text添加到注释节点,ast模板数据
            }
        );

        return root
    }

4. 详解parse

在parse函数中,先是定义一些变量,如何调用parseHTML函数对模板进行解析。
调用parse HTML函数时传递的options参数中的start函数是构建一个元素类型的AST节点并将它压入栈中,chars函数是构建一个文本类型的AST节点,comment函数是构建一个注释类型的AST节点,end是从栈中取出一个节点以便完成AST树状结构的构建。

    function parse(
        template, //html 模板 例:"<div id=\"app\">\n <!--this is comment--> {
    
    { message }}\n </div>"
        options //例: {shouldDecodeNewlines: false, shouldDecodeNewlinesForHref: false, delimiters: '', comments: '', warn: ƒ}
    ) {
    
    
        
        warn$2 = options.warn || baseWarn; //警告日志函数,通过finalOptions.warn挂载的
      
        //【说明1 options.isPreTag】
        platformIsPreTag = options.isPreTag || no;  // tag === 'pre';
        platformMustUseProp = options.mustUseProp || no; // 检验标签和属性是否对应
        platformGetTagNamespace = options.getTagNamespace || no; //判断 tag 是否是svg或者math 标签
        debugger
        // 遍历options.modules每一项,取key为'transformNode'项的value组成一个数组,不存在返回空数组
        transforms = pluckModuleFunction(options.modules, 'transformNode');// modules对应的是modules$1
        //定义在model$2中,model$2又挂载在modules$1,modules$1又挂载在modules
        preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
        // 这个没搞清干嘛用的
        postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');

        //【说明2 delimiters】
        delimiters = options.delimiters;
        var stack = []; // 标签堆栈
        //【说明3 preserveWhitespace】
        var preserveWhitespace = options.preserveWhitespace !== false; 
        var root;
        var currentParent; //当前父节点
        var inVPre = false;  //标记 标签是否还有 v-pre 指令
        var inPre = false; //  判断标签是否是pre
        var warned = false;
        
        // 可忽略-----
        function warnOnce(msg) {
    
    
            if (!warned) {
    
    
                warned = true;
                warn$2(msg); //警告日志函数
            }
        }

        //克隆节点
        function closeElement(element) {
    
    
            if (element.pre) {
    
    
                inVPre = false;
            }
            if (platformIsPreTag(element.tag)) {
    
     //element.tag === 'pre';
                inPre = false;
            }
            // postTransforms数组为空所以不执行这里
            for (var i = 0; i < postTransforms.length; i++) {
    
    
                postTransforms[i](element, options);
            }
        }
        // 后一节单独解析
        parseHTML(
            template
        )
        return root
    }

【说明1 options.isPreTag】

options的原型上有isPreTag方法。

扫描二维码关注公众号,回复: 13556643 查看本文章
  • 它一开始定义在baseOptions里面(其它几个属性同理)
  • 然后通过var finalOptions = Object.create(baseOptions)baseOptions的属性挂载到了finalOptions原型上
  • 接着在createCompilerCreator执行baseCompile(template, finalOptions),往前推找到入参函数 baseCompile(template, options),所以这里parse的options其实就是finalOptions。

【说明2 delimiters】

 <body>
    <div id="app">
        <!-- 插值符改为了es6插值形式了 -->
        ${num}
        <p><button @click="add">ADD</button></p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
     
      let app = new Vue({
     
     
        el: '#app',
        data:{
     
     
            num:1
        },
        methods:{
     
     
            add(){
     
         
                this.num++;
            }
        },
        delimiters:['${','}'] //将常用{
     
     {}}格式变成${}的格式
      })
    </script>
  </body>

【说明3 preserveWhitespace】

preserveWhitespace属于模板编译器的选项:让模板的元素和元素之间没有空格。

可以在vue-loader中设置:

{
    
    
   vue: {
    
    
          reserveWhitespace: false
        }
}
         

5.parseHTML

HTML解析,其接受template以及一些配置参数其中最主要的就是 start 和 end ,在parse HTML 内部对template 进行了标签拆分,拆分的时候又会根据不同类型的标签进行处理, 其主要分为几个阶段:

  • 起始标签开合
  • 起始标签闭合
  • 结束标签闭合
  • 文本解析

在parseHTML函数中,会维护一个栈,这个栈用来记录DOM的层级关系。解析HTML模板时是一个循环的过程,模板解析时又分为纯文本内容元素与非纯文本内容元素来进行处理。

5-1. 简析

parse里面的核心方法,我们也先看下结构,这里执行parseHTML,传入2个参数,一个是template,一个是对象,对象里面是一系列的配置和函数。

parseHTML(
    template, //字符串模板
    {
    
    
        warn: warn$2,
        expectHTML: options.expectHTML,
        isUnaryTag: options.isUnaryTag, 
        canBeLeftOpenTag: options.canBeLeftOpenTag, 
        shouldDecodeNewlines: options.shouldDecodeNewlines, 
        shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, 
        shouldKeepComment: options.comments, 

        start: function start( tag, attrs, unary ) {
    
    },
        end: function end() {
    
    },
        chars: function chars(text) {
    
    },
        comment: function comment(text) {
    
    
        }
}

接下来依次对start,end,chars,comment进项详细解析

5-2. start

start函数依次很长,它的主要功能是:

start: function start(
                    tag, //标签名称 例:'div'
                    attrs,   //标签属性 例:[{name: "id", value: "app"}]
                    unary  // 是否是单标签
                ) {
    
    
         
                    // 如果有父节点,且有命名空间,则返回;否则,tag 是svg或者math 标签则返回true,否则返回undefined
                    var ns = (currentParent && currentParent.ns) ||
                        platformGetTagNamespace(tag);

                    // 可以忽略
                    if (isIE && ns === 'svg') {
    
    
                        attrs = guardIESVGBug(attrs);
                    }

                    //转换属性,把数组属性转换成对象属性,返回对象 AST元素
                    
                    //创建一个ast标签dom
                    // 【说明1 createASTElement】
                    var element = createASTElement(tag, attrs, currentParent);

                    if (ns) {
    
     //判断 tag 是否是svg或者math 标签
                        element.ns = ns;
                    }


                    // 可忽略--------
                    // 是否是style||script标签
                    // 是否在服务器node环境下
                    if (
                        isForbiddenTag(element) &&
                        !isServerRendering()
                    ) {
    
    
                        element.forbidden = true;
                        "development" !== 'production' && warn$2(
                            'Templates should only be responsible for mapping the state to the ' +
                            'UI. Avoid placing tags with side-effects in your templates, such as ' +
                            "<" + tag + ">" + ', as they will not be parsed.'
                        );
                    }

                   
                    for (var i = 0; i < preTransforms.length; i++) {
    
    
                        // preTransforms attr属性的函数组成的数组,判断是否input标签并满足相应的属性
                        // 【说明3 preTransforms】
                        element = preTransforms[i](element, options) || element;
                    }

                    if (!inVPre) {
    
     //如果  标签 没有 v-pre 指令
                        processPre(element);    //检查标签是否有v-pre 指令 含有 v-pre 指令的标签里面的指令则不会被编译
                        if (element.pre) {
    
     //标记 标签是否还有 v-pre 指令
                            inVPre = true; //如果标签有v-pre 指令 则标记为true
                        }
                    }
                    if (platformIsPreTag(element.tag)) {
    
     //  判断标签是否是pre 如果是则返回真
                        inPre = true;
                    }
                    if (inVPre) {
    
     //如果含有 v-pre 指令
                        //浅拷贝属性 把虚拟dom的attrsList拷贝到attrs中,如果没有pre块,标记plain为true
                        processRawAttrs(element);
                    } else if (!element.processed) {
    
    
                        // structural directives  指令
                        //判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中
                        processFor(element);
                        //获取v-if属性,为el虚拟dom添加 v-if,v-eles,v-else-if 属性
                        processIf(element);
                        //获取v-once 指令属性,如果有有该属性 为虚拟dom标签 标记事件 只触发一次则销毁
                        processOnce(element);
                        // element-scope stuff

                        //校验属性的值,为el添加muted, events,nativeEvents,directives,  key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
                        processElement(element, options);
                    }

                    //检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性
                    function checkRootConstraints(el) {
    
    
                        {
    
    
                            if (el.tag === 'slot' || el.tag === 'template') {
    
    
                                warnOnce(
                                    "Cannot use <" + (el.tag) + "> as component root element because it may " +
                                    'contain multiple nodes.'
                                );
                            }
                            if (el.attrsMap.hasOwnProperty('v-for')) {
    
    
                                warnOnce(
                                    'Cannot use v-for on stateful component root element because ' +
                                    'it renders multiple elements.'
                                );
                            }
                        }
                    }

                    // tree management
                    if (!root) {
    
    
                        root = element;
                        //检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性
                        checkRootConstraints(root);
                    } else if (!stack.length) {
    
    
                        // allow root elements with v-if, v-else-if and v-else
                        //允许根元素带有v-if、v-else-if和v-else
                        if (root.if && (element.elseif || element.else)) {
    
    
                            checkRootConstraints(element);//检查根约束 根节点不能是slot或者template标签,并且不能含有v-for 属性

                            //为if指令添加标记
                            addIfCondition(
                                root, //根节点
                                {
    
    
                                    exp: element.elseif, //view 试图中的elseif 属性
                                    block: element //当前的虚拟dom
                                }
                            );
                        } else {
    
    
                            warnOnce(
                                "Component template should contain exactly one root element. " +
                                "If you are using v-if on multiple elements, " +
                                "use v-else-if to chain them instead."
                            );
                        }
                    }
                    //如果currentParent父节点存在。并且element.forbidden不存在
                    if (
                        currentParent &&
                        !element.forbidden   //如果是style或者是是script 标签并且type属性不存在 或者存在并且是javascript 属性 的时候返回真
                    ) {
    
    
                        if (element.elseif || element.else) {
    
     //如果有elseif或者else属性的时候
                            //找到上一个兄弟节点,如果上一个兄弟节点是if,则下一个兄弟节点则是elseif
                            processIfConditions(element, currentParent);
                        } else if (element.slotScope) {
    
     // scoped slot 作用域的槽

                            currentParent.plain = false;
                            //获取slotTarget作用域标签,如果获取不到则定义为default
                            var name = element.slotTarget || '"default"';
                            (currentParent.scopedSlots || (currentParent.scopedSlots = {
    
    }))[name] = element;
                        } else {
    
    
                            //如果父节点存在currentParent则在父节点添加一个子节点,并且
                            currentParent.children.push(element);
                            //当前节点上添加parent属性
                            element.parent = currentParent;
                        }
                    }

                    // var unary = isUnaryTag$$1(tagName) || //函数匹配标签是否是 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen, link,meta,param,source,track,wbr'
                    //     !!unarySlash; //如果是/> 则为真
                    //如果当前标签不是单标签,也不是闭合标签,就标志当前currentParent 是当前标签

                    if (!unary) {
    
    
                        currentParent = element;
                        //为parse函数 stack标签堆栈 添加一个标签
                        stack.push(element);
                    } else {
    
    
                        //克隆节点
                        closeElement(element);

                    }
                },

【说明1 createASTElement】,将start函数传进来的参数转换,通过标签,属性和父节点来创建 AST元素(对象)

    function createASTElement(
        tag,  //标签名称 例:'div'
        attrs, //标签属性 例:[{name: "id", value: "app"}]
        parent // 当前父节点
    ) {
    
    
        return {
    
    
            type: 1, //dom 类型
            tag: tag, 
            attrsList: attrs,
            /**
             * 对象属性 把数组对象转换成 对象  例:
             * attrsMap: {id: "app"}
             */
            //【说明2 makeAttrsMap】
            attrsMap: makeAttrsMap(attrs), 
            parent: parent, // 当前父节点
            children: []
        }
    }

最终返回的对象:

    {
        attrsList: [{name: "id", value: "app"}]
        attrsMap: {id: "app"}
        children: []
        parent: undefined
        tag: "div"
        type: 1
    }

【说明2 makeAttrsMap】, 把数组对象转换成 对象 例如:

[{name: "id", value: "app"}]

转换成

{ id: app }

    function makeAttrsMap(attrs) { //标签属性 例:[{name: "id", value: "app"}]
        debugger
        var map = {};

        for (var i = 0, l = attrs.length; i < l; i++) {
            // 可以忽略------
            if (
                "development" !== 'production' &&
                map[attrs[i].name] && !isIE && !isEdge
            ) {
                warn$2('duplicate attribute: ' + attrs[i].name);
            }
            //-----------

            map[attrs[i].name] = attrs[i].value;
        }
        return map
    }

【说明3 preTransforms】, attr属性的函数组成的数组

//定义在model$2中,model$2又挂载在modules$1,modules$1又挂载在modules
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');

var model$2 = {
    
    
        preTransformNode: preTransformNode
}
  /**
     * Expand input[v-model] with dyanmic type bindings into v-if-else chains
     * 使用dyanmic类型绑定将输入[v-model]展开到v-if-else链中
     * Turn this:
     * 把这个
     *   <input v-model="data[type]" :type="type">
     * into this: 到这个
     *   <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]">
     *   <input v-else-if="type === 'radio'" type="radio" v-model="data[type]">
     *   <input v-else :type="type" v-model="data[type]">
     *
     */
function preTransformNode(
        /**
         * el=》虚拟dom  例:{type: 1, tag: "div", attrsList: Array(1), attrsMap: {…}, 
                             parent: undefined, …}
         * options=》基础配置 例:{shouldDecodeNewlines: false, shouldDecodeNewlinesForHref: false, 
           delimiters: undefined, comments: undefined, warn: ƒ}
         */
        el,
        options
    ) {
    
    
        
        if (el.tag === 'input') {
    
     
            // map = {class: "full-input", type: "text", v-model: "message", placeholder: "请输入"}
            var map = el.attrsMap; 
            // 如果属性中没有v-model 则退出,说明这整个函数都了为了input标签
            // 的v-model指令服务的
            if (!map['v-model']) {
    
    
                return
            }

            var typeBinding; //类型
            // 如果有动态属性type
            if (map[':type'] || map['v-bind:type']) {
    
     
                // 【说明3-1 getBindingAttr】
                // 返回,例:typeBinding = _f("recordType")(texts)
                typeBinding = getBindingAttr(el, 'type'); //获取类型属性值
            }
            if (!map.type && !typeBinding && map['v-bind']) {
    
     //如果获取不到type属性也获取不到v-bind:type属性,可以获取到v-bind属性
                typeBinding = "(" + (map['v-bind']) + ").type"; //获取到v-bind的值,比如v-bind等于abc变成  (abc).type
            }

            if (typeBinding) {
    
             //判断 typeBinding 是否存在
                var ifCondition = getAndRemoveAttr(el, 'v-if', true); //获取v-if值
                var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : ""; //判断if是否有值比如v-if="flag" 如果有 变成   &&(flag)
                var hasElse = getAndRemoveAttr(el, 'v-else', true) != null; //获取 v-else 属性值 标志 如果有有 可能是 ''   ,  ''!= null 为真
                var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true); //获取v-else-if 的值


                // 1. 处理type为checkbox   克隆 创建 checkbox ast 元素
                var branch0 = cloneASTElement(el);
                //判断获取v-for属性是否存在如果有则转义 v-for指令 把for,alias,iterator1,iterator2属性添加到虚拟dom中
                processFor(branch0);
                //添加type 属性 值为checkbox
                addRawAttr(branch0, 'type', 'checkbox');
                //校验属性的值,为el添加muted, events,nativeEvents,directives,  key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
                processElement(branch0, options);
                branch0.processed = true; // prevent it from double-processed 防止它被重复处理
                branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra; //     ifConditionExtra 是 判断if是否有值比如v-if="flag" 如果有 变成   &&(flag)  最终合并成  ((abc).type)===checkbox&&(flag)
                //为if指令添加标记
                addIfCondition(
                    branch0,  //虚拟dom
                    {
    
    
                        exp: branch0.if, //if指令的标志  "(_f("recordType")(texts))==='checkbox'"
                        block: branch0 //虚拟dom
                    }
                );


                // 处理type为 radio元素
                var branch1 = cloneASTElement(el);
                //删除v-for 属性
                getAndRemoveAttr(branch1, 'v-for', true);
                //添加type 属性
                addRawAttr(branch1, 'type', 'radio');
                //校验属性的值,为el 虚拟dom添加muted, events,nativeEvents,directives,  key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
                processElement(branch1, options);
                //为if指令添加标记
                addIfCondition(branch0, {
    
    
                    exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
                    block: branch1
                });


                // 3. 处理type为 其它类型元素,类型为变量
                var branch2 = cloneASTElement(el);
                //删除v-for属性
                getAndRemoveAttr(branch2, 'v-for', true);
                //添加:type 属性
                addRawAttr(branch2, ':type', typeBinding);
                //校验属性的值,为el添加muted, events,nativeEvents,directives,  key, ref,slotName或者slotScope或者slot,component或者inlineTemplate 标志 属性
                processElement(branch2, options);
                //为if指令添加标记
                addIfCondition(
                    branch0,
                    {
    
    
                        exp: ifCondition, //v-if 属性值
                        block: branch2 //ast元素 需要渲染的ast子组件
                    }
                );
                debugger

                //判断是else还是elseif
                if (hasElse) {
    
    
                    branch0.else = true;
                } else if (elseIfCondition) {
    
    
                    branch0.elseif = elseIfCondition;
                }
                //返回转换过虚拟dom的对象值
                return branch0
            }
        }
    }

【说明3-1 getBindingAttr】, 获取 :属性 或者v-bind:属性,或者获取属性 移除传进来的属性name,并且返回获取到 属性的值

    function getBindingAttr(
        el, //虚拟dom  {type: 1, tag: "input", attrsList: Array(4), attrsMap: {…}, parent: {…}, …}
        name, // 例:'type',"class",'slot',"key","ref"
        getStatic // false
    ) {
    
    
        
        /**
         * 获取 :属性 或者v-bind:属性,并在el.attrsList删除它,
         * 它获取的是一个动态的变量,因为我们绑定就是一个变量,
         * 例如我们在data中定义了一个texts,
         * 那么这里就是拿到'texts'这个变量。
         */
        //【说明3-1-1 getAndRemoveAttr】
        var dynamicValue = getAndRemoveAttr(el, ':' + name) ||
            getAndRemoveAttr(el, 'v-bind:' + name);
 
        if (dynamicValue != null) {
    
    
            /*
             *处理value 解析成正确的value,把过滤器 转换成vue 虚拟dom的解析方法函数 比如把过滤器 ' ab | c | d' 转换成 _f("d")(_f("c")(ab))
             * 表达式中的过滤器解析 方法
             */
             //【说明3-1-2 parseFilters】
             // 返回,例:"_f("recordType")(texts)
            let parseFiltersValue = parseFilters(dynamicValue);
  
            return parseFiltersValue
        } else if (getStatic !== false) {
    
    
            //移除传进来的属性name,并且返回获取到 属性的值
            var staticValue = getAndRemoveAttr(el, name);
            if (staticValue != null) {
    
    
                //转换成字符串
                return JSON.stringify(staticValue)
            }
        }
    }

【说明3-1-1 getAndRemoveAttr】, 从el.attrsList移除传进来的属性name,并且返回获取到 属性的值

    function getAndRemoveAttr(
        el, //el  虚拟dom
        name,//属性名称 需要删除的属性 name,获取值的name属性
        removeFromMap //是否要删除属性的标志 undefined
    ) {
        var val;
        // 如果属性map中的对应属性有值
        if ((val = el.attrsMap[name]) != null) { 
            /**
             *  例:list:
             *  [{name: "class", value: "full-input"},
             *  {name: ":type", value: "texts"}]
             */
            var list = el.attrsList;
            // 从el.attrsList删除传入的属性项
            for (var i = 0, l = list.length; i < l; i++) {
                if (list[i].name === name) {
                    list.splice(i, 1); 
                    break
                }
            }
        }
        if (removeFromMap) { //是否要删除属性的标志
            delete el.attrsMap[name];
        }
        return val
    }

【说明3-1-2 parseFilters】,处理value 解析成正确的value,把过滤器 转换成vue 虚拟dom的解析方法函数 , 比如把过滤器 ' ab | c | d' 转换成_f("d")(_f("c")(ab))

//匹配 ) 或 . 或 + 或 - 或 _ 或 $ 或 ]
var validDivisionCharRE = /[\w).+\-_$\]]/;
function parseFilters(
        /**
         * 例如我们的html中:
         * <input :type="texts | recordType" v-model='message'/>
         * 这里是exp就是:"texts | recordType"
         */
        exp
        ) {
    
    
        // 是否在 ''中
        var inSingle = false;
        // 是否在 "" 中
        var inDouble = false;
        // 是否在 ``
        var inTemplateString = false;
        //  是否在 正则 \\ 中
        var inRegex = false;
        // 是否在 {
    
    { 中发现一个 culy加1 然后发现一个 } culy减1 直到culy为0 说明 { .. }闭合
        var curly = 0;
        // 跟{
    
    { 一样 有一个 [ 加1 有一个 ] 减1
        var square = 0;
        // 跟{
    
    { 一样 有一个 ( 加1 有一个 ) 减1
        var paren = 0;
        var lastFilterIndex = 0;
        var c, prev, i, expression, filters;


        // for循环传入的value字符串
        // 0x27 == ' ; 0x5C == \ ; 0x22 == " ; 0x60 == ` ; 0x2f == / ; 0x7c == |
        for (i = 0; i < exp.length; i++) {
    
    
            prev = c;
            // 遍历拿到每个过滤字符串的code码
            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
            ) {
    
    
                /*
                 初始化时,expression为空,说明这是第一个 管道符号 "|",
                 再次遇到 | 因为前面 expression = 'texts '
                 执行  pushFilter()
                 */
                if (expression === undefined) {
    
    
                    // first filter, end of expression
                    // 过滤器表达式 就是管道符号之后开始
                    // 此例值为:lastFilterIndex = 7, i = 6
                    lastFilterIndex = i + 1;
                    // 存储过滤器的 表达式,如:"texts"
                    // 这里匹配如果字符串是 'ab | c' 则把ab匹配出来
                    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 // `
                    // 解析为( 时,paren 计数加一, 通过 paren 是否为0判断 () 是否闭合
                    case 0x28: paren++; break                 // (
                    // 解析为 )时,paren 计数减一
                    case 0x29: paren--; break                 // )
                    // 解析为 [ 时, square 计数加一 ,通过 square 是否为0判断 [] 是否闭合
                    case 0x5B: square++; break                // [
                    // 解析为 ] 时, square 计数减一
                    case 0x5D: square--; break                // ]
                    // 解析为 { 时, curly 计数加一, 通过 curly 是否为0判断 {} 是否闭合
                    case 0x7B: curly++; break                 // {
    
    
                    // 解析为 } 时, curly 计数减一,
                    case 0x7D: curly--; break                 // }
                  }
                  // 如果是 / ,向前找第一个非空格的字符
                  if (c === 0x2f) {
    
     // /
                    let j = i - 1
                    let p
                    // find first non-whitespace prev char
                    for (; j >= 0; j--) {
    
    
                      p = exp.charAt(j)
                      if (p !== ' ') break
                    }
                    // 如果 p 不存在或者正则校验不通过,则说明是正则
                    if (!p || !validDivisionCharRE.test(p)) {
    
    
                      inRegex = true
                    }
                  }
        }

        // 如果最终没有过滤表达式,说明不符合过滤条件,
        // 直接将传入的字符串当作输出的返回值
        if (expression === undefined) {
    
     // exp = "texts | recordType"
            expression = exp.slice(0, i).trim();
        } else if (lastFilterIndex !== 0) {
    
    
            pushFilter();
        }
        // 获取当前过滤器的 并将其存储在filters 数组中,拿到的是后面的过滤函数组成的数组
        // 取 "texts | recordType | recordType1"  中的过滤函数组成:
        // filters = [ 'recordType' , 'recordType1']
        function pushFilter() {
    
    
            (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim());
            lastFilterIndex = i + 1;
        }

        debugger
        // "recordType"
        if (filters) {
    
    
  
            for (i = 0; i < filters.length; i++) {
    
    
                //把过滤器封装成函数 虚拟dom需要渲染的函数
                expression = wrapFilter(
                    expression, // "texts"
                    filters[i] //"recordType"
                    );
                // 返回,例:"_f("recordType")(texts)
            }
        }


        //返回值
        return expression
    }

猜你喜欢

转载自blog.csdn.net/weixin_39818813/article/details/118494573