vue源码阅读—02—数据驱动
数据驱动还有的疑问:
2.render函数如何产生vnode?
在core/vdom/render函数里有vm_render方法,调用这个方法就会调用我们自己定义的render函数,然后通过我们传递的h函数其实是vm.$creatElement方法;
然后在vm.$creatElement方法根据不同的情况,new 不同的Vnode类得到不同的vnode实例即可;
3.如果optoins里没有el?
optons没有el,那必须有new Vue().mount(el),否则会报错,因为根本不知道挂载在哪
4.options里没有render也米有template可以吗?
当然可以。首先会去判断有没有render,有render直接去吧render转化为vnode;
其次,如果没有render,会把template通过complier模块转化为render,再转化为vnode;
最后,如果没有render没有template,那么把el所代表的html当作template,转化为render再转化为vnode;
不过建立在我们使用compiler+runtime版本,才会把el转化为render;
如果我们使用的是runtime-only版本,那么会报错,因为没有什么东西可以把el转化为render;而在lifetcycle.js的mountComponent阶段,必须要有render函数,否则报错;
vue是数据驱动的,我们使用vue可以减少对dom的操作,只需要修改数据,vue会自动帮助我们修改dom;
this._c和this._init这些都是私有变量和私有方法,只能Vue函数内部使用,我们new出来的vue实例不应该在外部去调用它们这些方法;
this.$foo这个变量前面加上$表示,这个变量加一个$表示是Vue官方团队定义到vm实例上的,和我们自己私人定义到vm实例上的变量有所区别,这个变量是公有的,我们在自己new出来的vue实例也可以使用;;
数据驱动:
传递一个js对象,如何映射到dom上的:
一、vdom是什么?
Virtual DOM 就是用一个原生的 JS 对象去描述一个浏览器的 DOM 节点。
二、VDOM 为什么比真实 DOM性能开销小?
「Virtual Dom 的优势」其实这道题目面试官更想听到的答案不是上来就说「直接操作/频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到今天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。
首先我们需要知道:
1、DOM 引擎、JS 引擎 相互独立,但又工作在同一线程(主线程) JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最后激活 JS 引擎并继续执行。所以直接一次dom操作比较耗时;
2若有频繁的 DOM API 调用,且浏览器厂商不做“批量处理”优化, 引擎间切换的单位代价将迅速积累。
3若其中有强制重绘的 DOM API 调用,重新计算布局、重新绘制图像会引起更大的性能消耗。
其次,我们要知道vdom:
- 轻量:vdom并没有操作dom的方法,所以vdom整体大小是非常轻量的;
- js修改快:vdom用js来描述,js的创建和修改相对真实dom都是非常快速的,所以vdom的性能比较高;
- 同意最后操作:虚拟 DOM 不会立马进行排版与重绘操作,会在最后批量处理,有利于减少引擎切换的代价;
- diff算法尽量减少操作:虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分(diff算法),最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗。
VDOM 比真实 DOM的优点?
1.绝大部分情况下,性能开销小;除少数所有dom全部都要变化的情况下,这个时候由于vdom还要做diff算法,所以可能性能开销反而会更大;
2.由于是js来描述真实dom,所以是可以跨平台的;
三、vdom怎么产生?
在 Vue.js 中,Virtual DOM 是用 VNode
这么一个 Class 去描述,它是定义在 src/core/vdom/vnode.js
中的。所以,vode就是vdom,
那vnode怎么产生的呢?
在vue的render函数中,通过createElelment方法也即是h函数产生vnode。
四、vdom到真实dom的过程
Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。
_update
方法的作用?
把 VNode 渲染成真实的 DOM,它的定义在 src/core/instance/lifecycle.js
中:
Vue 的 _update
是实例的一个私有方法,它被调用的时机有 2 个,一个是首次渲染,一个是数据更新的时候
new Vue:
1.调用this_Init
2.调$mount($el)
3.如果是runtime-compiler版本,先调自己的mount方法,在这个方法里将template转化为render,再调用通用的mount方法;
如果是runtime-only版本,这种版本一般是工程化的,.vue文件的template会被vue-loader转化为render好了,所以直接调用通过mount方法;
4.通用的mount方法,
4.1确保存在render函数,调用beforeMounted钩子函数;
4.2调用mountComponent方法,这个方法做了两个操作
- 定义一个函数updateComponnet = ()=》{vm._update(vm._render())}
- new 了一个渲染watcher并将updateComponent赋值给watcher的getter,并调用getter
5.
5.1vm._render去实例化vnode节点
5.2vm._update将vnode节点调用patch操作,第一步将根据vnode的tag创建真实dom;第二步先把本vnode节点的子vnode节点通过node的insretBefore或appendChild插入到本vnode节点,再把自身vnode节点插入到真实dom上;第三步把挂载的div#app这个dom删除掉完成替换;
7.结束;
组件对象有两个阶段:
第一个是创建即实例化init阶段;
第二个是挂载mount阶段;又分为
- 编译阶段:将模板编译为render函数、
- 渲染阶段:通过render函数实例化组件Vnode
- patch阶段:即将vnode映射到真实dom上;
组件对象、组件构造函数、组件实例、组件vnode实例;
1.组件对象是我们自定义的;
2.组件构造函数是在实例化子组件占位符vnode时,把子组件对象变成了一个继承vue的子组件构造函数;
3.组件实例:new 组件构造函数时,得到组件实例;组件实例有组件对象的所有配置项,通过vm.$options可以取到;然后vm实例还有很多属性和方法;
4.组件vnode:通过_render()渲染阶段,调用vm.$options.render函数,生成一个组件的vnode实例;
为什么自己写的render函数h参数是vm.$createelemt,而编译器编译模板生成的redner函数用的是vm._c?
这个是我们使用vuejs的complier模块把模板编译成render函数的结果,我们看到,render变量引用地址所指向是一个匿名函数,并且这个匿名函数没有参数,所以在vm._render()中调用 vnode = render.call(vm._renderProxy, vm.$createElement)时即使传递了参数vm.$createElement也没有用,照样调用vm._c;
根节点如何渲染的?
渲染根节点的时候,会把 挂载的真实dom元素 作为第一个参数即oldVNode传给patch方法;
patch方法里,会通过emptyNodeAt方法把真实dom元素也生成vnode节点然后再重新赋值给oldVnode (不过dom元素并没有消失,通oldvnode.elm即可获取)
patch的最后,会把老的el所代表的dom摧毁掉,然后挂载新的dom元素。
流程: