vue数据监测原理

Posted teayear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue数据监测原理相关的知识,希望对你有一定的参考价值。

vue的数据监测原理

本章内容

  • Object.defineProperty() 数据劫持
  • vue的数据代理
  • vue的数据监测
  • 消息订阅与发布

一、vue的数据代理和响应式(数据监测)原理

当数据发生改变时 我们要实时监听到,并且随之做出改变(DOM操作)

1.1 了解Object.defineProperty()方法

概念: **Object.defineProperty() **方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

**备注:**应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。

语法

Object.defineProperty(obj, prop, descriptor)


Object.keys
Object.values
Object.assign()

参数

  • obj

    要定义属性的对象。

  • prop

    要定义或修改的属性的名称或 Symbol

  • descriptor

    要定义或修改的属性描述符。

返回值:

被传递给函数的对象。

描述:

该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举(遍历)的,在枚举对象属性时会被枚举到(for...inObject.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。

对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符**。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。

这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):

  • configurable

    当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false

  • enumerable 控制属性是否可以枚举(遍历)

    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false

数据描述符还具有以下可选键值:

  • value

    该属性对应的值。可以是任何有效的 javascript 值(数值,对象,函数等)。 默认为 undefined

  • writable

    当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。 默认为 false

存取描述符还具有以下可选键值:

  • get

    属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined

  • set

    属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined

描述符默认值汇总

  • 拥有布尔值的键 configurableenumerablewritable 的默认值都是 false
  • 属性值和函数的键 valuegetset 字段的默认值为 undefined

案例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			let people = 
				name:'张三',
				address:'郑州'
                age:18,
			
			/*
				Object.defineProperty(obj,prop,descriptor)
					obj:表示要添加属性的对象
					prop:表示要添加的属性
					descriptor:表示对新增属性的配置(描述)
			*/
			Object.defineProperty(people,'age',
				value:19,//新增属性age的值
				enumerable:true,//控制属性是否可以被遍历
				writable:true,//控制属性能否被修改
			)
			console.log(people)
			console.log(Object.keys(people))
		</script>
	</body>
</html>

可以使用Object.defineProperty()方法对 对象中的属性进行更精确的控制。

需求:能否让people对象中的age属性动态发生变化呢?

**尝试方法:**在外界定义变量num 把num的值赋给perople中的age属性。

<script>
	let num = 19;
	let people = 
				name:'张三',
				address:'郑州'
                age:num,
			
</script>
    

**结论:**此时代码第一次执行时,可以把num赋值给age,但是 后续num的值发生改变后,就无法在赋值了,所以无法完成需求。

**解决方法:**借助于Object.defineProperty()方法 中的get()配置来实现

<script>
	let num = 19;
	let people = 
    	name:'张三',
    	address:'郑州'
    
    Object.defineProperty(people,'age',
        //当有代码使用age属性时,就会执行get()方法,return的num就是age属性的值
        get()
            return num
        ,
        //当有代码给age属性赋值时 就会执行set()函数 并接收到要修改的值  val 再把val的值赋给 num变量 只要num的值发生改变 就相当于age的值发生了改变。
        set(val)
            num = val
        
    )
</script>
此时通过  Object.defineProperty方法 添加get()函数描述符 可以解决问题,此时并不是直接把num的值赋给age,而是,当每次有调用age属性时,就去执行get()函数 去重新读取num的值。此时 只要num的值发生改变  age的值就会随之发生改变。可以完成我们的需求。

1.2 vue的数据代理

**数据代理:**通过一个对象来对另一个对象中的属性进行读和写。

代码分析:

<body>
		<div id="app">
			<h1>名字是:name</h1>
			<h1>年龄是:age</h1>
		</div>
	</body>
	<script type="text/javascript">
		let vm = new Vue(
			el:'#app',
			data:
				name:'张三',
				age:18
			
		)
	</script>

**问题一:**页面标签中显示的数据 是直接从data中获取的吗?

​ 不是,vue在读取到data属性后 会先把data赋值给 vue实例上的另外一个属性 _data ,再把_data中的属性和getter和setter方法复制到vm的实例对象上。

问题二: 页面标签中的数据是直接从_data属性中获取的吗 ?

​ 不是,如果是直接从_data中获取数据,此时我们在页面标签中取数据就应该这样写 _data.name 这样写法其实也是可以的,但是我们每一次的操作都要加上_data编码不方便,所以 vue又把_data中的属性取出来在vm实例对象上放了一份。所以我们后续的代码写法可以直接写成name/age,而不用添加 _data了。底层的实现就是Object.defineProperty()方法。

