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函数的生成。

  • 相关阅读:
    easy ui 表单ajax和from两种提交数据方法
    easy ui 下拉级联效果 ,下拉框绑定数据select控件
    easy ui 下拉框绑定数据select控件
    easy ui 异步上传文件,跨域
    easy ui 菜单和按钮(Menu and Button)
    HTTP 错误 404.3
    EXTJS4.2 后台管理菜单栏
    HTML 背景图片自适应
    easy ui 表单元素input控件后面加说明(红色)
    EXTJS 4.2 添加滚动条
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7412074.html
Copyright © 2011-2022 走看看