Vue2——检测数据改变的原理

引例,更新问题

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
        <button @click="updataZs">更新张三信息</button>
        <ul>
            <li v-for="(p,index) in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
        </ul>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          name: "yang",
          persons: [
            { id: "001", name: "张三", age: 18 },
            { id: "002", name: "李四", age: 21 },
            { id: "003", name: "王五", age: 19 },
          ]
        },
        methods: {
            updataZs(){
                // this.persons[0].name="yang"//奏效
                // this.persons[0].age="18"//奏效
                this.persons[0] = {id: "001", name: "yang", age: 18}
            }
        },
      });
    </script>
  </body>
</html>

点击更新按钮之后,vm中的数据发生了改变但是页面数据没有改变。
在这里插入图片描述

vue监测数据改变原理

我们在页面模板中使用data中的数据时,data中的数据会在页面显示,并且当data中的数据发生改变时页面中相应的数据也发生改变。
那么vue是如何监视data中的数据改变的呢?
——通过设置响应式函数,当数据一发生改变,就调用响应式函数,响应式函数会引起模板的重新解析,将改变后的数据显示到页面上,那么响应式函数是如何产生的呢,通过data的数据加工。

data加工

我们知道 vm对象和data之间形成数据代理,即vm对象(vue对象)代理data对象中属性的操作。
代理过程如下:
在这里插入图片描述实际上源码中的data和vm对象中的_data不是完全一样的,vm._data中有每个属性的getter和setter方法,而data中只有属性值。这是因为源码data在存储在vm中之前先进行了数据加工,添加了getter和setter方法,之后在存储在vm中。

而添加的getter和setter方法是响应式的也就是说,当data中的数据一发生改变,就会调用响应式的setter方法,响应式setter方法会引起模板的重新解析。
在这里插入图片描述

vue监测数据改变原理的简单实现

vue通过响应式setter方法监测数据改变,这里进行简单的解析实现。

vue检测对象改变的简单实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <!-- <script type="text/javascript" src="../js/vue.js"></script> -->
  </head>
  <body>
    <script type="text/javascript">
      let data ={
        name:"yang",
        age:18
      }
      // 创建检测实例的对象,检测数据的变化
      const obs = new Observer(data)
      // 准备vm实例对象
      const vm = {}
      vm._data = data = obs


      function Observer(obj){
        // 获取对象中的所有属性
        const keys = Object.keys(obj);
        // 遍历
        keys.forEach((k)=>{
          Object.defineProperty(this,k,{
            get(){
              return obj[k]
            },
            set(val){
              console.log(`${k}被修改了,重新解析模板`)
              obj[k] = val
            }
          })
        })

      }
     
    </script>
  </body>
</html>

在这里插入图片描述

问题:vue对于后添加的属性不会设置setter和getter

eg:

_<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
       <h2>姓名:{{name}}</h2>
       <h2>年龄:{{age}}</h2>
       <!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
       <h2>朋友:{{friends.id}}-{{friends.name}}-{{friends.age}}-{{friends.sex}}</h2>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          name: "yang",
          age:"18",
          friends: { id: "001", name: "张三", age: 18 },
        },
      });
    </script>
  </body>
</html>

在这里插入图片描述

解决
  • 法一:使用vue提供的api
    Vue.set(要添加的对象,要添加的属性,要添加的属性值)
    在这里插入图片描述
    上述命令也可以使用vm代理数据data即:
    Vue.set(vm.friends,'sex','女')

  • 法二:使用vm提供的api
    vm.$set(要添加的对象,要添加的属性,要添加的属性值)
    在这里插入图片描述
    上述命令也可以使用vm代理数据data即:
    vm.$set(vm.friends,'sex','女')
    代码实现:

_<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
       <h2>姓名:{{name}}</h2>
       <h2>年龄:{{age}}</h2>
       <!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
       <h2>朋友:{{friends.id}}-{{friends.name}}-{{friends.age}}-{{friends.sex}}</h2>
       <button @click="addSex">点击添加朋友性别</button>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          name: "yang",
          age:"18",
          friends: { id: "001", name: "张三", age: 18 },
        },
        methods: {
            addSex(){
            //    Vue.set(this.friends,'sex','男')
            this.$set(this.friends,'sex','男')
            }
        },
      });
    </script>
  </body>
