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

时间:2022-09-26 10:21:35

  把其余的内置指令也搞完吧,来一个全家桶。

  案例如下:

    <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">

  正常切割后,如图所示:Vue源码后记-其余内置指令(1)

  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有兴趣自己去玩吧,处理完后得到这么一个对象:Vue源码后记-其余内置指令(1),与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,不在本篇的内置指令讨论之内,所以暂时跳过。这里会直接返回传进去的字符串,即:Vue源码后记-其余内置指令(1),然后顺便作为属性绑定到vm对象上。

  切割完v-if的div标签,接下来是:

  <input v-show="vShowIter" v-model='vModel' />

  该标签属于自闭合标签。依照惯例,依次用正则分割出两个attr,象征性放个图:Vue源码后记-其余内置指令(1)Vue源码后记-其余内置指令(1)

  接下来也会进入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上,并将切割后的属性对象作为数组元素,如图:Vue源码后记-其余内置指令(1)

  进行下一个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数组中,如图:Vue源码后记-其余内置指令(1)

  至此,所有的内置指令相关的标签都解析完了,还剩一个纯净的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;
}
}

  弄完,大概是个这样子:Vue源码后记-其余内置指令(1)

  

  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个子节点,多出来的是排版形成的回车换行符):Vue源码后记-其余内置指令(1)

  当一个节点被标记为静态节点,之后的虚拟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,其余处理被包装为一个字符串,如图:Vue源码后记-其余内置指令(1)

  

  这里对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函数,所以直接拼接,结果如图:Vue源码后记-其余内置指令(1)

  这里针对第一个指令进行了拼接,接下来还有一个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为一个字符串表示式:Vue源码后记-其余内置指令(1),很简单就是获取目标节点的值。

  下面有一个关于composing的判断,这个属性在MDN是这样解释的:

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

  简单来讲,这是一个只读属性,发生在compositionstart事件之后,compositionend事件之前,这两个事件类似于keyup、keydown,该属性指的是IDE输入过程中,如图:

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

  这里ddd是一个编辑中的状态,触发了composing事件,此时v-model是不响应的,所以可以看到拼接的字符串如下所示:

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

  即:如果文字在编辑中,那么直接返回。

  接下来会调用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属性,值为之前生成的表达式字符串:Vue源码后记-其余内置指令(1)

  处理完这个,最后会对trim、number、type=number做特殊处理,每次响应进行强制更新,格式化输入值。

  至此,v-model指令处理完毕,回到genDirectives拼接到了v-show字符串的后面:

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

  只要有属性,hasRuntime就会被置为true,因为拼接的字符串最后是逗号,在return的时候需要去除这个逗号并添加一个中括号完整表达式。

  先这样吧,下次搞。

  

Vue源码后记-其余内置指令(1)的更多相关文章

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

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

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

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

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

    我是这样计划的,写完这个还写一篇数据变动时,VNode是如何更新的,顺便初探一下diff算法. 至于vue-router.vuex等插件源码,容我缓一波好吧,vue看的有点伤. 其实在之前讲其余内置指 ...

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

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

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

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

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

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

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

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

  8. android studio应用修改到android源码中作为内置应用

    1. 方法一:导入,编译(太麻烦,各种不兼容问题) android studio和eclipse的应用结构目录是不同的,但是在android源码中的应用基本上都是使用的eclipse目录结构(在/pa ...

  9. 10&period;源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

随机推荐

  1. Javascript&period;Reactjs-5-prop-validation-and-proptypes

    Props & PropTypes 1. Props "Props are the mechanism React uses to let components communicat ...

  2. wtforms 使用

    wtforms是一个表单模板库, 下面以修改密码表单为例简单说明其用法. 我们可以用python代码定义form的基本元素, 比如用户名/邮箱, 并给定各个元素的validation条件. 然后在re ...

  3. Jsoup开发简单网站客户端之读取本地html文件

    用jsoup解析网页,相比于那些返回api数据来说 肯定耗流量,加载慢,所以程序assts中预先放了一个最新的html文件,第一次进来不走网络,直接从本地取,以后会加上wifi离线功能. 首先离线网站 ...

  4. 解决PowerDesigner 反向工程没有注释&lpar;备注&rpar;

    本文转载自:http://www.cnblogs.com/zhangxb/archive/2012/04/20/2458898.html 1. 列注释 原来代码: {OWNER, TABLE, S, ...

  5. Delphi 生成excel中的饼图

    生成excel中的饼图 var i,j,m,n,count1:integer; str:string; Jdate:tdatetime; channellist,potBstrtime,potEstr ...

  6. subnetting and the subnet mask

    原文:https://www.techopedia.com/6/28587/internet/8-steps-to-understanding-ip-subnetting/5 Step 4 - Sub ...

  7. Map 数据结构

    JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键.这给它的使用带来了很大的限制. 为了解决这个问题,ES6 提供了 Map 数据结构. ...

  8. 【Java深入研究】4、fail-fast机制

    在JDK的Collection中我们时常会看到类似于这样的话: 例如,ArrayList: 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证.快速失 ...

  9. POJ-3087 Shuffle&&num;39&semi;m Up (模拟)

    Description A common pastime for poker players at a poker table is to shuffle stacks of chips. Shuff ...

  10. &lpar;转&rpar;Java GC基本算法

    http://blog.csdn.net/heyutao007/article/details/38151581 1.引用计数(reference counting)    原理:此对象有一个引用,则 ...