Vue2数据驱动渲染(render、update)
上一篇文章我们介绍了 Vue2模版编译原理,这一章我们的目标是弄清楚模版 template和响应式数据是如何渲染成最终的DOM。数据更新驱动视图变化这部分后期会单独讲解
我们先看一下模版和响应式数据是如何渲染成最终DOM 的流程
Vue初始化
new Vue发生了什么
Vue入口构造函数
function Vue(options) {
this._init(options) // options就是用户的选项
...
}
initMixin(Vue) // 在Vue原型上扩展初始化相关的方法,_init、$mount 等
initLifeCycle(Vue) // 在Vue原型上扩展渲染相关的方法,_render、_c、_v、_s、_update 等
export default Vue
initMixin、initLifeCycle方法
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this
vm.$options = options // 将用户的选项挂载到实例上
// 初始化数据
initState(vm)
if (options.el) {
vm.$mount(options.el)
}
}
Vue.prototype.$mount = function (el) {
const vm = this
el = document.querySelector(el)
let ops = vm.$options
// 这里需要对模板进行编译
const render = compileToFunction(template)
ops.render = render
// 实例挂载
mountComponent(vm, el)
}
}
export function initLifeCycle(Vue) {
Vue.prototype._render = function () {} // 渲染方法
Vue.prototype._c = function () {} // 创建节点虚拟节点
Vue.prototype._v = function () {} // 创建文本虚拟节点
Vue.prototype._s = function () {} // 处理变量
Vue.prototype._update = function () {} // 初始化元素 和 更新元素
}
在 initMixin 方法中,我们重点关注 compileToFunction模版编译 和 mountComponent实例挂载 2个方法。我们已经在上一篇文章详细介绍过 compileToFunction 编译过程,接下来我们就把重心放在 mountComponent 方法上,它会用到在 initLifeCycle 方法给Vue原型上扩展的方法,在 render 和 update章节会做详细讲解
实例挂载
mountComponent 方法主要是 实例化了一个渲染 watcher,updateComponent 作为回调会立即执行一次。watcher 还有一个其他作用,就是当响应式数据发生变化时,也会通过内部的 update方法执行updateComponent 回调。
现在我们先无需了解 watcher 的内部实现及其原理,后面会作详细介绍
vm._render 方法会创建一个虚拟DOM(即以 VNode节点作为基础的树),vm._update 方法则是把这个虚拟DOM 渲染成一个真实的 DOM 并渲染出来
export function mountComponent(vm, el) {
// 这里的el 是通过querySelector获取的
vm.$el = el
const updateComponent = () => {
// 1.调用render方法创建虚拟DOM,即以 VNode节点作为基础的树
const vnode = vm._render() // 内部调用 vm.$options.render()
// 2.根据虚拟DOM 产生真实DOM,插入到el元素中
vm._update(vnode)
}
// 实例化一个渲染watcher,true用于标识是一个渲染watche
const watcher = new Watcher(vm, updateComponent, true)
}
接下来我们会重点分析最核心的 2 个方法:vm._render 和 vm._update
render
我们需要在Vue原型上扩展 _render 方法
Vue.prototype._render = function () {
// 当渲染的时候会去实例中取值,我们就可以将属性和视图绑定在一起
const vm = this
return vm.$options.render.call(vm) // 模版编译后生成的render方法
}
在之前的 Vue $mount过程中,我们已通过 compileToFunction方法将模版template 编译成 render方法,其返回一个 虚拟DOM。template转化成render函数的结果如下
<div id="app" style="color: red; background: yellow">
hello {{name}} world
<span></span>
</div>
ƒ anonymous(
) {
with(this){
return _c('div',{id:"app",style:{"color":"red","background":"yellow"}},
_v("hello"+_s(name)+"world"),
_c('span',null))
}
}
render 方法内部使用了 _c、_v、_s 方法,我们也需要在Vue原型上扩展它们
- _c: 创建节点虚拟节点(VNode)
- _v: 创建文本虚拟节点(VNode)
- _s: 处理变量
// _c('div',{},...children)
// _c('div',{id:"app",style:{"color":"red"," background":"yellow"}},_v("hello"+_s(name)+"world"),_c('span',null))
Vue.prototype._c = function () {
return createElementVNode(this, ...arguments)
}
// _v(text)
Vue.prototype._v = function () {
return createTextVNode(this, ...arguments)
}
Vue.prototype._s = function (value) {
if (typeof value !== 'object') return value
return JSON.stringify(value)
}
接下来我们看一下 createElementVNode 和 createTextVNode 是如何创建 VNode 的
createElement
每个 VNode 有 children,children 每个元素也是一个 VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树结构,即我们的虚拟DOM
// h() _c() 创建元素的虚拟节点 VNode
export function createElementVNode(vm, tag, data, ...children) {
if (data == null) {
data = {}
}
let key = data.key
if (key) {
delete data.key
}
return vnode(vm, tag, key, data, children)
}
// _v() 创建文本虚拟节点
export function createTextVNode(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text)
}
// 虚拟节点
function vnode(vm, tag, key, data, children, text) {
return {
vm,
tag,
key,
data,
children,
text,
// ....
}
}
VNode 和 AST一样吗?
我们的 VNode 描述的是 DOM元素
AST 做的是语法层面的转化,它描述的是语法本身 ,可以描述 js css html
虚拟DOM
DOM是很慢的,其元素非常庞大,当我们频繁的去做 DOM更新,会产生一定的性能问题,我们可以直观感受一下div元素包含的海量属性