</html>

注意: 该方法只能给data中的对象添加属性,而不能给data添加属性。

vue检测数组改变的简单实现

问题: data中数组中的数据的更改无法使页面重新解析

data中数组中的数据是没有设置gettersetter方法的,所以如果data中数组数组中的数据发生改变是无法重新解析界面的。
eg:

_<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
      <h2>姓名:{{name}}</h2>
      <h2>年龄:{{age}}</h2>
      <!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
      <h2>爱好:</h2>
      <ul>
        <li v-for="(hobby,index) in hobbies" :key="index">
          {{hobby}}
        </li>
      </ul>
      <h2>朋友:</h2>
      <ul>
        <li v-for="(friend,index) in friends" :key="index">
          {{friend.id}}-{{friend.name}}-{{friend.age}}
        </li>
      </ul>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          name: "yang",
          age: "18",
          hobbies:["吃饭","睡觉","打豆豆"],
          friends: [
            { id: "001", name: "张三", age: 18 },
            { id: "002", name: "李四", age: 19 },
            { id: "003", name: "王五", age: 20 },
            { id: "004", name: "赵六", age: 21 },
          ],
        },
        methods: {},
      });
    </script>
  </body>
</html>

在这里插入图片描述
在这里插入图片描述

解决1:

虽然不能通过直接更改数组元素使得页面重新解析加载,但是vue将数组的变更方法进行了包裹,所以只要我们使用这些变更方法来修改数组,修改完成之后就会触发视图更新。
变更方法包括:.push(). pop().shift().unshift(). splice(). sort(). reverse()

EG:
在这里插入图片描述
vue将包裹数组的变更方法的原理:
在这里插入图片描述

解决2:

使用Vue.set(要添加的对象,要添加的属性,要添加的属性值)或者vm.$set(要添加的对象,要添加的属性,要添加的属性值)
在这里插入图片描述

这个问题也是文章开始的那个问题

所以可以解决:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
        <button @click="updataZs">更新张三信息</button>
        <ul>
            <li v-for="(p,index) in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
        </ul>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          name: "yang",
          persons: [
            { id: "001", name: "张三", age: 18 },
            { id: "002", name: "李四", age: 21 },
            { id: "003", name: "王五", age: 19 },
          ]
        },
        methods: {
            updataZs(){
                // this.persons[0].name="yang"//奏效
                // this.persons[0].age="18"//奏效
                // this.persons[0] = {id: "001", name: "yang", age: 18}
                this.persons.splice(0,1,{id: "001", name: "yang", age: 18})
            }
        },
      });
    </script>
  </body>
</html>

在这里插入图片描述

总结