问题三: vm实例对象上的_data属性中为什么也会有对应的getter和setter方法呢?使用的是数据代理吗?

​ 不是,注意:此处采用的是数据劫持(就是Vue会先对data中的数据进行处理了,添加getter和setter方法就叫数据劫持),因为vue 要对数据做监听,当数据发生改变时,要同时把改变后的数据同步更新到DOM中。所以此处 被叫做响应式(vue监测数据改变的原理)。

小结:Object.defineProperty()方法的主要作用:

  • 可以让我们实时的获取最新的数据
  • 可以让我们在获取和设置数据时 能够做一些额外的操作(调用一些其它的函数)

1.3 vue监测数据改变的原理(响应式)对象/数组

**核心:**是当vue监测到数据改变时,是如何让页面上所有用到这个数据的DOM 发生更新的!!!

**设计模式: ** 观察者模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdkuKMmc-1655452041920)(assets/image-20220309145018046.png)]

1.3.1 消息发布与订阅

案例:用户关注公众号,订阅公众号信息,当公众号发布消息时,所有关注这个公众号的用户都可以接收到消息。

ES5 通过原型对象完成发布与订阅:

//定义发布者 Observer
function GongZongHao(name) 
	console.log(name + '公众号创建了')
	this.name = name;
    //定义数组 用来存储关注公众号的用户
	this.subList = [];

//向原型上添加方法  订阅方法   和 发布方法
GongZongHao.prototype.addUser = function(user) 
	console.log(user.name + "关注了AAA公众号")
	this.subList.push(user)

GongZongHao.prototype.sendMsg = function(msg) 
	console.log(this.name + "发布了消息:" + msg)
	this.subList.forEach(item => 
		//调用用户接收消息的方法
		item.receive(msg)
	)


//定义用户
function User(name) 
	this.name = name;

//定义用户接收消息的方法
User.prototype.receive = function(msg) 
	console.log(this.name + "接收到了消息:" + msg)


//定义AAA公众号对象
let AAA = new GongZongHao('aaa')

//定义用户张三,李四,王五,赵六
let zhangsan = new User('张三')
let lisi = new User('李四')
let wangwu = new User('王五')
let zhaoliu = new User('赵六')

//让用户订阅AAA公众号
AAA.addUser(zhangsan)
AAA.addUser(lisi)
AAA.addUser(wangwu)
AAA.addUser(zhaoliu)

//AAA公众号发布消息
AAA.sendMsg('今天是个好天气')

ES6 类与对象完成发布与订阅:

//定义发布者 公众号
class GongZongHao
	constructor(name) 
		//定义公众号的名字
	    this.name = name
		//定义一个数组用来存储订阅公众号的用户
		this.subList = []
	
	//定义订阅公众号的方法
	addUser(user)
		this.subList.push(user)
	
	
	//定义公众号发送消息的方法
	sendMsg(msg)
		console.log(this.name+"公众号发布了消息:"+msg)
		this.subList.forEach(item=>
			item.receive(msg)
		)
	


//定义订阅者  用户
class User
	constructor(name) 
	    //定义用户的名字
	    this.name = name;
	
	//定义用户接收信息的方法
	receive(msg)
		console.log(this.name+"接收到了消息:"+msg)
	



//定义AAA公众号
let AAA = new GongZongHao('aaa')

//定义用户张三
let zhangsan = new User('张三')
let lisi = new User('李四')
let wangwu = new User('王五')
let zhaoliu = new User('赵六')

//让用户订阅AAA公众号
AAA.addUser(zhangsan)
AAA.addUser(lisi)
AAA.addUser(wangwu)
AAA.addUser(zhaoliu)


//AAA公众号发布消息
AAA.sendMsg('今天是个好天气')

1.3.2 vue监测数据改变的原理----对象

案例:手动实现简易版vue监测对象的数据原理:

**需求:**定义data数据 当其中属性的值发生改变时 就 在控制台输出 某属性发生了改变

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			let data = 
				name:'张三',
				age:18
			
            //这种方式 会造成死循环
			/* Object.defineProperty(data,'name',
				get()
					return data.name
				,
				set(val)
					data.name = val
				
			)
			console.log(data);
			data.name = 'Lis' */
			
			//定义构造函数
			function Obs(obj)
				this.name = obj.name;
				this.age = obj.age;
				let keys = Object.keys(obj)
				keys.forEach(k=>
					Object.defineProperty(this,k,
						get()
							return obj[k]
						,
						set(val)
							console.log(`$k被修改了`)
							obj[k] = val
						
					)
				)
			
            //创建Obs构造函数的实例对象 并把要监测的 data数据对象 传入
            let obs = new Obs(data)
            //把定义好的数据监测对象赋值给data本身和vue实例对象
			let vm = data = obs
            
			data.name = '王五'
			data.age = 19
		</script>
	</body>