在Javascript对象中,Virtual DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别。
实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
vue中 VNode结构如下
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
/*当前节点的标签名*/
this.tag = tag
/*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
this.data = data
/*当前节点的子节点,是一个数组*/
this.children = children
/*当前节点的文本*/
this.text = text
/*当前虚拟节点对应的真实dom节点*/
this.elm = elm
/*当前节点的名字空间*/
this.ns = undefined
/*编译作用域*/
this.context = context
/*函数化组件作用域*/
this.functionalContext = undefined
/*节点的key属性,被当作节点的标志,用以优化*/
this.key = data && data.key
/*组件的option选项*/
this.componentOptions = componentOptions
/*当前节点对应的组件的实例*/
this.componentInstance = undefined
/*当前节点的父节点*/
this.parent = undefined
/*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
this.raw = false
/*静态节点标志*/
this.isStatic = false
/*是否作为跟节点插入*/
this.isRootInsert = true
/*是否为注释节点*/
this.isComment = false
/*是否为克隆节点*/
this.isCloned = false
/*是否有v-once指令*/
this.isOnce = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next https://github.com/answershuto/learnVue*/
get child (): Component | void {
return this.componentInstance
}
}
虚拟DOM的优点
- 提升效率。操作 DOM的代价是昂贵的,使用 diff算法,可以减少 JavaScript操作真实DOM 带来的性能消耗
通过 Virtual DOM 改变真正的 DOM并不比直接操作 DOM效率更高。恰恰相反,Virtual DOM 仍需要调用 DOM API 去操作 DOM,并且还会额外占用内存。but!!!我们可以通过 diff算法,找到需要更新的最小单位,最大限度地减少DOM操作。而且在大量频繁数据更新后,并不会立即重流重绘,而是批量操作真实的 DOM,最大限度的减少DOM操作,从而提升性能
- 跨平台。抽象了原本的渲染过程,提供了一个中间抽象层(runtime-dom/src/nodeOps),使我们可以在不接触真实DOM 的情况下操作 DOM,实现了跨平台的能力。而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,也可以是近期很火热的小程序。
runtime-dom/src/nodeOps 这里存放常见 DOM操作API,不同运行时(浏览器、小程序......)提供的具体实现不一样,最终将操作方法传递到 runtime-core中,所以 runtime-core不需要关心平台相关代码
update
vm._update 的作用就是把 VNode 渲染成真实的DOM
vm._update 被调用的时机有 2 个,一个是首次渲染,一个是数据更新的时候。我们暂时先不考虑数据更新部分
Vue.prototype._update = function (vnode) {
// 将vnode转化成真实dom
const vm = this
const el = vm.$el
// patch既有初始化元素的功能 ,又有更新元素的功能
vm.$el = patch(el, vnode)
}
vm._update 核心就是调用 patch 方法,parentElm 就是 oldVNode 的父元素,即我们的 body 节点,通过 createElm 递归创建一个完整的 DOM树 并 插入到 body 节点中,然后删除老节点
// 利用vnode创建真实元素
function createElm(vnode) {
let { tag, data, children, text } = vnode
if (typeof tag === 'string') {
// 标签
vnode.el = document.createElement(tag) // 这里将真实节点和虚拟节点对应起来,后续如果修改属性了
patchProps(vnode.el, data)
children.forEach(child => {
vnode.el.appendChild(createElm(child))
})
} else {
vnode.el = document.createTextNode(text)
}
return vnode.el
}
// 对比属性打补丁
function patchProps(el, props) {
for (let key in props) {
if (key === 'style') {
// { color: 'red', "background": 'yellow' }
for (let styleName in props.style) {
console.log(styleName, props.style[styleName])
el.style[styleName] = props.style[styleName]
}
} else {
el.setAttribute(key, props[key])
}
}
}
// patch既有初始化元素的功能 ,又有更新元素的功能
function patch(oldVNode, vnode) {
// 写的是初渲染流程
const isRealElement = oldVNode.nodeType
if (isRealElement) {
const elm = oldVNode // 获取真实元素
const parentElm = elm.parentNode // 拿到父元素
let newElm = createElm(vnode)
parentElm.insertBefore(newElm, elm.nextSibling)
parentElm.removeChild(elm) // 删除老节点
return newElm
} else {
// diff算法,暂时先不考虑
}
}
参考文档
Vue2数据驱动渲染(render、update)的更多相关文章
- vue2.0之render函数
虽然vue推荐用template来创建你的html,但是在某些时候你也会用到render函数. 虚拟DOM Vue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪.请近距离看一下这行代码 ...
- vue1 & vue2 数据驱动更新视图机制对比
vue1 小粒度更新,精确追踪到数据变化所影响的dom变化,精确更新变化的dom 具体实现为,维护 observer watcher directive 三个类 ·observer负责监听数据变化,并 ...
- 5 cocos2dx 3.0源码分析 渲染 render
渲染,感觉这个挺重要了,这里代入一个简单的例子 Sprite 建立及到最后的画在屏幕上, 我们描述一下这个渲染的流程: 1 sprite 初始化(纹理, 坐标,及当前元素的坐标大小信息) 2 主循 ...
- Django——2 路由分配设置 re_path正则匹配 include总路由 url传参 name使用 模板渲染render方法 模板渲染方法
Django 路由分配设置 re_path正则匹配 include总路由设置 url额外参数的传递 name的使用 模板的渲染:render方法 路由的分配中, 可以设定相应的转换器加以约束,比如 ...
- Layui数据表格加入自定义扩展方法(重新渲染Render当前页数据)
具体开发中遇到的问题如下, 数据表格的重新渲染或重新加载会导致当前操作的分页 或 配置被清空.我正在操作第5页,重新渲染后就回到了最原始第1页. 需要达到的效果是: 不调用接口,仅仅只是从table. ...
- 解析vue2.0中render:h=>h(App)的具体意思
render:h=>h(App)是ES6中的箭头函数写法,等价于render:function(h){return h(App);}. 注意点:1.箭头函数中的this是 指向 包裹this所在 ...
- vue2——指令渲染,{{}}渲染
博客地址 :https://www.cnblogs.com/sandraryan/ 声明式的渲染,以{{}}的形式调用数据 <!DOCTYPE html> <html lang=&q ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十五║初探SSR服务端渲染(个人博客二)
缘起 时间真快,现在已经是这个系列教程的下半部 Vue 第 12 篇了,昨天我也简单思考了下,可能明天再来一篇,Vue 就基本告一段落了,因为什么呢,这里给大家说个题外话,当时写博文的时候,只是想给大 ...
- 从壹开始前后端分离 [ Vue2.0+.NetCore2.1] 二十六║Client渲染、Server渲染知多少{补充}
前言 书接上文,昨天简单的说到了 SSR 服务端渲染的相关内容<二十五║初探SSR服务端渲染>,主要说明了相关概念,以及为什么使用等,昨天的一个小栗子因为时间问题,没有好好的给大家铺开来讲 ...
- ruby on rails 中render的使用
最近写ror,因为比较菜,很多东西不知道,只能看一点查一点了 render 先上点搜集的常用方式 render :action => "long_goal", :layout ...
随机推荐
- python+pytest接口自动化
本篇文章是用python+pytest写了一个简单的接口自动化脚本,外加循环请求接口的语法,大家可以参考~ 实例一: import requestsimport pytestimport time c ...
- node 版本管理
32位版本的node,运行较大的项目,会内存溢出.所以建议安装64位的版本,且运行速度比32位快.node14以下的版本支持node-sass,版本node16以上的不再支持node-sass,而sa ...
- 最短路算法之 Dijkstra
部分内容参考了李煜东的<算法竞赛进阶指南>,在此声明. 单源最短路径 单源最短路径问题,是说,给定一张有向图(无向图)\(G=(V,E)\) ,\(V\) 是点集,\(E\) 是边集,\( ...
- OO课程第二阶段(实验和期中试题)总结Blog2
OO课程第二阶段(实验和期中试题)总结Blog2 前言:学习OOP课程的第二阶段已经结束了,在此进行对于知识点,题量,难度的个人看法. 学习OOP课程的第二阶段已经结束了,较第一次阶段学习难度加大,学 ...
- gpio 理解
NVIC :NVIC_Init(&NVIC_Initsture); 1.NVIC只是设置某一种中断的优先级,而不是打开某种中断. 2.ppp_ITConfig():才是开/关具体某种中断使能位 ...
- 微信点击链接:debugx5.qq.com提示您使用的不是x5内核
微信点击链接:debugx5.qq.com提示您使用的不是x5内核 因为要测试小程序,需要webview调试功能,正常来说在微信任意一个聊天窗口,输入:debugx5.qq.com,点击该链接就可以, ...
- JAVA - 判断两个浮点数相等
JAVA - 判断两个浮点数相等 背景知识 float型和double型是JAVA的基本类型,用于浮点数表示,在JAVA中float型占4个字节32位,double型占8个字节64位,一般比较适合用于 ...
- django_设计模式和模板层
一.django的设计模式 1.传统MVC设计模式 (1)MVC(Model-View-Controller,模型-视图-控制器)模式. M--模型层,主要用于对数据库的封装: V--视图层,用于向用 ...
- MPC
Just for anyone searching for code. I found it here: https://drive.google.com/drive/folders/0BzLEHBD ...
- 正向代理和反向代理和spring的动态代理模式有几种?默认是那种?如何切换?
spring的动态代理模式有几种?默认是那种?如何切换? spring的动态的代理模式有两种 JDK动态代理,基于接口(默认代理模式),CGLIB动态代理(若要使用需要进行配置) JDK动态代理是由j ...