浅析Vue数据监测原理

我们讨论的问题是:我们修改了this.data中的数据,vue往往能监测到这个数据发生变化,并且渲染在页面上,vue是怎么监测数据的变化的呢?

阅读本文需要了解一下数据代理的知识点

vue数据劫持 监测对象

我们先来思考一个问题:这里有一个对象obj,我们希望当它其中的内容发生变化的时候,控制台就输出它发生了变化

这里主要使用的是数据劫持,数据劫持我们常用的api就是Object.defineProperty

最简单的想法:劫持一个name属性,然后在setter中输出一句话,那好了,设置了name属性(name属性变化)就调用setter,就会显示这段话了

但是我们发现有什么问题呢?我们发现由于缺少getter,我们根本无法查看obj.name的值

好,那么我们就加上getter,大家最最常见的想法就是getter直接返回obj.name,这样不就可以获得其当前的name属性值了嘛?但我们发现查看后直接报错了,原因是内存溢出

为什么会内存溢出呢?我们用图解释一下,当你查看name属性的时候就调用了getter,但是return obj.name的时候,也查看了name属性,就形成了循环调用,自然就内存溢出了

所以在getter/setter中我们不能直接调用当前劫持对象的属性,这会造成内存溢出

但是我们知道,如果想要监测数据变化就必须使用数据劫持,所以我们得用到一些技巧,来确保数据劫持正常运行

观察下面的代码:我们的思路就是创建一个副本,通过这个副本操作data的读/写,作为Object.defineProperty中操作的对象,这个对象有什么要求?它必须拥有data中所有的属性名,有了这些属性名之后以便添加到这个副本上,我们为这个副本上的属性分别创建getter/setter,但getter返回的是data中的数据,setter修改值的时候,修改的是data中的数据,接下来我们只对这个副本进行操作即可。

当这个副本被访问的时候,直接返回data中当前的数据(为啥现在可以返回了?因为我们Object.defineProperty操作的对象是这个副本,读取的是另一个对象data中的数据,当然不会造成循环引用),这样,我们就可以进行我们之前无法进行了操作:在getter/setter中 访问/修改 data中的数据

<script>
  let data = {
    name: 'zhouzhou',
    age: 18
  }
  //数据劫持

  //使用构造函数创建一个监视对象的实例
  let obs = new Observer(data)
  console.log(obs);

  //构造函数
  function Observer(obj) {
    //获取obj中的所有属性名
    const keys = Object.keys(obj)
    keys.forEach(item => {
      //最大的注意点:我们这里向this添加属性,而不是直接向data中添加属性
      //构造函数的this是指向它本身的,所以这里this是指向Observer这个构造函数创建的实例
      //其中item代表data中的所有属性名
        Object.defineProperty(this, item, {
          get() {
            //这里是返回对象的值
            return obj[item]
          },
          set(v) {
            obj[item] = v
          }
        })
      }
    )
  }
</script>

其中我们使用obj[item]这种形式来返回变量的值,一般我们获取变量值的方式是使用obj.name则会中方式,这里使用obj[item]这种方式的原因是,.运算符后面无法跟变量名

编写完代码之后,回到控制台输出一下obs对象

回到我们的标题,vue是怎么监测对象变化的?为何我们修改data中的数据,vue可以监测到并且返回到页面上?其中基本原理就是数据劫持

我们来输出一个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, user-scalable=no,maximum-scale=1.0,minimum-scale=1.0">
  <title>Title</title>
</head>
<body>
<script src="../js/vue.js"></script>

<div id="app">

</div>

<script>
  const vm = new Vue({
    el: '#app',
    data: {

      msg : {
        name: 'zhouzhou',
        age: 18
      }

    }
  })
  console.log(vm);
</script>
</body>
</html>

可以看到,Vue实例上有个_data,这也是vue进行数据劫持之后的副本

只不过,vue的getter/setter函数都变成了reactiveGetter/reactiveSetter,其中进行了监测到数据改变之后,数据渲染到页面上等操作,当然vue的代码比我们刚才演示的小demo复杂的多,我们还有很多问题没有解决,比如:如果我们data中包含有对象呢?我们的demo无法监测到其中的变化,但是vue可以,具体怎么搞的我也不太明白,日后阅读vue源码之后再来分享,我们这里使用的数据劫持api是Object.defineProperty,这是vue2使用的数据劫持api,而vue3换装了proxy,这有助于vue3监测数组中的变化

Vue.set()

先贴一张文档介绍

观察下面这个例子,我们message中没有sex属性,那么我们该怎么加上sex并且让其显示呢?

页面中没有输出message.sex因为vue默认undefined不显示

对象中没有的属性sex,为undefined,但是如果没有这个对象,vue就会报错

我们在控制台尝试向这个对象中添加数据

可以看到我们自己添加的sex属性没有getter/setter方法,因为没有其方法,所以页面上无法显示

这时候,不改变data中的数据代码的情况下,我们可以使用Vue.set方法(在vue实例对象身上名叫$set)

注意:set方法,不能往data中直接创建对象,也就是说,不能创建message同级的对象

使用示例:

<!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, user-scalable=no,maximum-scale=1.0,minimum-scale=1.0">
  <title>Title</title>
</head>
<body>
<script src="../js/vue.js"></script>

<div id="app">
  <div>
    <button @Click="showSex()">点击添加sex属性</button>
    {{message.name}}+{{message.age}}+{{message.sex}}
  </div>
</div>

<script>
  const vm = new Vue({
    el: '#app',
    data: {
      message: {
        name: 'zhouzhou',
        age: 21
      }
    },
    methods: {
      showSex() {
        this.$set(this.message, 'sex', '男')
      }
    }
  })
</script>
</body>
</html>

vue监测数组

我们来看一个例子

我们点击添加爱好页面中的数据是否会更新呢?

我们发现,页面上的爱好依旧是三个,但是vue实例上,却有四个爱好

那说好的响应式呢?咋没用了?我们阅读vue2的官方文档,我们可以发现

如果要支持响应式,那么操作数组的话请使用以下方法,而不是直接使用[]来操作数组

收集表单信息

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>收集表单数据</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 
			收集表单数据:
					若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
					若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
					若:<input type="checkbox"/>
							1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
							2.配置input的value属性:
									(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
									(2)v-model的初始值是数组,那么收集的的就是value组成的数组
					备注:v-model的三个修饰符:
									lazy:失去焦点再收集数据
									number:输入字符串转为有效的数字
									trim:输入首尾空格过滤
		-->
		<!-- 准备好一个容器-->
		<div id="root">
			<form @submit.prevent="demo">
				账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
				密码:<input type="password" v-model="userInfo.password"> <br/><br/>
				年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
				性别:
				男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
				女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
				爱好:
				学习<input type="checkbox" v-model="userInfo.hobby" value="study">
				打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
				吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
				<br/><br/>
				所属校区
				<select v-model="userInfo.city">
					<option value="">请选择校区</option>
					<option value="beijing">北京</option>
					<option value="shanghai">上海</option>
					<option value="shenzhen">深圳</option>
					<option value="wuhan">武汉</option>
				</select>
				<br/><br/>
				其他信息:
				<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
				<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
				<button>提交</button>
			</form>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		new Vue({
			el:'#root',
			data:{
				userInfo:{
					account:'',
					password:'',
					age:18,
					sex:'female',
					hobby:[],
					city:'beijing',
					other:'',
					agree:''
				}
			},
			methods: {
				demo(){
					console.log(JSON.stringify(this.userInfo))
				}
			}
		})
	</script>
</html>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值