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

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

  ……

  指令这个讲起来还有点复杂,先把html弄上来:

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

  上一节是解析完了input标签的2个属性,并将其打包进了directives属性中返回。

  接着会继续跑genData函数,如下:

    function genData(el) {
var data = '{'; // 从这出来
var dirs = genDirectives(el);
if (dirs) {
data += dirs + ',';
} // code... // DOM props
if (el.props) {
data += "domProps:{" + (genProps(el.props)) + "},";
}
// event handlers
if (el.events) {
data += (genHandlers(el.events, false, warn$3)) + ",";
} // code... data = data.replace(/,$/, '') + '}';
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data);
}
return data
}

  由于在处理v-model的时候给el添加了props与events属性,所以之后会跑进两个处理函数。

  首先看看genProps:

    // props => {name:value,value:(vModel)}
function genProps(props) {
var res = '';
for (var i = 0; i < props.length; i++) {
var prop = props[i];
res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
}
return res.slice(0, -1)
} // 替换新换行符
function transformSpecialNewlines(text) {
return text
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
}

  比较简单,直接看返回的字符串:Vue源码后记-其余内置指令(2)

  

  接下来是处理添加的events:

    // event handlers
if (el.events) {
data += (genHandlers(el.events, false, warn$3)) + ",";
} function genHandlers(events, native, warn) {
var res = native ? 'nativeOn:{' : 'on:{';
for (var name in events) {
var handler = events[name]; // click.right => contextmenu
// 右键并不能触发click事件 建议使用H5新属性 然而这个属性兼容性捉急 res += "\"" + name + "\":" + (genHandler(name, handler)) + ",";
}
return res.slice(0, -1) + '}';
} // name => inpout
// handler => {modifiers => null,value:'if(...)...'}
function genHandler(name, handler) {
if (!handler) {
return 'function(){}'
} if (Array.isArray(handler)) {
return ("[" + (handler.map(function(handler) {
return genHandler(name, handler);
}).join(',')) + "]")
} // simplePathRE => /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/;
// 这是匹配的啥玩意啊!
var isMethodPath = simplePathRE.test(handler.value);
// fnExpRE => /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
// 匹配箭头函数或function
var isFunctionExpression = fnExpRE.test(handler.value); // 匹配完 两个都是false
// 包装成函数形式返回
if (!handler.modifiers) {
return isMethodPath || isFunctionExpression ?
handler.value :
("function($event){" + (handler.value) + "}") // inline statement
}
// 处理事件后缀
else {
// code...
}
}

  事件处理方面也很明了,首先判断是原生事件还是自定义,然后根据表达式的形式进行处理返回,本例会被包装为一个普通的函数返回,如图:

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

  字符串代表DOM上有一个input事件,值为"on:..."

  至此,input标签AST转换完毕,返回的code字符串如图:

    "_c('input',
{directives:[{name:"show",rawName:"v-show",value:(vShowIter),expression:"vShowIter"},{name:"model",rawName:"v-model",value:(vModel),expression:"vModel"}],
domProps:{"value":(vModel)},
on:{"input":function($event){if($event.target.composing)return;vModel=$event.target.value}}})"

  包含tag名、内置指令v-model/v-show、props、events。

  下面处理span标签,包含一个v-once:

    <span v-once>{{msg}}</span>

  这个el有个once属性,会进入genOnce函数:

    function genOnce(el) {
el.onceProcessed = true;
// 优先处理v-if
if (el.if && !el.ifProcessed) {
return genIf(el)
} else if (el.staticInFor) {
// 同时出现v-once与v-for
} else {
return genStatic(el)
}
} // hoist static sub-trees out
function genStatic(el) {
el.staticProcessed = true;
// 该节点属于静态dom
staticRenderFns.push(("with(this){return " + (genElement(el)) + "}"));
return ("_m(" + (staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
} function genElement(el) {
if (el.staticRoot && !el.staticProcessed) {
// once/for/if/templte/slot
} else {
// component or element
var code;
if (el.component) {
code = genComponent(el.component, el);
} else {
var data = el.plain ? undefined : genData(el); var children = el.inlineTemplate ? null : genChildren(el, true);
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
}
// module transforms => return code
}
}

  由于v-once是一次性渲染,所以会被归类于静态渲染,render函数会被弹入staticRenderFns数组。

  这里还有一个值得注意的是,由于span节点只有v-once,并没有其余的属性,没有必要进入genData进行属性切割,所以早在parseHTML阶段,该el的plain属性被置为true,跳过genData阶段。

    // parseHTML => processRawAttrs
function processRawAttrs(el) {
var l = el.attrsList.length;
if (l) {
// code...
} else if (!el.pre) {
el.plain = true;
}
}

  呃……

  没有属性处理会genChildren,子节点是一个表达式,会返回一个如图字符串:Vue源码后记-其余内置指令(2),最开始跑源码的时候见过_v、_s,不用的是该表达式被弹入了staticRenderFns数组。

  至此,span标签解析完毕。

  下面开始解析下一个div标签:

    <div v-html="html"></div>

  该标签包含一个内置指令,所以会进入genDirectives分支,并调用对应的gen函数处理v-html:

    function html(el, dir) {
// dir.value => html
if (dir.value) {
addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"));
}
}

  代码很简单,将v-html对应的值用_s包裹起来,添加到props属性中,如图:Vue源码后记-其余内置指令(2)

  这里需要注意的是,该函数并未返回任何值,因为v-html不需要弄进directives属性,所以默认返回的是undefined,直接会跳过后面条件判断返回。

  因为设置了props属性,所以后面的genProps也要跑,过程不看了直接看结果:Vue源码后记-其余内置指令(2)

  至此,v-html的标签解析完了,返回一个render函数:Vue源码后记-其余内置指令(2)

  下面解析根元素的下一个子元素,静态div:

    <div class='on'>empty Node</div>

  调用流程是这样的:generate => genChildren => genNode => genElement => genData && genChildren

  节点包含一个静态的类以及字符串子节点,所以会调用最后的genData与genChildren返回一个render字符串,流程比较简单,所以直接放结果:Vue源码后记-其余内置指令(2)

 

  到此为止,所有的AST都被转化为render函数,可以看一下结构:

    _c('div' /*<div id='app'>*/ , {
attrs: {
"id": "app"
}
}, [(vIfIter) /*v-if条件*/ ?
// 条件为真渲染下面的DOM
_c('div' /*<div v-if="vIfIter" v-bind:style="styleObject">*/ , {
style: (styleObject)
}, [_c('input' /*<input v-show="vShowIter" v-model='vModel' />*/ , {
directives: [{
name: "show",
rawName: "v-show",
value: (vShowIter),
expression: "vShowIter"
}, {
name: "model",
rawName: "v-model",
value: (vModel),
expression: "vModel"
}],
domProps: {
"value": (vModel)
},
on: {
"input": function($event) {
if ($event.target.composing) return;
vModel = $event.target.value
}
}
}),
_v(" ") /*这些是回车换行符*/ ,
_m(0) /*<span v-once>{{msg}}</span>*/ , _v(" "),
_c('div' /*<div v-html="html"></div>*/ , {
domProps: {
"innerHTML": _s(html)
}
})
]) :
// 否则渲染一个空的div...(错了)
_e() /*comment*/ ,
_v(" "),
_c('div' /*<div class='on'>empty Node</div>*/ , {
staticClass: "on"
}, [_v("empty Node")])
])

  这样看起来结构就非常清晰了,render函数针对v-if是使用一个三元表示式处理的,该函数的整体框架结构为_c(tagName,attrs,childrens),其中childrens可能嵌套多个_c。

  其中有个小点需要注意的是里面并没有v-once的那个span标签,取而代之的是一个_m(0),这是因为span标签是静态节点,所以被弹入staticRenderFns数组并作为第一个元素,这里将来会将其取出来并渲染。

  这个patch下次再讲,最近被项目搞的脑袋疼。

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

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

    把其余的内置指令也搞完吧,来一个全家桶. 案例如下: <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. &lbrack;干货&rsqb;Chloe官网及基于NFine的后台源码毫无保留开放

    扯淡 经过不少日夜的赶工,Chloe 的官网于上周正式上线.上篇博客中LZ说过要将官网以及后台源码都会开放出来,为了尽快兑现我说过的话,趁周末,我稍微整理了一下项目的源码,就今儿毫无保留的开放给大家, ...

  2. swift language

    API reference Swift UIKit Swift 菜鸟教程 Great Installed Visual Studio Code, I found I cannot open it fr ...

  3. mongodb&lowbar;修改器(&dollar;inc&sol;&dollar;set&sol;&dollar;unset&sol;&dollar;push&sol;&dollar;pop&sol;upsert&period;&period;&period;&period;&period;&period;)

    主从复制:http://blog.csdn.net/drifterj/article/details/7833883 对于文档的更新除替换外,针对某个或多个文档只需要部分更新可使用原子的更新修改器,能 ...

  4. OpenStack API 与 CloudStack API 模块比较

    OpenStack API Block Storage Service API Compute API Compute API extensions Identity Service API and ...

  5. ural1439 Battle with You-Know-Who

    Battle with You-Know-Who Time limit: 2.0 secondMemory limit: 64 MB Rooms of the Ministry of Magic ar ...

  6. Exif Info 隐私政策

    隐私政策 本应用尊重并保护所有使用服务用户的个人隐私权.为了给您提供更准确.更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息.但本应用将以高度的勤勉.审慎义务对待这些信息.除本隐 ...

  7. Android5&period;0特性阴影效果和裁剪

    阴影和剪裁 View的z属性 Material Design建议为了凸显布局的层次,建议使用阴影效果,并且Android L为了简化大家的工作,对View进行了扩展,能使大家非常方便的创建阴影效果: ...

  8. PLC之六部十层电梯整体框架

    1.基本框架 此图基于西门子杯逻辑控制赛项中电梯题目的变量表以及功能设计 1.I/O输入是指变量表中的input数字量,包括电梯所有的内呼按钮.外呼按钮.电梯平层开关等 2.逻辑处理是指根据相对应的输 ...

  9. java&lowbar;32 SQLyog中创建数据库表

    USE test; /*1.创建账务表 id name mony*/ CREATE TABLE zhangwu(id INT PRIMARY KEY AUTO_INCREMENT, sname VAR ...

  10. SQL-44 将id&equals;5以及emp&lowbar;no&equals;10001的行数据替换成id&equals;5以及emp&lowbar;no&equals;10005&comma;其他数据保持不变,使用replace实现。

    题目描述 将id=5以及emp_no=10001的行数据替换成id=5以及emp_no=10005,其他数据保持不变,使用replace实现.CREATE TABLE IF NOT EXISTS ti ...