Vue源码学习(五):渲染第四步,生成虚拟dom并将其转换为真实dom

vue,源码,学习,templete,渲染,第四步,生成,虚拟,dom,转换,真实 · 浏览次数 : 129

小编点评

**3.3.init.js** * `vm._updata` 用于缓存 vnode 的属性。 * `vm.$el` 用于存储被创建的 DOM 元素的节点。 * `patch` 方法用于根据新的 vnode 创建新的 DOM 元素并将其插入现有元素的下一个兄弟节点。 **3.4.mountComponent()方法** * `mounetComponent()` 方法实例化 Vue 实例并设置它的模板。 * 它将 `_render()` 方法注册到实例上。 **3.5._render()方法** * `_render()` 方法将 vnode 渲染到 DOM 中。 * 它创建一个新的 DOM 元素,并将其设置到 `el` 属性中。 * 如果 vnode 是字符串,它创建一个标签元素并添加它到 DOM 中。 * 如果 vnode 是文本,它创建一个文本元素并添加它到 DOM 中。 **3.6._updata方法** * `_updata()` 方法用于更新 vnode 的属性。 * 它使用 `patch` 方法创建一个新的 DOM 元素并将其插入现有元素的下一个兄弟节点。 * 从父节点中移除旧节点。 **3.7.patch方法** * `patch()` 方法用于创建新的 DOM 元素并将其插入现有元素的 DOM 中。 * 它使用三个参数来实现插入: * `oldVnode`:旧 vnode。 * `vnode`:新的 vnode。 * `parentEL`:旧 vnode 的父节点的 DOM 元素。 * 它首先创建一个新的 DOM 元素,并将其设置为 `el` 属性的值。 * 然后,它将旧 vnode 的子节点插入到新的 DOM 元素的下一个兄弟节点中。 * 最后,它删除了旧 vnode 从父节点中。

正文

好家伙,

 

前情提要:

在上一篇我们已经成功将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>

提示:网页调试结果

 评论区回答一波

 

与Vue源码学习(五):渲染第四步,生成虚拟dom并将其转换为真实dom相似的内容:

Vue源码学习(五):渲染第四步,生成虚拟dom并将其转换为真实dom

好家伙, 前情提要: 在上一篇我们已经成功将ast语法树转换为渲染函数 现在我们继续 1.项目目录 代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍, 胜过我解释给你听一万遍 新增文件:vnode/index.js vnode/p

Vue源码学习(二):渲染第一步,模板解析

好家伙, 1.去哪了 在正式内容之前,我们来思考一个问题, 当我们使用vue开发页面时,中的内容是如何变成我们网页中的内容的? 它会经历四步: 解析模板:Vue会解析中的内容,识别出其中的指令、插值表达式({{}}),以及其他元素和属性。

Vue源码学习(三):渲染第二步,创建ast语法树

好家伙,书接上回 在上一篇Vue源码学习(二):渲染第一步,模板解析中,我们完成了模板解析 现在我们继续,将模板解析的转换为ast语法树 1.前情提要 代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍, 胜过我

Vue源码学习(四):渲染第三步,将ast语法树转换为渲染函数

好家伙, Vue源码学习(三):渲染第二步,创建ast语法树, 在上一篇,我们已经成功将 我们的模板 转换为ast语法树 接下来我们继续进行操作 1.方法封装 由于代码太多,为了增加代码的可阅读性 我们先将代码进行封装 index.js import { generate } f

Vue源码学习(七):合并生命周期(混入Vue.Mixin)

好家伙, 1.使用场景 现在来,来想一下,作为一个使用Vue的开发者,假设现在我们要使用created(),我们会如何使用 1.1. .vue文件中使用 {{ message }}

Vue源码学习(十一):计算属性computed初步学习

好家伙, 1.Computed实现原理 if (opts.computed) { initComputed(vm,opts.computed); } function initComputed(vm, computed) { // 存放计算属性的watcher const watchers = vm

Vue源码学习(一):数据劫持(对象类型)

好家伙,了解一下Vue如何实现数据劫持 1.Vue中data的使用 首先,我得搞清楚这玩意的概念,我们先从vue的使用开始吧 想想看,我们平时是如何使用vue的data部分的? 无非是这两种情况 (你可千万不要带着惊讶的表情说"啊!原来有两种写法的吗") //函数写法 data() { return

Vue源码学习(六):(支线)渲染函数中with(),call()的使用以及一些思考

好家伙, 昨天,在学习vue源码的过程中,看到了这个玩意 嘶,看不太懂,研究一下 1.上下文 这段出现vue模板编译的虚拟node部分 export function renderMixin(Vue) { Vue.prototype._c = function () { //创建标签 return

Vue源码学习(十):关于dep和watcher使用的一些思考

好家伙, 前面想了好久,都没想明白为什么要dep和watcher打配合才能实现数据-视图同步 为什么要多一个依赖管理这样的东西 给每个数据绑个watcher(xxfunction),然后,数据变了,调set,然后调xxfunction,不就行了, 然后今天突然想明白了,不是为什么要这么干,而是必须这

Vue源码学习(九):响应式前置:实现对象的依赖收集(dep和watcher)

好家伙,这是目前为止最绕的一章,也是十分抽象的一章 由于实在太过抽象,我只能用一个不那么抽象的实例去说服我自己 完整代码已开源https://github.com/Fattiger4399/analytic-vue.git 1.我们要做什么? 来看这个例子, index.html setTimeou