VUE2 学习笔记6 vue数据监测原理

Vue监测数据改变的原理

**数据:**指的是存在data中的数据。

如果我们修改了data中存储的数据,Vue会监测到这种修改,并且把修改反应到页面上。

Vue的这种监测是通过Vue上默认的监视实现的。与watch不同,watch是Vue提供给开发者使用的方法。但不管是默认监视,还是watch,背后的原理都类似。

了解Vue的监视原理是很重要的,如果不知道,当自己写的某种数据修改不能被Vue识别到,就很难去分析成因。

一种Vue无法监视到数据修改的情况:

如果用访问下标的方法替换数组中某个对象元素,把整个元素都替换成新的,虽然后台数据已经修改了,但Vue无法检测到这种修改,因此页面的数据也无法改变。

而且,有时候,页面上的数据没有改变,但通过控制台输出修改后的数据,再打开开发者工具,开发者工具中的数据会发生改变,但页面上的内容仍然不变,这种错乱的现象令人迷惑。

html 复制代码
<body>
    <div id="root">
        <button @click="changeData">change</button>
        <ul>
            <li v-for="(data,index) in listData" :key="data.id">
                {{ data.name }}:{{data.age}} -- {{index}}
               
            </li>
        </ul>
    </div>

    <style>
        .styleBackgroundColor{
            background-color: aqua;
        }
        .styleContent{
            width:300px;
            height: 200px;
        }
        .styleBorder{
            border: 2px black solid;
        }
    </style>

    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        let vm = new Vue({
            el: '#root',
            data() {
                return {
                   listData:[
                    {name:'catcat',age:5,id:'001'},
                    {name:'dogcat',age:8,id:'002'},
                    {name:'QAQ',age:15,id:'003'},
                    {name:'QAQTAT',age:15,id:'004'},
                    {name:'123',age:15,id:'005'},
                   ],
                }
            },
            methods:{
                changeData(){
                    this.listData[0] = {name:'test',age:'changed',id:'001'};
                }
            }
        })
    </script>
</body>

在这个例子中,页面上显示原始数组信息,当点击change按钮时,修改数组中的第一条数据,但这时令人迷惑的情况发生了,页面上的数据并不会发生改变,但在控制台打印listData数组,后台的数据已经被更新,这种更新并没能传递到页面上:

这与Vue的监测机制相关。

Vue如何监测对象数据的改变

Vue会对data中的数据加工,生成_data,_data中为每个属性都添加了set和get,当data中数据改变时,会触发数据对应的set函数,set函数的执行代表有数据发生变化,会引起Vue模版的重新解析,Vue模版会使用新的数值解析指令,最终在页面上显示出新的数据。

要对数据进行监测,要靠Observer函数进行实现,Observer函数是一个构造函数,能够创建一个监视的实例对象。

在Observer构造函数内部,首先会汇总对象中所有属性,并形成数组。然后会遍历这个数组,为Observer实例对象添加所有属性,对每个属性设置get和set,在get函数内部,返回被监视data的属性值,在set函数内部,把被监视data的属性值修改掉。

简单的模拟代码如下(实际vue底层代码要更复杂和完善一些):

javascript 复制代码
    <script>
        let data = {
            name: 'catcat',
            age: 10,
        }

        function Observer(obj){
            //获取数据中的所有属性
            let keys = Object.keys(obj);

            for(const k of keys){
                //把所有属性添加到Observer实例上,并且增加set和get方法
                Object.defineProperty( this, k , {
                    get(){
                        return obj[k]; 
                    },
                    set(value){
                        console.log('发现修改,重新进行模版解析等行为');
                        obj[k] = value;
                    }
                } )
            }
        }
        //创建监听实例
        let obs = new Observer(data);

        
        const vm = {};
        vm._data = data = obs;
    </script>

对于这段代码,当前对data数据只考虑了一层数值,当data数值是对象时,当前的代码对对象并不生效。但Vue会对对象及对象内嵌套的对象进行递归,为每层的数据都添加get和set,直到没有层级结构。Vue对藏在数组中的对象,也都为对象的属性赋予了get和set。

对于下列这种数据:

javascript 复制代码
    <script>
        const vm = new Vue({
            data:{
                a:{
                    f:100,
                    b:{
                        g:'123123',
                        c:{
                            d:1,
                            e:2,
                        }
                    }
                },
                list:[
                    {
                        name:'a',age:1,
                    },
                    {
                        name:'n',age:2,
                    }
                    
                ],
            },
            
        })
    </script>

可以看到a以及内层的bf,b内层的cg,c内层的de,上面都有get和set。

对于list,list内部的对象属性也有get和set。

Vue.set

对于data中的数据,在vue实例创建阶段配置好的数据,是响应式的,但对于后加入的数据,由于没有set和get,并不是响应式的。