代码总结演示

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <div id="root">
      <h1>学生信息</h1>
      <button @click="student.age++">点击按钮年龄+1</button>
      <button @click="addSex">添加性别,默认值是男</button>
      <button @click="updateSex">修改性别</button>
      <button @click="addFriend">在列表首部添加朋友</button>
      <br>
      <button @click="updateFirstFriendName">修改列表中第一个朋友的名字为yang</button>
      <button @click="addHobby">添加一个爱好</button>
      <button @click="updateHobby">修改第一个爱好为"开车"</button>
      <button @click="removeCar">过滤掉爱好"开车"</button>
      <h2>姓名:{{student.name}}</h2>
      <h2>年龄:{{student.age}}</h2>
      <h2 v-if="student.sex">性别:{{student.sex}}</h2>
      <!-- 如果一个对象的某属性未定义,返回undefined,vue对于undefined 类型的数据不显示,也不报错 -->
      <h2>爱好:</h2>
      <ul>
        <li v-for="(hobby,index) in student.hobbies" :key="index">{{hobby}}</li>
      </ul>
      <h2>朋友:</h2>
      <ul>
        <li v-for="friend in student.friends" :key="friend.id">
          {{friend.id}}-{{friend.name}}-{{friend.age}}
        </li>
      </ul>
    </div>
    <script type="text/javascript">
      Vue.config.productionTip = false;

      const vm = new Vue({
        el: "#root",
        data: {
          student: {
            name: "yang",
            age: "18",
            hobbies: ["吃饭", "睡觉", "打豆豆"],
            friends: [
              { id: "001", name: "张三", age: 18 },
              { id: "002", name: "李四", age: 19 },
              { id: "003", name: "王五", age: 20 },
              { id: "004", name: "赵六", age: 21 },
            ],
          },
        },
        methods: {
          addSex() {
            Vue.set(this.student, "sex", "男");
          },
          updateSex() {
            if(!this.student.sex) return;
            if (this.student.sex === "男") {
              this.student.sex = "女";
            }else{
              this.student.sex = "男";
            }
          },
          addFriend(){
            // 修改数组,但是使用unshift修改数据页面可以更新
            this.student.friends.unshift({ id: "005", name: "老七", age: 21 })
            // 并且给新加入的对象的属性设置getter和setter方法
          },
          updateFirstFriendName(){
            // 由于vue为新加入的对象的属性设置getter和setter方法.所以这里可以直接为属性赋值
            // (没有getter和setter方法的是数组中的元素,但是数组中的元素的属性是有getter和setter方法的)
            this.student.friends[0].name = "yang"

          },
          addHobby(){
            this.student.hobbies.push("学习")
          },
          updateHobby(){
            Vue.set(this.student.hobbies, 0, "开车");
            // this.student.hobbies.splice(0,1,"开车")
          },
          removeCar(){
            this.student.hobbies = this.student.hobbies.filter((h)=>{
              return h!=="开车"
            })
          }
        },
      });
    </script>
  </body>
</html>

在这里插入图片描述

文字总结

  1. vue会监视data中所有层次的数据。

  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据(即在data中定义好要检测的数据)。
    (1). 对象中后追加的属性,Vue默认不做响应式处理
    (2). 如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target.propertyName/indexvalue)
    vm.$set(target.propertyName/index,value)

  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1) 调用原生对应的方法对数组进行更新。
    (2) 重新解析模板,进而更新页面。

  4. 在Vue修改数组中的某个元素一定要用如下方法:
    (1) 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    (这些操作都修改了原数组)
    (2) Vue.set()或 vm.$set()
    对于不修改原数组的操作直接使用data中已经定义好的数据接收即可。

  5. 特别注意:Vue.set()和 vm.$set()不能给vmvm的根数据对象(即不能修改data的属性)添加属性!!!
    即不能直接向data中添加属性。

数据劫持

实际上数据劫持就是说为data数据添加响应式getter和setter的过程。添加完响应式getter和setter之后,只要数据一改变,就会被setter"劫持",进而重新解析模板。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue2的双向数据绑定原理是通过数据劫持结合发布订阅模式来实现的。具体来说,Vue在初始化阶段,通过Object.defineProperty()方法对每个属性进行劫持,定义了其getter和setter。当属性被访问时,getter会进行依赖的收集,将相关的依赖(比如视图模板中使用到的该属性)进行收集。当属性发生变化时,setter会通知订阅者,触发相应的监听回调来更新视图。 在具体实现过程中,Vue数据对象进行递归遍历,对对象的每个属性都进行了数据劫持,使其具有响应式特性。当数据发生变化时,Vue会遍历相关的依赖(也就是之前收集到的依赖),触发相应的更新操作,更新视图。 总的来说,Vue的双向数据绑定原理可以概括为以下几个步骤: 1. 在初始化阶段,通过Object.defineProperty()方法对每个属性进行劫持,定义其getter和setter。 2. 当属性被访问时,getter进行依赖的收集,将相关的依赖进行收集。 3. 当属性发生变化时,setter通知订阅者,触发相应的监听回调来更新视图。 这样就实现了数据和视图的双向绑定,数据的变化会触发视图的更新,视图的交互变化也会更新数据模型。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue2双向数据绑定基本原理](https://blog.csdn.net/weixin_40712618/article/details/126232040)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [【Vue的双向数据绑定原理】](https://blog.csdn.net/Lnbd_/article/details/122813853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值