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

时间:2022-09-26 10:48:48

  其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样。

  

  go!

  之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正的函数:

    function compileToFunctions(template, options, vm) {
// code... // compile
var compiled = compile(template, options); // code... // 转换render
res.render = makeFunction(compiled.render, fnGenErrors);
var l = compiled.staticRenderFns.length;
// 转换staticRenderFns
res.staticRenderFns = new Array(l);
for (var i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);
} // code... return (functionCompileCache[key] = res)
} function makeFunction(code, errors) {
try {
return new Function(code)
} catch (err) {
// error...
}
}

  这个没啥讲的

  将render转换成VNode其实也没什么讲的,重点看一下之前没见过的函数,

    _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函数包含_c、_v、_m、_e、_s5个函数,其中_c、_v、_s之前都讲过,这里看一下_m、_e是什么。

_m

  直接看源码:

    Vue.prototype._m = renderStatic;

    function renderStatic(index, isInFor) {
var tree = this._staticTrees[index];
// 如果该静态节点已经被渲染且不在v-for中
// 复用该节点
if (tree && !isInFor) {
return Array.isArray(tree) ?
cloneVNodes(tree) :
cloneVNode(tree)
}
// otherwise, render a fresh tree.
tree = this._staticTrees[index] =
this.$options.staticRenderFns[index].call(this._renderProxy);
markStatic(tree, ("__static__" + index), false);
return tree
}

  可以看到,对于静态节点,vue做了一层缓存,尽量复用现成的虚拟DOM,但是目前是初次渲染,所以会创建一个新的。

  这里有两步。

第一步:this.$options.staticRenderFns[index].call(this._renderProxy)

  即取出staticRenderFns对应索引的函数并执行,将其缓存到_staticTrees上。

  之前在生成render函数时,将v-once的节点当成静态节点处理,弹入了该数组,函数如下:

    (function() {
with(this) {
return _c('span', [_v(_s(msg))])
}
})

  这里_s将msg字符串化,_v生成一个文本VNode,_c生成一个带有tag的VNode,children为之前的VNode。

第二步:markStatic(tree, ("__static__" + index), false)

  给VNode做标记。

    // tree => VNode
// key => __static__0
// isonce => false
function markStatic(tree, key, isOnce) {
if (Array.isArray(tree)) {
for (var i = 0; i < tree.length; i++) {
if (tree[i] && typeof tree[i] !== 'string') {
// key => __static__0_0...
markStaticNode(tree[i], (key + "_" + i), isOnce);
}
}
} else {
markStaticNode(tree, key, isOnce);
}
} function markStaticNode(node, key, isOnce) {
node.isStatic = true;
node.key = key;
node.isOnce = isOnce;
}

  比较简单,直接看结果了:Vue源码后记-其余内置指令(3)

  

_e

  这个其实我在注释里写了,就是一个空的div,瞄一眼源码,发现我错了:

    Vue.prototype._e = createEmptyVNode;

    var createEmptyVNode = function() {
var node = new VNode();
node.text = '';
node.isComment = true;
return node
};

  生成一个空的VNode,将其标记为注释,内容为空。

  //剩下的太简单,我不想讲啦!撤了,最近心情不好,兼容360,这客户我真是日了狗了。

  还没完,讲讲patch阶段那些directives、on、domProps是如何渲染的吧!

