Vue学习 — Vue响应式原理

本文详细解读了Vue响应式系统的实现,介绍了Object.defineProperty的作用,数据劫持如何通过观察并监听数据变化来驱动视图更新。重点讲解了依赖收集和发布订阅模式在数据更新中的应用,以及双向绑定在简化开发中的关键作用。
摘要由CSDN通过智能技术生成

一、 Object.defineProperty

在学习vue响应式原理之前,必须搞懂 Object.defineProperty

Object.defineProperty(obj, prop, descriptor)

看到一篇写的十分不错的博客:理解Object.defineProperty方法

二、vue响应式更新

2.1响应式

所谓响应式,简单说就是用户更改数据(Data)时,视图可以自动刷新,页面UI能够响应数据变化。

Vue 最独特的特性之一,是其非侵入性的响应式系统。
									    —— 尤雨溪

看一个没有响应式的例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app"></div>
    <button onclick="flushMsg()">更新msg</button>
    <script>
        let app = document.getElementById('app');
        let msg = "123";
        app.innerHTML = `<p>msg: ${ msg }</p>`;
        function flushMsg(){
            console.log('点击了按钮,msg更新为12345');
            msg = "12345";
        }
    </script>
</body>

</html>

效果:

在这里插入图片描述

可以看到,我通过按钮点击事件改变了msg的值,但是页面是不会自动改变的,即没有实现响应式。那么怎么实现呢,很简单,将点击事件改为:

 function flushMsg(){
     console.log('点击了按钮,msg更新为12345');
     msg = "12345";
     app.innerHTML = `<p>msg: ${ msg }</p>`
 }

即我们改变了数据后,需要重新操作dom元素,更新数据,还是很简单的。可是当我们的项目变得复杂的时候,一个页面的数据会变得非常多,那么我们要给每一个变量都进行相应的处理,即数据改变,则调用dom改变视图。还好,vue帮我们完成了这步操作,我们只需要在data中声明数据(m),在视图中展示数据(v),其他的交给vue吧(vm)。所以我们也称vue为mvvm框架。

2.2 vue响应式

首先直接列出几个核心:

  • 数据劫持 / 数据代理 侦测数据的变化
  • 依赖收集 收集视图依赖了哪些数据
  • 发布订阅模式 数据变化时,自动“通知”需要更新的视图部分,并进行更新

2.2.1 数据劫持

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function render() {
            console.log('模拟视图渲染, 页面应该改变')
        }
        let data = {
            name: 'yancy',
            language: ['c', 'java', 'javascript']
        }
        observe(data)

        function observe(obj) {
            if (!obj || typeof obj !== 'object') {
                return
            }
            Object.keys(obj).forEach(key => {
                defineReactive(obj, key, obj[key])
            })

            function defineReactive(obj, key, value) {
                // 递归子属性
                observe(value)
                Object.defineProperty(obj, key, {
                    enumerable: true, //可枚举(可以遍历)
                    configurable: true, //可配置(比如可以删除)
                    get: function() {
                        console.log('触发get:', value) // 监听
                        return value
                    },
                    set: function(newVal) {
                        observe(newVal) //如果赋值是一个对象,也要递归子属性
                        if (newVal !== value) { //数据变化
                            console.log('触发set:', newVal) // 监听
                            value = newVal
                            render(); //数据改变 重新渲染视图
                        }
                    }
                })
            }
        }
    </script>
</body>

</html>

在这里插入图片描述

我们将储存着数据的对象data的所有属性使用Object.defineProperty进行监听,获取属性的值,触发get函数。更改属性的值,触发set函数,就实现了数据劫持,所以我们页面的数据都要在vue实例中的data属性中声明,因为vue只会劫持data对象中的数据。

2.2.2 依赖收集

上面我们已经能够劫持观察到数据的变化了,那么下一步便是将数据的变化渲染到页面上,2.2.1我们使用render()函数一笔带过了。
想要把数据的改变渲染到页面上,我们应该要知道页面中什么地方使用到了数据,然后重新渲染。
我们只有通过收集依赖才能知道哪些地方依赖我的数据,以及数据更新时派发更新。那依赖收集是如何实现的?其中的核心思想就是“事件发布订阅模式”。

2.2.3 发布订阅

当数据变化触发依赖,dep(发布者)通知所有的Watcher(观察者)实例更新视图。
在这里插入图片描述
至于vue数据更新后的具体渲染过程,那么就涉及了虚拟DOM和diff算法,得再写一篇博客学习一下。

三、双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="app">
        <input type="text" name="" id="input">
        <p id="p"></p>
    </div>
    <script>
        let input = document.getElementById('input');
        let p = document.getElementById('p');
        var obj = {}
        Object.defineProperty(obj,'msg',{
            set: function(newVal){
                input.value = newVal;
                p.innerHTML = newVal;
            }
        })
        input.addEventListener('keyup',function(e){
            obj.msg = e.target.value;
        })
    </script>
</body>
</html>

在这里插入图片描述

可以看到,关键是代码:

 Object.defineProperty(obj,'msg',{
     set: function(newVal){
         input.value = newVal;
         p.innerHTML = newVal;
     }
 })

作用设置obj上的msg属性被写入时触发的set函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小绵杨Yancy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值