一篇文章带你吃透VUE响应式原理

本文详细介绍了Vue.js的响应式原理,从MVVM模式出发,通过创建微型的miniVue实例,逐步剖析模板编译和响应式系统的实现。包括数据劫持、依赖收集、watcher的创建与更新,以及如何在编译过程中结合响应式,实现数据变化时视图的自动更新。文章通过实例代码深入浅出地展示了Vue响应式的工作流程。

本篇响应式原理介绍比较长,全文大概1w+字。虽然内容繁杂,但阅读过后,绝对会让你对vue的响应式有更加深刻的理解。

分块阅读,效果更佳。(建议读者有一定vue使用经验和基础再食用)

首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重。

响应式原理图

在这里插入图片描述

一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程。全文分为两大块,首先介绍实例模板的编译过程,然后详细介绍响应式,这里先介绍编译是为了给介绍响应式奠定基础。

编译

我们把我们创建的这个微型响应系统命名为miniVue,我们按照平常使用Vue的模式,首先创建一个miniVue的实例。

<scirpt>
    const vm = new miniVue({
   
   
        el: '#app',
        data: {
   
   
            obj: {
   
   
                name: "miniVue",
                auth: 'xxx'
            },
            msg: "this is miniVue",
            htmlStr: "<h3>this is htmlStr</h3>"
        },
        methods: {
   
   
            handleClick() {
   
   
                console.log(this);
            }
        }
    });
</scirpt>

我们根据这个实例,我们可以创建出miniVue的类,这个类中我们肯定要保存该实例所绑定的DOM以及数据对象data。然后我们要开始解析模板,即解析我们所绑定的DOM

class miniVue {
   
   
    constructor(options) {
   
   
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
   
   
        // 解析模板 to Compile
    }
}

这里我们来创建一个compile类来进行解析模板的操作

创建compile类

Compile类是用来解析模板的,所以肯定要传入要解析的DOM。拿到DOM后直接操作这个DOM会导致页面频繁的回流和重绘,所以我们把这个DOM放到一个文档碎片中,然后操作这个文档碎片。操作这个文档碎片的过程中我们需要获取到数据对象data中的属性来填充一些节点的内容,所以我们还需要传入实例对象。最后将操作好的文档碎片追加到原本的DOM上。

class Compile {
   
   
    constructor(el, vm) {
   
   
        // 判断的原因是因为传入的el有可能是DOM,也有可能是选择器例如‘#app’
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm
        // 新建文档碎片存储DOM
        const fragment = this.toFragment(this.el)
        
        // 操作文档碎片 to handle fragment
        
        // 将操作好的文档碎片追加到原本的DOM上面
        this.el.appendChild(fragment)
    }
    
    // 判断是否为元素节点
    isElementNode(node) {
   
   
        return node.nodeType === 1
    }
    
    // dom碎片化
    toFragment(el) {
   
   
        const f = document.createDocumentFragment()
        // 递归存入
        recursion(el, f)
        
        function recursion(el, father) {
   
   
            el.children.forEach((child) => {
   
   
                if(child.children.length > 0) {
   
   
                    recursion(child.children, child)
                }
                father.appendChild(child)
            })
        }
    }
}// 上面的miniVue实例相应的改为
class miniVue {
   
   
    constructor(options) {
   
   
        this.$el = options.el
        this.$data = options.data
        this.$options = options
    }
    if(this.$el) {
   
   
        // 解析模板 to Compile
        new Compile(this.$el, this) // 这里的this就是miniVue实例
    }
}

操作fragment

操作保存好的文档碎片,我们可以专门定义一个函数,然后把文档碎片通过参数传入进来。

操作文档碎片我们又可以分为两步。因为针对文本节点元素节点,我们需要进行不同的操作,所以我们在遍历所有节点后的第一步应该先判断它是元素节点还是文本节点

handleFragment(fragment) {
   
   
    // 获取文档碎片的子节点
    const childNodes =  fragment.childNodes
    // 遍历所有子节点
    [...childNodes].forEach((child) => {
   
   
        if(this.isElementNode(child)) {
   
   
            // 元素节点
            this.compileElement(child)
        } else {
   
   
            // 文本节点
            this.compileText(child)
        }
        
        // 递归遍历
        if(child.childNodes && child.childNodes.length) {
   
   
            handleFragment(child)
        }
    })
}// 同样的我们需要完善一下compile的构造函数
constructor(el, vm) {
   
   
    this.el = this.isElementNode
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值