目录
一、虚拟DOM
1、虚拟DOM是什么
2、为什么要使用虚拟DOM
(1)浏览器显示网页的五步过程:
(2)虚拟DOM的优点
3、Diff算法
二、VNode简介
1、VNode是什么
2、VNode的作用
3、VNode的优点
4、VNode如何生成:在Vue源码中,VNode是通过一个构造函数生成的。
VNode的生成过程如下:
三、render函数
1、初识render函数:
2、为什么使用render函数
3、render函数的解析
四、实例属性
1、组件树的访问
2、虚拟DOM的访问:
五、实例方法
1、实例DOM方法的使用
2、实例event方法的使用
3、$watch()的使用
一、虚拟DOM
1、虚拟DOM是什么
(1) Vue通过建立一个虚拟DOM树对真实DOM发生的变化保持追踪。
(2)一棵真实DOM树的渲染需要先解析CSS样式和DOM树,然后将其整合成一棵渲染树,再通过布局算法去计算每个节点在浏览器中的位置,最终输出到显示器上。而虚拟DOM则可以理解为保存了一棵DOM树被渲染前所包含的所有信息,这些信息可以 通过对象的形式一直保存在内存中,并通过javascript的操作进行维护
2、为什么要使用虚拟DOM
(1)浏览器显示网页的五步过程:
a、解析标签,生成元素树(DOM树)
b、解析样式,生成样式树
c、生成元素与样式的关系
d、生成元素的显示坐标
e、显示页面
若修改真实的DOM元素,那么上述5步将重新走一遍,修改几次就走几遍,性能很差。如果使用虚拟DOM,虚拟DOM存储在内存中,对每个元素的修改是在内存中进行的,修改完后,比较虚拟DOM和真实DOM的差异,当有差异时,再一次过去更新网页的显示,而不是每次都重新按照浏览器的运行过程走。
(2)虚拟DOM的优点
a、保证性能的下限:框架的虚拟DOM需要适应任何上层API可能产生的操作,它的一些DOM操作的实现必须是普适的,所以它的性能并不是最优的,但是比起粗暴的DOM操作性能要好很多,因此框架的虚拟DOM至少可以保证在不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限
b、无须手动操作DOM:只需要编写好View-Model的代码逻辑,框架会根据虚拟DOM和数据双向绑定,以可以预期的方式更新视图,极大的提高开发效率
c、跨平台:虚拟DOM本质是javascript对象,而DOM与平台强相关。相比之下虚拟DOM可以进行更方便地跨平台操作
3、Diff算法
当虚拟DOM发生改变时,为了以最小的性能开销完成更新操作,需要比较新旧虚拟DOM,用于比较的算法称为Diff算法
Diff算法本身非常复杂,实现难度很大。常用的核心函数有两个
(1)patch(container,vnode):将虚拟DOM渲染成真正的DOM。即在初次渲染的时候,将虚拟的DOM渲染成真正的DOM,然后插入到容器里面
function createElement(vnode){
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || []
if(!tag){
return null
}
//创建真实的DOM元素
var vm = document.createElement(tag)
//定义属性
var attrName
for(attrName in attrs){
if(attrs.hasOwnProperty(attrName)){
vm.setAttribute(attrName,attrs[attrName])
}
}
//定义子元素
children.forEach(function(childVnode){
//给vm添加子元素,如果还有子节点,则递归的生成子节点
vm.appendChild(createElement(childVnode))
})
//返回真实的DOM元素
return vm
}
(2)patch(vnode,newVnode);再次渲染的时候,将新的VNode和旧的VNode进行对比,然后将之间的差异应用到所构建的真实DOM树上。
function updateChildren(vnode,newVnode){
var children = vnode.children || []
var newChildren = newVnode.children || []
//遍历现有的children
children.forEach(function(childVnode,index){
var newChildVnode = newChildren[index]
if(childVnode.tag === newChildVnode.tag){
//深层次对比,递归
updateChildren(childVnode,newChildVnode)
}else{
//两者tag不一样,则替换
replaceNode(childVnode,newChildVnode)
}
})
}
二、VNode简介
1、VNode是什么
VNode是JavaScript对象。VNode表示Virtual DOM,用JavaScript对象来描述真实的DOM把DOM标签,属性,内容都变成对象的属性。就像使用JavaScript对象对一种动物进行说明一样{name: ‘Hello Kitty’, age: 1, children: null}。
2、VNode的作用
通过render
将template
模版描述成VNode
,然后进行一系列操作之后形成真实的DOM
进行挂载。
3、VNode的优点
(1)兼容性强,不受执行环境的影响。VNode因为是JS对象,不管Node还是浏览器,都可以统一操作,从而获得了服务端渲染、原生渲染、手写渲染函数等能力。
(2)减少操作DOM,任何页面的变化,都只使用VNode进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能。
4、VNode如何生成:在Vue
源码中,VNode
是通过一个构造函数生成的。
export default class VNode {
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
}
VNode
的生成过程如下:
// 模版
<a class="demo" style="color: red" href="#">
generator VNode
</a>
// VNode描述
{
tag: 'a',
data: {
class: 'demo',
attrs: {
href: '#'
},
style: {
color: 'red'
}
},
children: ['generator VNode']
}
//这个JS对象,已经囊括了整个模板的所有信息,完全可以根据这个对象来构造真实DOM。
三、render函数
1、初识render函数:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App)
})
2、为什么使用render函数
vue推荐在绝大多数情况下使用template来创建我们的HTML。然而在一些场景中,我们真的需要JavaScript的完全编程的能力,这就是render函数,它比template更接近编译器。(这是官方的话)
import Vue from "vue";
这一个引入你看似没有任何问题,但问题恰恰就是出在这。在不同版本的vue中,有vue.js和vue.runtime.xxx.js这两种js文件。其中
(1)vue.js是完整版的vue,包含核心功能+模板解析器。
(2)vue.runtime.xxx.js是运行版vue,只包含核心功能,没有模板解析器。
vue开发者为了让我们打包的文件能尽可能小一点,在上述引入的是运行版vue。因为vue.runtime.xxx.js没有模板解析器,所以不能使 用template配置项,这时候就需要使用render函数去接收到的createElement函数去指定具体内容,创建html模板。
3、render函数的解析
render 函数即渲染函数,它是个函数,它的参数 createElement 也是个函数。
上边的代码中 render: h => h(App) ,这是 ES6的箭头函数的写法,可以把 h 当作 createElement 的别名。所以这段代码其实相当 于:
render: function (createElement) {
return createElement(App);
}
这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实DOM 节点,并挂载到根节点上。
createElement 函数的返回值是 VNode(即:虚拟节点)
createElement 函数的3个参数
(1)一个 HTML 标签字符串,组件选项对象,或者解析上述任何一种的一个 async 异步函数。类型:String | Object | Function。必 需。
(2)一个包含模板相关属性的数据对象,你可以在 template 中使用这些特性。类型:Object。可选。
(3)子虚拟节点 (VNodes),由 createElement() 构建而成,也可以使用字符串来生成“文本虚拟节点”。类型:String | Array。可选。
new Vue({
el: '#app',
render:function (createElement) {
//1.普通用法
// createElement(标签,{属性},[内容])
return createElement("h2",{class:"box"},['hello',createElement("button",["按钮"])])
}
})
同时createElement也可以传进去一个组件,因此
render: h => h(App)
//等同于
render:function (createElement) {
return createElement(App)
}
四、实例属性
Vue实例暴露了一些有用的实例属性与方法,这些实例属性与方法都有前缀$,以便与代理的数据属性区分
1、组件树的访问
(1)$parent:用来访问组件实例的父实例(父组件)
(2)$root:用来访问当前组件树的根实例(根组件)
(3)$children:用来访问当前组件实例的直接子组件实例
(4)$refs:用来访问v-refs指令的子组件
2、虚拟DOM的访问:
(1)$el:表示挂载当前组件实例的DOM元素
(2)$data:用来访问组件实例观察的数据对象
五、实例方法
1、实例DOM方法的使用
(1)$appendTo(elementOrSelector,callback):将el所指的DOM元素插入目标元素
(2)$before(elementOrSelector,callback):将el所指的DOM元素插入目标元素之前
(3)$after(elementOrSelector,callback):将el所指的DOM元素插入目标元素之后
(4)$remove(callback):将el所指的DOM元素或片段从DOM中删除
(5)$nextTick(callback):用来在下一次DOM更新循环后执行指定的回调函数
2、实例event方法的使用
(1)$on(event,callback):监听实例的自定义事件
(2)$once(event,callback):监听实例的自定义事件,但是只能触发一次
(3)$dispatch(event,args):派发事件,先在当前实例触发,再沿父链一层层向上,对应的监听函数返回false则停止
(4)$emit(event,args):触发事件
3、$watch()的使用
var obj = { a: 1 }
var vm = new Vue({
el: '#app',
data: obj
})
vm.$data === obj //-> true
vm.$el === document.getElementById('app') // ->true
vm.$watch('a',function(newVal,oldVal){
//这个回调函数将在vm.a改变后触发
})