zoukankan      html  css  js  c++  java
  • Vue源码后记-更多options参数(1)

      我是这样计划的,写完这个还写一篇数据变动时,VNode是如何更新的,顺便初探一下diff算法。

      至于vue-router、vuex等插件源码,容我缓一波好吧,vue看的有点伤。

      其实在之前讲其余内置指令有涉及到on事件绑定,这里再详细从头看一下事件绑定的正统流程吧!

      上html代码:

        <body>
            <div id='app'>
                <div @click.self.number='click'>
                    {{computedValue | filter}}
                </div>
            </div>
        </body>
        <script src='./vue.js'></script>
        <script>
            new Vue({
                el: '#app',
                data: {
                    value: 1
                },
                computed: {
                    computedValue: function() {
                        return this.value * 2
                    }
                },
                methods: {
                    click: function() {
                        console.log('click!');
                    }
                },
                filters: {
                    filter: function(value) {
                        if (!value) {
                            return;
                        }
                        return value * 4;
                    }
                }
            })
        </script>

      包含最常见的click事件,计算属性以及过滤器,钩子函数讲过就不写了,所有的函数都是最简单模式。

    1、mergeOptions

      源码看的有点恶心,流程都能背下来了。

      第一步是进行参数合并,传入的对象作为options与默认的options进行合并,并通过strat对象对应的方法处理:

        // parent => baseOptions
        // child => options
        function mergeOptions(parent, child, vm) {
            // code...
    
            function mergeField(key) {
                var strat = strats[key] || defaultStrat;
                options[key] = strat(parent[key], child[key], vm, key);
            }
            return options
        }

      这里主要是调用strat对象方法对每一个options的键进行处理,目前传进来有data、computed、methods、filters四个。

      data前面讲过,其余三个依次看一下。

      computed、methods

      两个参数对应同一个方法:

        strats.props =
            strats.methods =
            strats.computed = function(parentVal, childVal) {
                if (!childVal) {
                    return Object.create(parentVal || null)
                }
                if (!parentVal) {
                    return childVal
                }
                var ret = Object.create(null);
                extend(ret, parentVal);
                extend(ret, childVal);
                return ret
            };

      比较简单,将两个合并,直接返回该对象并挂载到vm上:

      

      filters

        function mergeAssets(parentVal, childVal) {
            var res = Object.create(parentVal || null);
            return childVal ?
                extend(res, childVal) :
                res
        }

      非常无趣的函数:

    2、initState

      vue上面属性添加完后,接下来是数据初始化阶段:

        function initState(vm) {
            vm._watchers = [];
            var opts = vm.$options;
            if (opts.props) { initProps(vm, opts.props); }
            if (opts.methods) { initMethods(vm, opts.methods); }
            if (opts.data) {
                initData(vm);
            } else {
                observe(vm._data = {}, true /* asRootData */ );
            }
            if (opts.computed) { initComputed(vm, opts.computed); }
            if (opts.watch) { initWatch(vm, opts.watch); }
        }

      可以看到这里对props、methods、data、computed、watch几个属性都做了初始化。

    methods

        function initMethods(vm, methods) {
            var props = vm.$options.props;
            for (var key in methods) {
                // 重新绑定上下文
                vm[key] = methods[key] == null ? noop : bind(methods[key], vm); {
                    if (methods[key] == null) {
                        // 函数对象值为空时
                    }
                    // 判断重名
                    if (props && hasOwn(props, key)) {
                        // warning
                    }
                }
            }
        }

      这个Init函数做了2件事,第一是对所有函数进行上下文绑定并挂载到vm上,第二是与props进行排重,不允许出现相同键名。

     initComputed

        function initComputed(vm, computed) {
            var watchers = vm._computedWatchers = Object.create(null);
    
            for (var key in computed) {
                var userDef = computed[key];
                var getter = typeof userDef === 'function' ? userDef : userDef.get; {
                    if (getter === undefined) {
                        // warn No getter function
                        getter = noop;
                    }
                }
                // create internal watcher for the computed property.
                watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);
    
                if (!(key in vm)) {
                    defineComputed(vm, key, userDef);
                } else {
                    // key查重
                }
            }
        }
    
        function defineComputed(target, key, userDef) {
            if (typeof userDef === 'function') {
                sharedPropertyDefinition.get = createComputedGetter(key);
                sharedPropertyDefinition.set = noop;
            } else {
                // setter、getter
            }
            Object.defineProperty(target, key, sharedPropertyDefinition);
        }
    
        function createComputedGetter(key) {
            return function computedGetter() {
                var watcher = this._computedWatchers && this._computedWatchers[key];
                if (watcher) {
                    if (watcher.dirty) {
                        watcher.evaluate();
                    }
                    if (Dep.target) {
                        watcher.depend();
                    }
                    return watcher.value
                }
            }
        }

      处理computed时,内部会new一个watcher来专门监听相关数据变动,然后用defineProperty在vm上声明一个对象。

    2、parseHTML

      合并完参数并做init初始化后,第二步应该是解析DOM字符串了。

      直接看关键点,外层的div跳过,只看里层的,首先是:

        <div @click.self.number='click'>

      切割函数为:

        var startTagMatch = parseStartTag();
        if (startTagMatch) {
            handleStartTag(startTagMatch);
            continue
        }

      第一次暴力切割后,直接以=分割属性:

      接下来进入handleStartTag => start => processAttrs

        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;
                // name => @click.self.number
                if (dirRE.test(name)) {
                    el.hasBindings = true;
                    // 修饰符
                    // modifiers => {self:true,number:true}
                    modifiers = parseModifiers(name);
                    if (modifiers) {
                        // name => @click
                        name = name.replace(modifierRE, '');
                    }
                    if (bindRE.test(name)) { // v-bind
                        // code...
                    } else if (onRE.test(name)) { // v-on
                        name = name.replace(onRE, '');
                        addHandler(el, name, value, modifiers, false, warn$2);
                    } else { // normal directives
                        // code...
                    }
                } else {
                    // code...
                }
            }
        }
    
        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
            }
        }

      这里会首先对事件name进行修饰符判断,截取出self与number两个字段,保存到modifiers对象中。

      修饰符处理完后,进入v-on指令分支,首先正则替换掉第一个@字符,进入addHandler函数。

        function addHandler(el, name, value, modifiers, important, warn) {
            // handle capture,passive,once
            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 {
                // {value:click,modifiers:{self:true,number:true}}
                events[name] = newHandler;
            }
        }

      该函数处理特殊事件类型,包括capture、once、passive,并会给事件字符串添加特殊的前缀。

      完事后,该AST会被添加一个events属性,如图:

      下面转换节点内部的表达式:

        {{computedValue | filter}}

      关键函数是parseFilters,用来分割过滤器:

        function parseFilters(exp) {
            // var...
    
            for (i = 0; i < exp.length; i++) {
                prev = c;
                c = exp.charCodeAt(i);
                if (inSingle) {
                    // code...
                } else if (
                    c === 0x7C && // pipe
                    exp.charCodeAt(i + 1) !== 0x7C &&
                    exp.charCodeAt(i - 1) !== 0x7C &&
                    !curly && !square && !paren
                ) {
                    // 第一次匹配到 | 时
                    if (expression === undefined) {
                        // first filter, end of expression
                        lastFilterIndex = i + 1;
                        expression = exp.slice(0, i).trim();
                    }
                    // 多个filter串联 
                    else {
                        pushFilter();
                    }
                } else {
                    // code...
                }
            }
    
            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
        }

      其中切割大括号的部分跳过,主要看是如何处理分割后的filter:

        function wrapFilter(exp, filter) {
            // filter有可能带有参数 => {{value | filter(args)}}
            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)
            }
        }

      这里本来只有两种情况,一种只有函数名,一种是带参数的函数。

      1、单纯函数

        {{computedValue | filter}}

      如果只有函数名,此时i为-1,会进入第一个分支,直接返回对应的拼接字符串,如图:

       2、带参数的函数

        {{computedValue | filter(1)}}

      此时会进入分支2,并且通过正则进行切割,name为函数名,args为参数,最后返回拼接字符串:

      3、???

      后来,我又发现了第三种情况,就是如果函数名被括号给包起来,解析会变得有点奇怪。

        {{computedValue | (filter)}}

      此时,由于检测到小括号的存在,后面的被认为是形参,一个空白字符串被当成函数名进行拼接,返回如图:

      当然,这个会报错,filter被认为是形参,又不存在对应的函数,既然有warning提示,也不算啥问题,尽量不作死就行。

      至此,AST的转化完成,下一节讲render函数的生成。

  • 相关阅读:
    POJ 3680_Intervals
    POJ 3680_Intervals
    POJ 3686_The Windy's
    POJ 3686_The Windy's
    Codeforces 629C Famil Door and Brackets
    Codeforces 629D Babaei and Birthday Cake(树状数组优化dp)
    Codeforces 629D Babaei and Birthday Cake(线段树优化dp)
    Codeforces 628F Bear and Fair Set
    18.04.26 魔兽世界终极版
    18.4.20 STL专项练习8选6
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7412074.html
Copyright © 2011-2022 走看看