input

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

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

  VNode中的data属性细节可以看图,这里看一下domProps、on是如何渲染的。

  首先on是事件相关,刚发现chrome调试一个特别好用的东西,可以看函数流向!

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

  图中patch是渲染DOM的入口函数,createElm生成DOM节点,createChildren递归处理子节点,invokeCreateHooks则负责处理节点的属性,updateDOMListeners很显然是处理事件绑定,看一下源码:

    function updateDOMListeners(oldVnode, vnode) {
// 新旧VNode至少有一个存在on属性
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
// 保存属性
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
// 特殊情况处理
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, vnode.context);
}

  除去判断,这里会先对特殊情况下的on做特殊处理,然后再进行事件绑定,可以看下处理的代码:

    function normalizeEvents(on) {
var event;
/* istanbul ignore if */
if (isDef(on[RANGE_TOKEN])) {
// IE input[type=range] only supports `change` event
event = isIE ? 'change' : 'input';
on[event] = [].concat(on[RANGE_TOKEN], on[event] || []);
delete on[RANGE_TOKEN];
}
if (isDef(on[CHECKBOX_RADIO_TOKEN])) {
// Chrome fires microtasks in between click/change, leads to #4521
event = isChrome ? 'click' : 'change';
on[event] = [].concat(on[CHECKBOX_RADIO_TOKEN], on[event] || []);
delete on[CHECKBOX_RADIO_TOKEN];
}
}

  可以看到,特殊情况有两种:

  第一种是IE下的type=range,这个H5属性只支持IE10+,并且在IE中只有change事件。

  第二种是Chrome下的radio、checkbox,事件类型会被置换为click。

  接下来是事件的绑定函数:

    // on/oldOn => 新旧VNode事件
// add/remove$$1 => 事件的绑定与解绑函数
function updateListeners(on, oldOn, add, remove$$1, vm) {
var name, cur, old, event;
for (name in on) {
cur = on[name];
old = oldOn[name];
// str => obj
event = normalizeEvent(name);
if (isUndef(cur)) {
// error
}
// 添加事件
else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur);
}
add(event.name, cur, event.once, event.capture, event.passive);
}
// 事件替换
else if (cur !== old) {
old.fns = cur;
on[name] = old;
}
}
// 旧VNode事件解绑
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture);
}
}
}

  在遍历所有事件类型字符串的时候,由于可能会有特殊标记,所以会对其进行解析转换为一个对象:

    var normalizeEvent = cached(function(name) {
var passive = name.charAt(0) === '&';
name = passive ? name.slice(1) : name;
var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first
name = once$$1 ? name.slice(1) : name;
var capture = name.charAt(0) === '!';
name = capture ? name.slice(1) : name;
return {
name: name,
once: once$$1,
capture: capture,
passive: passive
}
});

  意思简单明了,这里就不解释了。

  接下来会将事件处理函数作为属性挂载到一个invoker函数上:

    // 将函数或函数数组作为属性挂到函数上 可以调用执行
function createFnInvoker(fns) {
function invoker() {
var arguments$1 = arguments; var fns = invoker.fns;
if (Array.isArray(fns)) {
for (var i = 0; i < fns.length; i++) {
fns[i].apply(null, arguments$1);
}
} else {
// return handler return value for single handlers
return fns.apply(null, arguments)
}
}
invoker.fns = fns;
return invoker
}

  这样做的原因可能是方便执行事件,有时候一个DOM会有多个相同事件,此时事件会是一个数组,通过这样处理后,无论是单一函数还是函数数组都可以通过直接调用invoker来执行。

  下面就是最后一个步骤,事件绑定:

    // event => input
