Vue源码后记-其余内置指令(1)
把其余的内置指令也搞完吧,来一个全家桶。
案例如下:
<body>
<div id='app'>
<div v-if="vIfIter" v-bind:style="styleObject">
<input v-show="vShowIter" v-model='vModel' />
<span v-once>{{msg}}</span>
<div v-html="html"></div>
</div>
<div class='on'>empty Node</div>
</div>
</body>
<script src='./vue.js'></script>
<script>
var app = new Vue({
el: '#app',
data: {
vIfIter: true,
vShowIter: true,
vModel: 1,
styleObject: {
color: 'red'
},
msg: 'Hello World',
html: '<span>v-html</span>'
},
});
</script>
基本上内置指令都有,由于v-on涉及事件,也就是methods,这个后面再说,这里暂时只处理指令。另外添加了一个纯净的节点,可以跑一下ref和optimize。
跳过前面所有无聊的流程,直接进入parseHTML,切割方面也没什么看头,最外层div切割完,会进入v-if那个标签,即:
<div v-if="vIfIter" v-bind:style="styleObject">
正常切割后,如图所示:
attrs存放着该标签的2个属性,分别为v-if与v-bind:style,简单的切割后,会调用handleStart进一步处理,其中就包含一系列process函数:
function start(tag, attrs, unary) {
// code... if (inVPre) {
processRawAttrs(element);
} else {
processFor(element);
processIf(element);
processOnce(element);
processKey(element); element.plain = !element.key && !attrs.length; processRef(element);
processSlot(element);
processComponent(element);
for (var i$1 = 0; i$1 < transforms.length; i$1++) {
transforms[i$1](element, options);
}
processAttrs(element);
} // code...
}
这里对for、if、once等内置指令进行2次处理,for之前专门分析过一节,所以不管,首先看看if:
// el为之前的切割对象
function processIf(el) {
// 将v-if从attrsList中移除 因为会影响render函数的生成
var exp = getAndRemoveAttr(el, 'v-if');
if (exp) {
// el.if => vIfIter
el.if = exp;
addIfCondition(el, {
exp: exp,
block: el
});
}
// 处理else与else-if
else {
// code...
}
} // 保存节点display状态
function addIfCondition(el, condition) {
if (!el.ifConditions) {
el.ifConditions = [];
}
el.ifConditions.push(condition);
}
案例只有v-if,else和else-if有兴趣自己去玩吧,处理完后得到这么一个对象:,与v-for类似,有一个属性专门保存对象的值,另外有个codition保存状态。
接下来处理v-bind:style属性,处理函数在下面的transforms数组中,一个负责class,一个负责style,看一个就行了。
function transformNode$1(el, options) {
var warn = options.warn || baseWarn;
// 获取静态style属性并添加在staticStyle属性上
var staticStyle = getAndRemoveAttr(el, 'style');
if (staticStyle) {
// warning...
el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
}
// 获取动态绑定的style
var styleBinding = getBindingAttr(el, 'style', false /* getStatic */ );
if (styleBinding) {
el.styleBinding = styleBinding;
}
} // 该函数专门用来处理v-bind绑定的属性
// name => style
// getStatic => false
function getBindingAttr(el, name, getStatic) {
// 处理缩写:
var dynamicValue =
getAndRemoveAttr(el, ':' + name) ||
getAndRemoveAttr(el, 'v-bind:' + name);
// dynamicValue => styleObject
if (dynamicValue != null) {
return parseFilters(dynamicValue)
} else if (getStatic !== false) {
var staticValue = getAndRemoveAttr(el, name);
if (staticValue != null) {
return JSON.stringify(staticValue)
}
}
}
这里分别对静态的style动态(v-bind:style)的style分别进行处理,静态的直接抽取出来JSON化保存到一个属性。
动态的根据简写或全名来进行获取,获取到对应的值,这里是styleObject,然后调用parseFilters进行过滤。
这个函数当初抄源码那个恶心哦。。。
这里放一下这个函数:
// 处理过滤器
function parseFilters(exp) {
var inSingle = false;
var inDouble = false;
var inTemplateString = false;
var inRegex = false;
var curly = 0;
var square = 0;
var paren = 0;
var lastFilterIndex = 0;
var c, prev, i, expression, filters;
// 遍历字符串
for (i = 0; i < exp.length; i++) {
// prev => 上一个字符
// c => 当前字符
prev = c;
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
) {
if (expression === undefined) {
// 截取expresion为|符号前面的字符串
lastFilterIndex = i + 1;
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 // `
case 0x28:
paren++;
break // (
case 0x29:
paren--;
break // )
case 0x5B:
square++;
break // [
case 0x5D:
square--;
break // ]
case 0x7B:
curly++;
break // {
case 0x7D:
curly--;
break // }
}
// 正则表达式
if (c === 0x2f) { // /
var j = i - 1;
var p = (void 0);
// find first non-whitespace prev char
for (; j >= 0; j--) {
p = exp.charAt(j);
if (p !== ' ') {
break
}
}
if (!p || !validDivisionCharRE.test(p)) {
inRegex = true;
}
}
}
} 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
} function wrapFilter(exp, filter) {
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)
}
}
这个函数很长很长,主要是格式化绑定的值。由官网的实例可知,模板语法支持形式诸如{{message | filter}}或者v-bind:id='str | filter',甚至支持正则语法。而这个函数就是处理这种复杂值的。
而筛选器涉及到options的参数filter,不在本篇的内置指令讨论之内,所以暂时跳过。这里会直接返回传进去的字符串,即:,然后顺便作为属性绑定到vm对象上。
切割完v-if的div标签,接下来是:
<input v-show="vShowIter" v-model='vModel' />
该标签属于自闭合标签。依照惯例,依次用正则分割出两个attr,象征性放个图:
接下来也会进入handleStartTag函数,处理分割出的各种属性。
在处理v-show与v-model时,并没有专门的process函数,这些内置指令被统一用一个processAttrs处理,这里看看是如何被处理的:
// 处理其余的v-指令
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;
// dirRE => /^v-|^@|^:/
// 专业匹配v- @ :三剑客
if (dirRE.test(name)) {
el.hasBindings = true;
// 匹配一些后缀 诸如事件的.self/.prevent等
modifiers = parseModifiers(name);
// 截取到后缀后去掉
if (modifiers) {
name = name.replace(modifierRE, '');
}
// bindRE => /^:|^v-bind:/
// 处理v-bind绑定的属性
if (bindRE.test(name)) { // v-bind
// code...
}
// onRE => /^@|^v-on:/
else if (onRE.test(name)) {
// 绑定事件处理器
name = name.replace(onRE, '');
addHandler(el, name, value, modifiers, false, warn$2);
}
// 普通指令
else {
// 截取v-后面的字符串
name = name.replace(dirRE, '');
// argRE => /:(.*)$/
var argMatch = name.match(argRE);
var arg = argMatch && argMatch[1];
if (arg) {
name = name.slice(0, -(arg.length + 1));
}
addDirective(el, name, rawName, value, arg, modifiers);
// v-for的别名跟v-model重复
if ("development" !== 'production' && name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
// 处理普通的属性绑定
// warning:<div id="{{ val }}"> => use <div :id="val">
addAttr(el, name, JSON.stringify(value));
}
}
} // name => show、model
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
}
} // el =>
function addDirective(el, name, rawName, value, arg, modifiers) {
(el.directives || (el.directives = [])).push({
name: name,
rawName: rawName,
value: value,
arg: arg,
modifiers: modifiers
});
}
这里分别处理三种情况:v-、:、@,分别是内置指令、属性绑定、事件绑定,分别调用不同的方法处理并添加对应的属性到vm上。
内置指令处理完会生成一个directives的数组属性绑定到vm上,并将切割后的属性对象作为数组元素,如图:
进行下一个tag切割,即:
<span v-once>{{msg}}</span>
这里的内置属性为v-once,有一个processOnce函数处理这个指令:
function processOnce(el) {
var once$$1 = getAndRemoveAttr(el, 'v-once');
if (once$$1 != null) {
el.once = true;
}
}
太简单,注释都懒得写了。
处理{{msg}}的过程就不写了,在跑源码的时候用的就是这个形式。
下一个tag:
<div v-html="html"></div>
这个指令没有特殊函数处理,被丢到了processAttrs函数,然后通过addDirective添加到directives数组中,如图:
至此,所有的内置指令相关的标签都解析完了,还剩一个纯净的DOM节点:
<div class='on'>empty Node</div>
正常切割完节点后开始解析属性,此处的class并没有用v-bind进行绑定,所以在调用transformNode方法处理class属性时,会被认定为static属性,如下:
function transformNode(el, options) {
var warn = options.warn || baseWarn;
// 获取静态的class => on
var staticClass = getAndRemoveAttr(el, 'class');
// warning...
if (staticClass) {
// JSON化后作为属性添加到对象上
el.staticClass = JSON.stringify(staticClass);
}
var classBinding = getBindingAttr(el, 'class', false /* getStatic */ );
if (classBinding) {
el.classBinding = classBinding;
}
}
弄完,大概是个这样子:
AST转化完后会进入optimize阶段,可以稍微讲下这个地方,首先会对所有节点进行标记:
function optimize(root, options) {
if (!root) {
return
}
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// 标记是否静态节点
markStatic$1(root);
// 标记根节点
markStaticRoots(root, false);
} function markStatic$1(node) {
// 标记当前节点是否为静态节点
node.static = isStatic(node);
if (node.type === 1) {
if (!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (var i = 0, l = node.children.length; i < l; i++) {
// 遍历子节点递归判断
var child = node.children[i];
markStatic$1(child);
// 如果子节点是非静态 那么父节点也是非静态
if (!child.static) {
node.static = false;
}
}
}
} function isStatic(node) {
// {{...}}大括号表达式
if (node.type === 2) { // expression
return false
}
// 纯文本节点
if (node.type === 3) { // text
return true
}
// 子节点没有hasBindings、v-for、v-if、非slot/component标签、组件、静态属性判断
return !!(node.pre || (!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
这里一层一层递归进行标记,所有的AST对象会添加一个static属性,只有最后那个纯净节点的static标记为true(其实有3个子节点,多出来的是排版形成的回车换行符):
当一个节点被标记为静态节点,之后的虚拟DOM在通过diff算法比较差异时会跳过该节点以提升效率,这就是AST的优化。
静态节点标记完后,还有最后一步,调用markStaticRoots函数进行二次优化,并会对v-if做特殊处理:
function markStaticRoots(node, isInFor) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor;
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true;
return
} else {
node.staticRoot = false;
}
if (node.children) {
for (var i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
// v-if
if (node.ifConditions) {
walkThroughConditionsBlocks(node.ifConditions, isInFor);
}
}
}
这里的优化直接看那段注释就可以了,大概意思是:如果一个节点的子节点只有一个表达式,那就没有必要当做非静态节点。
看一下v-if的处理函数:
function walkThroughConditionsBlocks(conditionBlocks, isInFor) {
for (var i = 1, len = conditionBlocks.length; i < len; i++) {
markStaticRoots(conditionBlocks[i].block, isInFor);
}
}
看毛,直接跳出来了,这里的ifCondition只有一个值,所以跳过了。
优化完,会进行generate,把AST转化为函数:
function generate(ast, options) {
// save previous staticRenderFns so generate calls can be nested
// code... staticRenderFns = prevStaticRenderFns;
onceCount = prevOnceCount;
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: currentStaticRenderFns
}
} // 处理静态、v-once、v-for、v-if、template/slot
function genElement(el) {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el)
} else if (el.once && !el.onceProcessed) {
return genOnce(el)
} else if (el.for && !el.forProcessed) {
return genFor(el)
} else if (el.if && !el.ifProcessed) {
return genIf(el)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el)
} else {
// component or element
// code...
return code
}
}
案例中的v-if、v-once在这里都会被特殊处理,首先看一下v-if:
function genIf(el) {
el.ifProcessed = true; // avoid recursion
return genIfConditions(el.ifConditions.slice())
} function genIfConditions(conditions) {
if (!conditions.length) {
return '_e()'
}
// 取出v-if对应的表达式 => vIfIter
var condition = conditions.shift();
if (condition.exp) {
return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions)))
} else {
return ("" + (genTernaryExp(condition.block)))
} // v-if with v-once should generate code like (a)?_m(0):_m(1)
// 处理v-once与v-if同时出现的情况
function genTernaryExp(el) {
return el.once ? genOnce(el) : genElement(el)
}
}
函数首先会将ifCondition标记为true,防止递归处理子节点时候又跳到这个函数,接下来会判断该节点是否同时有v-once,这里没有,调用genElement处理其余属性。
在genData中,会对class与style进行处理,其中也包括v-bind绑定的属性:
function genData$2(el) {
var data = '';
// 静态style
if (el.staticStyle) {
data += "staticStyle:" + (el.staticStyle) + ",";
}
// v-bind:style => styleObject
if (el.styleBinding) {
data += "style:(" + (el.styleBinding) + "),";
}
return data
}
除去v-if,其余处理被包装为一个字符串,如图:
这里对v-if的render函数包装会暂时停下来,优先递归处理子节点,简单看一下函数:
function genChildren(el, checkSkip) {
var children = el.children;
if (children.length) {
var el$1 = children[0];
// 单纯的v-for子节点
if (children.length === 1 &&
el$1.for &&
el$1.tag !== 'template' &&
el$1.tag !== 'slot') {
return genElement(el$1)
}
// 对每一个子节点做判断
var normalizationType = checkSkip ? getNormalizationType(children) : 0;
return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
}
}
子节点这里做了一点优化,如果只是单纯的v-for,就不做类型区别判断,直接生成render函数,就像之前解析v-for的案例一样,所以上一篇是没有跑这个的。
这一次不太一样,有3个子节点,加上两个空白换行,共有5个。
跳过v-for的判断,会进入子节点类型分类:
// determine the normalization needed for the children array.
// 0: no normalization needed
// 1: simple normalization needed (possible 1-level deep nested array)
// 2: full normalization needed
function getNormalizationType(children) {
var res = 0;
for (var i = 0; i < children.length; i++) {
var el = children[i];
if (el.type !== 1) {
continue
}
// el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
if (needsNormalization(el) ||
(el.ifConditions && el.ifConditions.some(function(c) {
return needsNormalization(c.block);
}))) {
res = 2;
break
}
// !isPlatformReservedTag$1 => isHTMLTag(tag) || isSVG(tag) => 非内置标签
if (maybeComponent(el) ||
(el.ifConditions && el.ifConditions.some(function(c) {
return maybeComponent(c.block);
}))) {
res = 1;
}
}
return res
}
函数的作用可以直接看注释,不懂也没关系,这里简单解释一下,该函数将子节点分为三类:
1、默认普通子节点
2、包含v-for属性或者是template/slot的模板标签
3、非内置标签,即自定义组件
三类子节点分别对应res的0、1、2。
这里都是普通子节点,res返回0,跳出来后返回的字符串后面就不会拼接一个类型,直接拼接空字符。
("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
这里接下来会调用genNode进行子节点处理:
function genNode(node) {
if (node.type === 1) {
return genElement(node)
} else {
return genText(node)
}
}
该函数对不同类型的子节点做不同处理,首先是input标签,进入genElement函数,之前有看过该函数,这里有一点不一样的是会进入genDirectives,处理内置指令v-show、v-model:
function genDirectives(el) {
var dirs = el.directives;
if (!dirs) {
return
}
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// html/model/text || bind/cloak
var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, warn$3);
}
if (needRuntime) {
hasRuntime = true;
// 很长很长的拼接
res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}
函数意思很简单,取出对应的内置指令对象,判断是否存在gen函数,然后进行长拼接,第一个v-show不存在gen函数,所以直接拼接,结果如图:
这里针对第一个指令进行了拼接,接下来还有一个v-model,这个指令存在对应的gen函数,所以流程会多一步:
function model(el, dir, _warn) {
warn$1 = _warn;
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type; {
// 有傻逼会用:type绑定input的类型吗???
// 另外type=file是无法用v-model监听的
} // 针对不同的input类型做处理
if (tag === 'select') {
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el, value, modifiers);
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else {
// 不支持v-model的标签
} // ensure runtime directive metadata
return true
}
这里首先做了错误预判,type属性的无法动态绑定的,file上传的类型v-mode也不起作用,然后针对不同类型的input标签,包括select/checkbox/radio/text/textarea做处理。
由于只是个没有type的<input/>,所以默认为text并进入genDefaultModel分支:
// value => vModel
function genDefaultModel(el, value, modifiers) {
var type = el.attrsMap.type;
var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
var event = lazy ?
'change' :
type === 'range' ?
RANGE_TOKEN :
'input';
// 获取对应input标签的值
// 包含处理后缀为trim、number
var valueExpression = '$event.target.value';
if (trim) {
valueExpression = "$event.target.value.trim()";
}
if (number) {
valueExpression = "_n(" + valueExpression + ")";
} var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
code = "if($event.target.composing)return;" + code;
} addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
if (trim || number || type === 'number') {
addHandler(el, 'blur', '$forceUpdate()');
}
}
这里生成了获取对应input值得表达式,正常为$event.target.value,$event代表原生的事件,如果有trim/number后缀,会自动调用去空白与数字化函数。
接着调用genAssignmentCode,这里涉及一个idx属性,没搞懂具体是什么情况会出现:
// value => vModel
function genAssignmentCode(value, assignment) {
// {exp:vModel,idx:null}
var modelRs = parseModel(value);
if (modelRs.idx === null) {
return (value + "=" + assignment)
} else {
// code...
}
} function parseModel(val) {
str = val;
len = str.length;
index$1 = expressionPos = expressionEndPos = 0; if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
return {
exp: val,
idx: null
}
} // code...
}
针对本例中简单的属性设置,会直接返回一个对象。
此时code为一个字符串表示式:,很简单就是获取目标节点的值。
下面有一个关于composing的判断,这个属性在MDN是这样解释的:
简单来讲,这是一个只读属性,发生在compositionstart事件之后,compositionend事件之前,这两个事件类似于keyup、keydown,该属性指的是IDE输入过程中,如图:
这里ddd是一个编辑中的状态,触发了composing事件,此时v-model是不响应的,所以可以看到拼接的字符串如下所示:
即:如果文字在编辑中,那么直接返回。
接下来会调用addProp函数:
addProp(el, 'value', ("(" + value + ")"));
一句代码函数,看了就懂是干嘛用的:
// el => dom
// name => value
// value => (vModel)
function addProp(el, name, value) {
(el.props || (el.props = [])).push({
name: name,
value: value
});
}
判断是否有props属性,并添加一个对象到属性中,值得注意的是,这里的值用一个括号包装起来了,所以是(vModel)。
下面是给dom绑定一个事件:
// event => input
// code => if(...)...
addHandler(el, event, code, null, true); function addHandler(el, name, value, modifiers, important, warn) {
// warn prevent and passive modifier
// code... // 检测事件是否带有capture/once/paasive后缀
// code... 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 {
events[name] = newHandler;
}
}
这里会在当前dom节点的ast上添加一个events属性,值为之前生成的表达式字符串:
处理完这个,最后会对trim、number、type=number做特殊处理,每次响应进行强制更新,格式化输入值。
至此,v-model指令处理完毕,回到genDirectives拼接到了v-show字符串的后面:
只要有属性,hasRuntime就会被置为true,因为拼接的字符串最后是逗号,在return的时候需要去除这个逗号并添加一个中括号完整表达式。
先这样吧,下次搞。
Vue源码后记-其余内置指令(1)的更多相关文章
- Vue源码后记-其余内置指令(2)
-- 指令这个讲起来还有点复杂,先把html弄上来: <body> <div id='app'> <div v-if="vIfIter" v-bind ...
- Vue源码后记-其余内置指令(3)
其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...
- Vue源码后记-更多options参数(1)
我是这样计划的,写完这个还写一篇数据变动时,VNode是如何更新的,顺便初探一下diff算法. 至于vue-router.vuex等插件源码,容我缓一波好吧,vue看的有点伤. 其实在之前讲其余内置指 ...
- Vue源码后记-vFor列表渲染(1)
钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...
- Vue源码后记-钩子函数
vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过 ...
- Vue源码后记-vFor列表渲染(2)
这一节争取搞完! 回头来看看那个render代码,为了便于分析,做了更细致的注释: (function() { // 这里this指向vue对象 下面的所有方法默认调用Vue$3.prototype上 ...
- Vue源码后记-更多options参数(2)
写起来感觉都是老三套,AST => render => VNode => patch 之前是把AST弄完了,对事件和过滤器处理如图: render函数也只看这两部分的转换吧! 首先是 ...
- android studio应用修改到android源码中作为内置应用
1. 方法一:导入,编译(太麻烦,各种不兼容问题) android studio和eclipse的应用结构目录是不同的,但是在android源码中的应用基本上都是使用的eclipse目录结构(在/pa ...
- 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
随机推荐
- 献身说法---修复bug时的一些小技巧
最近,修复了项目当中的一些bug,觉着有些思路可以分享出来供大家借鉴. 场景一 开发环境中系统正常运行,测试环境中,部分机器未能正常运行. 解决过程:远程连接了测试环境中的机器,观察了系统的运行情况, ...
- Redis——windows环境安装redis和redis sentinel部署
一:Redis的下载和安装 1:下载Redis Redis的官方网站Download页面,Redis提示说:Redis的正式版不支持Windows,要Windows学习Redis,请点击Learn m ...
- 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ----初始化一个线程
使用线程的一个常见问题就是如何能够在一个线程开始运行之前,适当地将它初始化.初始化最常见的理由就是为了调整优先权.另一个理由是为了在SMP 系统中设定线程比较喜欢的 CPU.第10 章谈到 MFC 时 ...
- Muddy Fields
Muddy Fields Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Submi ...
- S2_SQL_第三章
3.1:修改表 3.1.1:修改表 语法: Alter table <旧表名> rename [ TO] <新表名>; 例子:Alter table `demo01` rena ...
- qplot函数用法(转载)
http://blog.csdn.net/u014801157/article/details/24372499 写的很全面 放在这里记录下
- Python多线程练习(threading)
这几天学习python多线程的时候,试了几次thread模块和threading模块,发现thread模块非常的不好用.强烈不建议大家使用thread,建议使用threading模块,此模块对thre ...
- Ubuntu中MongoDB安装
在Ubuntu中MongoDB有时候启动不起来,可以参考以下方法从新安装: 1.导入包管理系统使用的公钥 Ubuntu 的软件包管理工具(即dpkg和APT)要求软件包的发布者通过GPG密钥签名来确保 ...
- ASP.Net MVC 布局页 模板页 使用方法详细说明
一.Views文件夹 -> Shared文件夹下的 _Layout.cshtml 母版页 @RenderBody 当创建基于_Layout.cshtml布局页面的视图时,视图的内容会和布局页面合 ...
- HDU1081 最大字段和 压缩数组
最大字段和题型,推荐做题顺序: HDU1003 HDU1024 HDU1081 zoj2975 zoj2067 #include<cstdio> #include< ...