比如:

html 复制代码
<body>
    <div id="id">
        <h2>{{a.cat}}</h2>
    </div>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const vm = new Vue({
            el:'#id',
            data:{
                a:{
                    f:100,
                    b:{
                        g:'123123',
                        c:{
                            d:1,
                            e:2,
                        }
                    }
                }
            },
            
        })
    </script>
</body>

对于这段代码,页面上显示了data中的a.cat,如果后续给a添加了cat属性,页面上还是无法显示出cat,因为后添加的cat并不是响应式的,页面无法监测到后台新加了数据。

如果希望后添加的数据也有响应式,需要使用Vue上提供的一个api。

Vue.set(target 往谁的身上追加属性,key 要追加的属性名 字符串格式,value 追加的属性值)

除了Vue.set,Vue实例对象上也有一个相同功能的方法,vm.$set。语法是一模一样的。

要找到追加属性的位置,可以不用vm._data.a,用vm.a也可以。

html 复制代码
<body>
    <div id="id">
        <button @click="add">addData</button>
        <h2>{{a.cat}}</h2>
    </div>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const vm = new Vue({
            el:'#id',
            data:{
                a:{
                    f:100,
                }
            },
            methods:{
                add(){
                    Vue.set(this.a,'cat','ttt');
                    //this.$set(this.a,'cat','ttt');
                }
            }
            
        })
    </script>
</body>

不过,set Api也有一些缺点。set Api只能给data中某一个对象追加属性,不能够给data追加属性。vm和vm._data不能作为target。

Vue如何监测数组数据的改变:

对于数组数据,vue不会为数组内部的元素添加set和get。Vue对数组其实也有监视机制,只不过不是通过set和get进行,因此通过下标修改数组元素,Vue不会监听到这种修改。

html 复制代码
<body>
    <div id="id">
        <button @click="change">change</button>
        <h2 v-for="data in list">{{data}}</h2>
    </div>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const vm = new Vue({
            el:'#id',
            data:{
                list:[
                    '123',
                    {name:'a',age:2},
                    100
                ]
            },
            methods:{
                change(){
                    console.log('change');
                    this.list[0] = 'aa';
                }
            }
            
        })
    </script>
</body>

页面上有change按钮,当点击按钮的时候修改数组第一条数据,但点击按钮之后,页面上的数据并不会发生变化。

当对数组数据使用push pop shift unshift splice sort reverse这些能够修改数组本身的方法时,Vue才能监测到

javascript 复制代码
            methods:{
                change(){
                    console.log('change');
                    this.list.shift();
                    this.list.unshift('aa');
                }
            }

对于不修改数组本身的方法,比如filter,只使用filter也是无法被vue监测到的,需要把filter的返回值赋值给整个数组,vue才能监测到这种修改。

因此,回到最开始的问题,为什么把数组第一条数据用下标修改之后,页面上的数据无法进行更新,是因为vue无法监测到对下标访问的修改。

**vue是如何监测到对数组使用了修改数组本身的方法:**vue使用了包装的手段,也就是说,在vue中调用数组的push等方法,这时使用的并不是Array原型对象上的push,而是vue自己写的push。在vue的push中,一开始还是调用了array原型上的push,然后,vue会重新解析模版,生成新的虚拟DOM,diff算法,然后更新页面。

除了使用push pop等方法修改数组,能实现响应式,用Vue.set vm.$set Api也可以响应式修改数组

**数据劫持:**对于一个数据,把数据及数据内层所有子变量都赋予一个get和set,把这种动作行为叫做劫持。当有人修改或读取数值时,都会被劫持,进入set或get。

相关推荐
试图让你心动3 小时前
原生input添加删除图标类似vue里面移入显示删除[jquery]
前端·vue.js·jquery
chenchihwen3 小时前
大模型应用班-第2课 DeepSeek使用与提示词工程课程重点 学习ollama 安装 用deepseek-r1:1.5b 分析PDF 内容
人工智能·学习
陈琦鹏4 小时前
轻松管理 WebSocket 连接!easy-websocket-client
前端·vue.js·websocket
超浪的晨4 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
小毛驴8505 小时前
创建 Vue 项目的 4 种主流方式
前端·javascript·vue.js
使二颗心免于哀伤5 小时前
《设计模式之禅》笔记摘录 - 10.装饰模式
笔记·设计模式
悠哉悠哉愿意6 小时前
【电赛学习笔记】MaxiCAM 项目实践——与单片机的串口通信
笔记·python·单片机·嵌入式硬件·学习·视觉检测
快乐肚皮6 小时前
ZooKeeper学习专栏(五):Java客户端开发(原生API)详解
学习·zookeeper·java-zookeeper
慕y2746 小时前
Java学习第七十二部分——Zookeeper
java·学习·java-zookeeper