博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Vue源码后记-更多options参数(1)
阅读量:5150 次
发布时间:2019-06-13

本文共 9153 字,大约阅读时间需要 30 分钟。

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

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

 

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

  上html代码:

        
{
{computedValue | filter}}

  包含最常见的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跳过,只看里层的,首先是:

  切割函数为:

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

转载于:https://www.cnblogs.com/QH-Jimmy/p/7412074.html

你可能感兴趣的文章