写于vue3.0发布前夕的helloworld之四
OK。接上回到render:
with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(msg))])}
接着开始执行_s:一言以蔽之,就是undefined 和null输出一个空字符串,其他类型需要转换,然后开始执行_v方法:
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
就是创建了一个虚拟的文本节点,然后执行_c方法:
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
这个之前有提过,等执行完这些方法之后,我们就会有一个虚拟DOM节点,长这样:

至此,vm._render执行完毕,代码最终返回到updateComponent方法里,开始执行,vm._update:
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
restoreActiveInstance();
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null;
}
if (vm.$el) {
vm.$el.__vue__ = vm;
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el;
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
};
和其他函数一样一开始先拿一些所需用到的变量,这里因为是第一次挂载,prevVnode为undefined,__patch__方法执行remove移除策略,代码注释中也有讲到:
function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
var isInitialPatch = false;
var insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode
} else {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
);
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode);
}
// replacing existing element
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
var ancestor = vnode.parent;
var patchable = isPatchable(vnode);
while (ancestor) {
for (var i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor);
}
ancestor.elm = vnode.elm;
if (patchable) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, ancestor);
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
var insert = ancestor.data.hook.insert;
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
insert.fns[i$2]();
}
}
} else {
registerRef(ancestor);
}
ancestor = ancestor.parent;
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
}
patch方法中,首先会初始化isRealElement变量,这里我们我们的oldVnode调用的时候直接传的是div的引用,故而为true,所以if-else分支走了最下面的一个,当然这是第一次挂载,当为更新的时候,isRealElement的值为false,就会执行patchVnode方法,去patch新旧Vnode, 这里注意,patch的时候,patchVnode,只会在两个vnode是相同的时候才会patch,而什么时候是两个相同的vnode呢?就是下面这个方法的返回值为真的时候:
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
所以看到key值得重要性了么。
继续到我们得patch得方法,接着会调emptyNodeAt,创造一个tag是div得空Vnode,然后将挂载上得div元素引用和body得引用拿到,使用createElm方法创建真实得dom节点:
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode);
} vnode.isRootInsert = !nested; // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
} var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
{
if (data && data.pre) {
creatingElmInVPre++;
}
if (isUnknownElement$$1(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
);
}
} vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode); /* istanbul ignore if */
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
} if (data && data.pre) {
creatingElmInVPre--;
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
一进来得if是说如果当前得vnode在前一个render中,现在被当作一个新得vnode使用时,重写他的elm属性可能会引发后续得patch error,这个时候应该clone他。下来会使用createComponent方法做个检查,接着拿到vnode得data,children,tag,以上三个都是创建dom节点时需要用到的,setscope之后创建children节点,createChildren就是循环children依次调用createElm完成得,这里子节点因为是文本节点最后调用了nodeOps.createTextNode完成创建,并插入到父级得el上,然后会使用invokeCreateHooks更新dom属性,最后将完成得新的dom节点插入到body上。
执行完createElm方法后,就会将新生成得dom节点插入到dom树上,然后我们得代码继续走,返回到patch方法中,移除原来得dom节点。然后patch执行完毕,返回到Vue.prototype._update方法中,执行一些标记操作,然后继续返回,直到返回到Watcher.prototype.get中,接下来执行popTarget方法,将当前watcher实例从targetStack中拿出来,并将dep.target设置为undefined,这也预示着此次依赖收集已经结束,接着执行Watcher.prototype.cleanupDeps重新初始化deps属性,然后回到依赖收集最开始得那条语句,也就是watcher得构造函数里的:
this.value = this.lazy
? undefined
: this.get();
至此,dom挂载结束,依赖收集完毕,然后回到mountComponent方法,唤起mounted生命周期钩子,层层返回。然后源码版hello wrold至此结束。随后还会有一个异步代码,唤起devtools,这是后话。
写于vue3.0发布前夕的helloworld之四的更多相关文章
- 写于vue3.0发布前夕的helloworld
前言: vue3.0马上要来了,于今昔写一篇vue将一个字符串hellowrold渲染于页面的过程,慰藉我这几个月写vue的'枯燥'. 源码版本是2.6.10. 开始: 我们的模板足够简单: < ...
- 写于vue3.0发布前夕的helloworld之三
接上,watcher构造函数: var Watcher = function Watcher ( vm, expOrFn, cb, options, isRen ...
- VUE3.0发布,自己搞个文档网站
9月19日,尤大神发表了VUE3.0版本的演说,强大且震撼,这两天一直在找网站文档,可能还未被百度收录,未找到文档网站.后来在github上面找到了中文代码. 地址为:https://github.c ...
- 预计2019年发布的Vue3.0到底有什么不一样的地方?
摘要: Vue 3.0预览. 原文:预计今年发布的Vue3.0到底有什么不一样的地方? 作者:小肆 微信公众号:技术放肆聊 Fundebug经授权转载,版权归原作者所有. 还有几个月距离 vue2 的 ...
- 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...
- 纯小白入手 vue3.0 CLI - 3.3 - 路由的导航守卫
vue3.0 CLI 真小白一步一步入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 尽量把纷繁的知识,肢解重组成为可以堆砌的知识. ...
- 纯小白入手 vue3.0 CLI - 2.1 - 组件 ( component )
vue3.0 CLI 真小白入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 我的 github 地址 - vue3.0Study ...
- 快速进阶Vue3.0
在2019.10.5日发布了Vue3.0预览版源码,但是预计最早需要等到 2020 年第一季度才有可能发布 3.0 正式版. 可以直接看 github源码. 新版Vue 3.0计划并已实现的主要架构改 ...
- Vue3实战系列:Vue3.0 + Vant3.0 搭建种子项目
最近在用 Vue3 写一个开源的商城项目,开源后让大家也可以用现成的 Vue3 大型商城项目源码来练练手,目前处于开发阶段,过程中用到了 Vant3.0,于是就整理了这篇文章来讲一下如何使用 Vue3 ...
- Laf v1.0 发布:函数计算只有两种,30s 放弃的和 30s 上线的
一般情况下,开发一个系统都需要前端和后端,仅靠一个人几乎无法胜任,需要考虑的特性和功能非常多,比如: 需要一个数据库来存放数据: 需要一个文件存储来存放各种文件,比如图片文件: 后端需要提供接口供前端 ...
随机推荐
- 关于Qt高分屏缩放几个知识点
在windows上经常遇到高分屏缩放的问题,很头疼,貌似这东西就是windows首发的. 在Qt4时代的程序遇到高分屏缩放,不作任何处理,毕竟Qt4时代(2010年以前)出来的时候几乎还没高分屏缩放这 ...
- Qt开源作品33-图片开关控件
一.前言 进入智能手机时代以来,各种各样的APP大行其道,手机上面的APP有很多流行的元素,开关按钮个人非常喜欢,手机QQ.360卫士.金山毒霸等,都有很多开关控制一些操作,在WINFORM项目上,如 ...
- 监听sessionStorage中值的变化
应用场景:将登录接口所返回的用户信息存入sessionStorage,在其他地方需要使用到用户信息,但不知道什么时候接口请求完,此时可以监听sessionStorage中值的变化. 应用:Event自 ...
- 如何快速的开发一个完整的iOS直播app(推流篇)
开发一款直播app,肯定需要流媒体服务器,本篇主要讲解直播中流媒体服务器搭建,并且讲解了如何利用FFMPEG编码和推流,并且介绍了FFMPEG常见命令. 效果 一.安装Homebrew Homebre ...
- biancheng-Maven依赖
目录http://c.biancheng.net/maven2/profile.html 1Maven简介2Maven安装与配置3Maven POM4创建Maven项目5Maven项目的构建与测试6M ...
- Hetemit pg walkthrough Intermediate
nmap ┌──(root㉿kali)-[~] └─# nmap -p- -A 192.168.157.117 Starting Nmap 7.94SVN ( https://nmap.org ) a ...
- Cockpit pg walkthrough Intermediate
nmap 发现两个 web站 80 和 9090 还有 22端口 dirsearch 发现80端口有login.php 登录界面 发现没有弱口令 测试sql注入 测试了一会发现密码 password= ...
- 边缘计算与MEC浅谈
本文分享自天翼云开发者社区<边缘计算与MEC浅谈>,作者:y****n 一.什么是边缘计算 边缘计算是在靠近物或数据源头的网络边缘侧,通过融合网络.计算.存储.应用核心能力的分布式开放平台 ...
- 云网融合再加码!天翼云SD-WAN PON来了!
近日,在中国电信集团政企信息服务事业群的组织下,天翼云科技有限公司联合中国电信上海分公司,成功举办2023年天翼云SD-WAN PON融合网关试点成果总结及推广会.会上,中国电信集团政企领导和专家,以 ...
- Google 常用语法说明
Google 常用语法说明 背景 Google Hacking,作为一种利用谷歌搜索引擎的强大能力来挖掘互联网中敏感或未公开信息的技巧,已成为安全研究.漏洞挖掘及信息搜集领域的重要工具. 通过精心构造 ...