</html>

案例: 使用发布与订阅模式+Object.defineProperty()方法完成监听数据改变,并更新页面DOM的需求。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<p id='p1'>你好哈哈哈</p>
	</body>
	<script type="text/javascript">
	
		//定义发布者
		class Dep
			constructor(data) 
			    this.data = data
				//定义数组用来存储订阅者
				this.subList = []
			
			
			//添加订阅者
			addWatcher(watcher)
				//把订阅者添加到数组中
				this.subList.push(watcher)
			
			//通知订阅者
			send()
				//通知所有订阅者 并调用对应的操作
				this.subList.forEach(w=>
					w.callBack(this.data)
				)
			
			//监听data中数据的改变
			listenData()
				//console.log(this)
				let that = this;
				//取出要监听的数据中所有的key值
				let keys = Object.keys(this.data)
				keys.forEach(key=>
					let value = this.data[key]
					Object.defineProperty(this.data,key,
						configurable:true,
						enumerable:true,
						//当外界获取元素时执行
						get()
							console.log(`读取了$key的值`)
							return value
						,
						//当外界设置,或修改元素时执行 val就是修改时 传入的值
						set(val)
							console.log(`$key的值发生了改变`)
							value = val
							//调用通知方法
							that.send()
						
					)
				)
			
		
		
		//定义订阅者
		class Watcher
			constructor(fn) 
			    this.fn = fn
			
			callBack(data)
				this.fn(data)
			
		
		//定义订阅者收到消息后的操作
		function fn(data)
			console.log('监听到数据发生改变,需要调用渲染dom的方法')
			console.log(data)
			//更新DOM结构的数据
			document.getElementById('p1').innerText = data.name
		
		
		//创建订阅者对象
		let w1 = new Watcher(fn)
		
		//定义vue对象的构造函数
		class my_vue
			constructor(options) 
				this.$data = options.data
				this.$el = options.el
				//创建发布者
				let dep = new Dep(options.data)
				//调用添加订阅者方法
				dep.addWatcher(w1)
				//调用监听数据的改变的方法
				dep.listenData()
				//通知订阅者
				dep.send()
			
		
		
        //定义被代理的数据
        let obj = 
				name:'张三',
				age:18
			
        
		//创建vue实例对象
		let vm = new my_vue(
			el:'#app',
			data:obj
		)
		
	</script>
</html>

1.3.3 Vue.set()vm.$set()方法

需求:vm实例对象中data属性中含有一个对象类型的数据Person,如何动态的向Person对象中添加一个响应式的属性?

如果我们直接向Person对象中添加一个age属性 可以添加成功,但是此时这个age属性并不是响应式数据,也就是说数据发生改变时,页面视图并不会随之改变。我们可以通过Vue.set()或者vm.set()方法来完成这个需求。

Vue.set( target, propertyName/index, value )
  • 参数

    • Object | Array target
    • string | number propertyName/index
    • any value
  • 返回值:设置的值。

  • 用法

    向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

    **注意:**对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

	<body>
		<div id="app">
			<h2>姓名是:person.name</h2>
			<h2>地址是:person.address</h2>
			<h2 v-if="person.age">年龄是:person.age</h2>
			<button type="button" @click="addAge">点我添加属性age</button>
		</div>
	</body>
	<script type="text/javascript">
		new Vue(
			el:'#app',
			data:
				person:
					name:'小明',
					address:'郑州'
				
			,
			methods:
				addAge()
					//这种添加方式不是响应式的数据
					//this.person.age = 18
					//通过vue内部提供的方法添加   就是响应式数据
					//Vue.set(this.person,'age',19)
					this.$set(this.person,'age',20)
				
			
		)
	</script>

**注意:**此方法只能用于在data内部的对象属性上添加内容,而不能直接在data上 或者 vm上添加属性。

小结:

  • Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter (数据劫持)转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
  • 对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。

1.3.4 vue监测数据改变的原理----数组

**需求:**编码实现 点击按钮把张三的个人信息更改成新的信息。

<!DOCTYPE html>
<html>
	<head>
		<meta<

以上是关于vue数据监测原理的主要内容,如果未能解决你的问题,请参考以下文章

vue3.0的proxy响应式原理简单实现

Vue监测数据改变的原理

Vue 条件渲染,列表渲染,key的作用和原理,列表过滤,列表排序,Vue监测数据原理和注意事项

vue.js响应式原理解析与实现

vue.js响应式原理解析与实现

vue.js响应式原理解析与实现