好家伙,
前情提要:
在上一篇我们已经成功将ast语法树转换为渲染函数
现在我们继续
1.项目目录
代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍,
胜过我解释给你听一万遍
新增文件:vnode/index.js vnode/patch.js lifecycle.js
2.虚拟节点
2.1.什么是虚拟节点?
虚拟节点(Virtual Node)是前端开发中的一个概念,它是一个轻量级的JavaScript对象,用来描述真实DOM树中的一个节点(元素)。
虚拟节点包括了该节点的标签名、属性、子节点等信息。
在一些前端框架(比如Vue.js、React等)中,使用虚拟节点来表示整个DOM结构,通过对比新旧虚拟节点,确定发生了哪些变化,并且最小化对真实DOM的操作。
这种方式可以提高性能,避免频繁地直接操作真实DOM带来的性能消耗。
2.2.为什么要用虚拟节点?
使用虚拟节点的主要原因是为了提高性能和渲染效率。下面是一些使用虚拟节点的好处:
** 1.减少直接操作真实DOM带来的性能损耗:直接对真实DOM进行频繁的增删改查操作会导致浏览器进行重排和重绘,这是非常耗费性能的。**
** 而使用虚拟节点,可以进行批量的DOM操作,最终只对差异部分进行真实DOM的操作,大大减少了性能损耗**
** 2.最小化真实DOM的操作次数:虚拟DOM通过对比新旧虚拟节点的差异,找出需要更新的部分,然后只对这部分进行真实DOM的操作,避免不必要的操作。**
** 这可以减少页面重新渲染的次数,加快页面的响应速度。**
** 3.提供更灵活的渲染策略:虚拟节点提供了一种对DOM进行抽象的方式,可以根据具体的需求,实现更灵活的渲染策略。**
** 例如,可以通过操作虚拟节点来实现页面的动态更新、条件渲染、组件化等功能。**
** 4.跨平台能力:使用虚拟节点,开发者可以将同一套代码渲染到不同的平台(如浏览器、移动端、桌面端等),只需要针对不同平台进行虚拟DOM的适配即可,提高了代码的复用性和跨平台能力。**
** 总而言之,使用虚拟节点可以优化DOM操作、提高性能、提供灵活的渲染策略,并具有跨平台的能力,这是使用虚拟节点的主要原因。**
2.3.虚拟节点长什么样?
虚拟节点(Virtual Node)是一个简单的JavaScript对象,用来描述真实DOM中的一个节点(元素)。它包括了节点的标签名、属性、子节点等信息。
{ tag: 'div', // 标签名
props: { // 属性
id: 'myDiv', className: 'container', style: { backgroundColor: 'blue', color: 'white' } }, children: \[ // 子节点
{ tag: 'h1', props: { className: 'title' }, children: \['Hello, World!'\] }, { tag: 'p', props: {}, children: \['This is a paragraph'\] } \] }
概括:一个描述节点的对象
3.从头开始走一遍
各种方法封装的太杂了
感觉有点乱,所以我们要从头开始理清
3.1.vue入口文件src/index.js
今天的主角登场了
3.2.vnode/index.js
export function renderMixin(Vue) { Vue.prototype._c = function () { //创建标签
return createElement(...arguments) } Vue.prototype._v = function (text) { //文本
return createText(text) } Vue.prototype._s = function (val) { return val == null?"":(typeof val ==='object')?JSON.stringify(val):val } Vue.prototype._render = function () { //render函数变成 vnode
let vm = this let render = vm.$options.render let vnode = render.call(this) // console.log(vnode)
return vnode } } //vnode只可以描述节点 //创建元素
function createElement(tag,data={},...children){ return vnode(tag,data,data.key,children) } //创建文本
function createText(text){ return vnode(undefined,undefined,undefined,undefined,text) } //创建vnode
function vnode(tag,data,key,children,text){ return { tag, data, key, children, text } }
我们先来分析创建节点部分
我们确定vnode的节点由哪几个属性组成就好,这里是标签名、属性、键值、子节点和文本内容
接着往下走
3.3.init.js
3.4.mountComponent()方法
mounetComponent()方法在lifecycle.js中定义
import { patch } from "./vnode/patch" export function mounetComponent(vm,el){ //源码
vm._updata(vm._render()) //(1)vm._render() 将 render函数变成vnode //(2)vm.updata()将vnode变成真实dom
} export function lifecycleMixin(Vue){ Vue.prototype._updata =function(vnode){ console.log(vnode) let vm = this //两个参数 () vm.$el = patch(vm.$el,vnode) } } //(1) render()函数 =\>vnode =\>真实dom
_render方法在上方有定义,
Vue.prototype._c = function () { //创建标签
return createElement(...arguments) } Vue.prototype._v = function (text) { //文本
return createText(text) } Vue.prototype._s = function (val) { return val == null?"":(typeof val ==='object')?JSON.stringify(val):val } Vue.prototype._render = function () { //render函数变成 vnode let vm = this let render = vm.$options.render let vnode = render.call(this) // console.log(vnode) return vnode }
于是,到此处,我们进入到最核心的两个方法了
3.5. _render()
em,这玩意有点复杂,另外开了一篇来说
Vue源码学习(六):(支线)渲染函数中with(),call()的使用以及一些思考
一句话概括,将render的作用域指定为Vue实例后运行
这里我们拿到了我们要的虚拟节点
vm._render()返回vnode
接着往下走
3.6._updata
3.7.来看patch方法
export function patch(oldVnode,vnode){ console.log(oldVnode,vnode) //(1) 创建新DOM
let el = createEl(vnode) console.log(el) //(2) 替换 1) 获取父节点 2)插入 3)删除
let parentEL = oldVnode.parentNode parentEL.insertBefore(el,oldVnode.nextsibling) parentEL.removeChild(oldVnode) return el } function createEl(vnode){ //vnode 拆解
let {tag,data,key,children,text} = vnode //判断标签是否为字符串 0:创建标签元素,递归处理子节点 1:文本节点
if(typeof tag === 'string'){ vnode.el = document.createElement(tag) if(children.length \>0){ children.forEach(child =\> { //递归
vnode.el.appendChild(createEl(child)) }); } }else{ vnode.el = document.createTextNode(text) } return vnode.el }
emm,node节点的知识点忘了,赶紧复习一下
删节点操作
// 获取旧节点的父节点
let parentEL = oldVnode.parentNode; // 将新节点插入到旧节点的下一个兄弟节点之前
parentEL.insertBefore(el, oldVnode.nextsibling); // 从父节点中移除旧节点
parentEL.removeChild(oldVnode);
至此,我们的真实dom渲染完成了
4.最终效果
来看看网页
index.html对应内容
网页
由原先的
变成了
{{}}模板字符串内容成功渲染
5.思考
问:{{msg}}是在哪一步变成hello的?
上下文,
<div id="app" style="display: block;color: #000">Hello{{msg}}<h2>张三</h2></div>
<script src="dist/vue.js"></script>
<script>
//umd Vue
// console.log(Vue)
//响应式 Vue
let vm = new Vue({
el: '#app', //编译模板
// data: {
// },
data() {
// console.log(this)
return {
msg: 'hello',
a: {
b: 99
},
list: [1, 2, 3],
arr: [{
a: 1
}]
}
}
})
</script>
提示:网页调试结果
评论区回答一波