// handler => invoker
// 剩余三个为之前normalizeEvent的属性
function add$1(event, handler, once$$1, capture, passive) {
// 一次性执行事件
if (once$$1) {
var oldHandler = handler;
var _target = target$1;
handler = function(ev) {
// 单参数 or 多参数
var res = arguments.length === 1 ?
oldHandler(ev) :
oldHandler.apply(null, arguments);
// 执行完立马解绑事件
if (res !== null) {
remove$2(event, handler, capture, _target);
}
};
}
target$1.addEventListener(
event,
handler,
supportsPassive ? {
capture: capture,
passive: passive
} :
capture
);
}

  对于一次性执行事件,这里的处理和jQuery源码里还是蛮像的,不过要简洁多了,这个很简单,没啥讲的。

  下面处理domProps,起初我以为这个属性是专门处理组件间传值那个props的,后来发现这属性有点瞎:

    function updateDOMProps(oldVnode, vnode) {
if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {
return
}
var key, cur;
var elm = vnode.elm;
var oldProps = oldVnode.data.domProps || {};
var props = vnode.data.domProps || {};
// __ob__属性代表动态变化的值
if (isDef(props.__ob__)) {
props = vnode.data.domProps = extend({}, props);
}
// 新VNode缺失属性置为空
for (key in oldProps) {
if (isUndef(props[key])) {
elm[key] = '';
}
}
for (key in props) {
cur = props[key];
// 这两种情况特殊处理
if (key === 'textContent' || key === 'innerHTML') {
          if (vnode.children) { vnode.children.length = 0; }
          if (cur === oldProps[key]) { continue }
            }

            if (key === 'value') {
// 先保存值 之后所有值会被转为字符串
elm._value = cur;
// avoid resetting cursor position when value is the same
var strCur = isUndef(cur) ? '' : String(cur);
if (shouldUpdateValue(elm, vnode, strCur)) {
elm.value = strCur;
}
} else {
elm[key] = cur;
}
}
} function shouldUpdateValue(elm, vnode, checkVal) {
return (!elm.composing && (
vnode.tag === 'option' ||
// document.activeElement !== elm && elm.value !== checkVal
isDirty(elm, checkVal) ||
// 处理trim,number
isInputChanged(elm, checkVal)
));
}

  其中包括两种情况,textContent、innerHTML、value以及其他,此处props的值为value,会判断当前DOM的值是否一致,然后进行修正。

  当props为textContent或innerHTML时,需要将所有子节点清空,然后将对应的属性修改为对应的值。

  至此,基本上必要的点已经完事了~啊。。。。88

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

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

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

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

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

  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. PostgreSQL 中定义自己需要的数据类型

    PostgreSQL解决某系数据库中的tinyint数据类型问题,创建自己需要的数据类型如下: CREATE DOMAIN tinyint AS smallint CONSTRAINT tinyint ...

  2. 《深入Java虚拟机学习笔记》- 第10章 栈和局部变量操作

    Java栈和局部变量操作 Java虚拟机是基于栈的机器,几乎所有Java虚拟机的指令都与操作数栈相关.栈操作包括把常量压入操作数栈.执行通用的栈操作.在操作数栈和局部变量之间往返传输值. 1常量入栈操 ...

  3. 粵語&sol;廣東話&sol;Cantonese 資料&sol;Material

    一.粵語歌詞網 1.海闊天空(粵語) 歌詞 今天我 寒夜裡看雪飄過 gam1 tin1 ngo5 hon4 je6 leoi5 hon3 syut3 piu1 gwo3 懷著冷卻了的心窩漂遠方 waa ...

  4. HIVE和HBASE区别11

    对于刚接触大数据的用户来说,要想区分Hive与HBase是有一定难度的.本文将尝试从其各自的定义.特点.限制.应用场景等角度来进行分析,以作抛砖引玉之用.  Hive是什么? Apache Hive是 ...

  5. 201521123072《Java程序设计》第1周学习总结

    201521123072<Java程序设计>第1周学习总结 标签(空格分隔): JAVA学习 1,本周学习总结 了解了JDK,JRE,JVM,学会使用cmd中一些简单的命令 了解了Java ...

  6. 【译】Asp&period;Net Identity Cookies 格式化-中英对照版

    原文出处 Trailmax Tech Max Vasilyev: ASP.Net MVC development in Aberdeen, Scotland I've been reached out ...

  7. sysbench

    安装 http://www.cnblogs.com/zhoujinyi/archive/2013/04/19/3029134.html http://space.itpub.net/758322/vi ...

  8. A - 低阶入门膜法 - K-th Number (主席树查询区间第k小)

    题目链接:https://cn.vjudge.net/contest/284294#problem/A 题目大意:主席树查询区间第k小. 具体思路:主席树入门. AC代码: #include<i ...

  9. JBPM使用方法、过程记录

    一.How to call Web Service Using JBPM 5, designer https://204.12.228.236/browse.php?u=ObFK10b3HDFCQUN ...

  10. Source InSight context 窗口丢失的解决办法

    我没关si的情况下强制关机导致的 Source InSight context 窗口丢失的解决办法 (2010-09-03 13:35:45) 转载▼     今天早晨改改CLI,上了趟WC,回来发现 ...