我是这样计划的,写完这个还写一篇数据变动时,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函数的生成。

Vue源码后记-更多options参数(1)的更多相关文章

  1. Vue源码后记-更多options参数(2)

    写起来感觉都是老三套,AST => render => VNode => patch 之前是把AST弄完了,对事件和过滤器处理如图: render函数也只看这两部分的转换吧! 首先是 ...

  2. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  3. Vue源码后记-钩子函数

    vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过 ...

  4. Vue源码后记-其余内置指令(1)

    把其余的内置指令也搞完吧,来一个全家桶. 案例如下: <body> <div id='app'> <div v-if="vIfIter" v-bind ...

  5. Vue源码后记-vFor列表渲染(2)

    这一节争取搞完! 回头来看看那个render代码,为了便于分析,做了更细致的注释: (function() { // 这里this指向vue对象 下面的所有方法默认调用Vue$3.prototype上 ...

  6. Vue源码后记-vFor列表渲染(1)

    钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...

  7. Vue源码后记-其余内置指令(2)

    -- 指令这个讲起来还有点复杂,先把html弄上来: <body> <div id='app'> <div v-if="vIfIter" v-bind ...

  8. Vue源码后记-vFor列表渲染(3)

    这一节肯定能完! 经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了. render函数跑完应该是在这里: function mountComponent( ...

  9. vue源码逐行注释分析+40多m的vue源码程序流程图思维导图 (diff部分待后续更新)

    vue源码业余时间差不多看了一年,以前在网上找帖子,发现很多帖子很零散,都是一部分一部分说,断章的很多,所以自己下定决定一行行看,经过自己坚持与努力,现在基本看完了,差ddf那部分,因为考虑到自己要换 ...

随机推荐

  1. maven 执行mvn package/clean命令出错

    mvn compile/test都没报错,但是执行mvn package和mvn clean时候就报错:a required class was missing while executing.... ...

  2. (转)Unity3D中移动物体位置的几种方法

    1. 简介 在unity3d中,有多种方式可以改变物体的坐标,实现移动的目的,其本质是每帧修改物体的position. 2. 通过Transform组件移动物体 Transform 组件用于描述物体在 ...

  3. multimap 和priority_queue详解

    上一期是关于STL和并查集结合的例题,也附了STL中部分容器的使用摘要,由于是从网上东拼西凑的,感觉有的关键点还是没解释清楚,现在从其中摘出两个容器,用例题对它们的用法进行进一步解释. 以下是例题的介 ...

  4. ios开发——实用技术篇&三维旋转动画

    实现三位旋转动画的方法有很多种,这里介绍三种 一:UIView 1 [UIView animateWithDuration:1.0 animations:^{ 2 self.iconView.laye ...

  5. Codevs1380没有上司的舞会_KEY

    没有上司的舞会 1380 没有上司的舞会 时间限制: 1 s 空间限制: 128000 KB 题目描述 Description Ural大学有N个职员,编号为1~N.他们有从属关系,也就是说他们的关系 ...

  6. 洗礼灵魂,修炼python(2)--python安装和配置

    安装python和基本配置: python官方下载地址:www.python.org 打开网站,然后下载对应(32位和64位,windows版还是linux版)的版本,你可以选择python3或者2, ...

  7. java核心卷轴之泛型程序设计

    本文根据<Java核心卷轴>第十二章总结而来,更加详细的内容请查看<Java核心卷轴> 1. 泛型类型只能是引用类型,不可以使用基本数据类型. 2. 类型变量含义 E : 集合 ...

  8. pygame_polygon

    今天我们要在窗口上绘制简单的多边形 1.认识几个简单的常用的颜色: black=(0,0,0) while=(255,255,255) red=(255,0,0) green=(0,255,0) bl ...

  9. hdu1166 敌兵布阵

    敌兵布阵 C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动 ...

  10. 笨鸟先飞之ASP.NET MVC系列之过滤器(02授权过滤器)

    授权过滤器 概念介绍 在之前的文章中我们已经带大家简单的了解了下过滤器,本次我们开始介绍授权过滤器. 我们之前提到过授权过滤器在认证过滤器之后,其他过滤器和方法被调用之前运行,而授权过滤器和它名字的含 ...