数据代理

在Vue.js中,数据代理是Vue实现响应式数据的核心机制之一。它通过使用JavaScript的Object.defineProperty(在Vue 3中则是Proxy)来拦截对对象属性的访问和修改,从而实现数据和视图的双向绑定。这使得开发者可以更方便地使用简洁的语法来访问和操作数据,而Vue会自动更新相应的视图。

数据代理的概念

数据代理的核心是将组件的data对象中的属性代理到Vue实例上。这意味着你可以通过this.propertyName直接访问和修改data中的属性,而Vue会自动更新视图。

数据代理的实现原理

在Vue 2中,数据代理是通过Object.defineProperty实现的。Vue在实例化时,会遍历data对象中的所有属性,并为每个属性设置gettersetter。当你访问或修改这些属性时,Vue会自动触发相应的DOM更新。

数据代理的使用

示例代码

<!DOCTYPE html>
<html>
<head>
<title>Vue Data Proxy Example</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>Message: {{ message }}</p>
<input v-model="message" placeholder="Edit message">
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
</body>
</html>

解释:在这个示例中,message 是定义在 data 对象中的属性。通过Vue的数据代理机制,你可以直接通过 &#123;&#123; message &#125;&#125; 访问它,或者通过 v-model 绑定到输入框上进行修改。
Vue会自动代理data中的message属性,使得你可以通过this.message来访问它,并在视图中使用&#123;&#123; message &#125;&#125;绑定进行显示。

数据代理的工作流程

当你在模板中引用&#123;&#123; message &#125;&#125;时,Vue实际上是访问了this.message,并通过数据代理机制去data对象中获取对应的值。

当你在输入框中输入新内容时,v-model指令会触发message属性的setter,Vue捕捉到这个变化后,会重新渲染与message相关的部分视图。

数据代理的好处

  • 简洁性:你可以通过简洁的语法(例如this.message)直接访问数据,而不需要每次都通过data对象。

  • 响应性:数据代理使得数据与视图之间的双向绑定更加直观和简单,Vue会自动处理数据变化引起的视图更新。

  • 可维护性:通过数据代理,你可以将数据与视图的逻辑清晰地分离,代码更易于维护和理解。

计算属性与监视属性

在Vue.js中,计算属性(computed properties)和监视属性(watchers)是两种常用的响应式特性,用于在数据发生变化时自动更新页面的内容。它们虽然在功能上有相似之处,但适用于不同的场景。

计算属性(Computed Properties)

功能

计算属性是基于已存在的响应式数据计算出一个新值的属性。它会根据依赖的数据自动更新,并且有缓存机制,只有当依赖的数据发生变化时才会重新计算。

计算属性适合用于基于其他数据动态计算出新值的场景,比如对数据进行格式化、合并、筛选等操作。

特点

缓存:计算属性的结果会被缓存,直到它的依赖发生变化。这使得它在性能上比直接在模板中使用复杂表达式更优。

简洁:计算属性通常以gettersetter形式定义,getter用于获取计算值,setter用于响应用户的输入或其他操作。

插值语法示例代码

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>姓名案例_插值语法实现</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{firstName}}-{{lastName}}</span>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			firstName:&#x27;张&#x27;,
			lastName:&#x27;三&#x27;
		&#125;
	&#125;)
&lt;/script&gt;

</html>

methods实现示例代码

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>姓名案例_methods实现</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName()}}</span>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			firstName:&#x27;张&#x27;,
			lastName:&#x27;三&#x27;
		&#125;,
		methods: &#123;
			fullName()&#123;
				console.log(&#x27;@---fullName&#x27;)
				return this.firstName + &#x27;-&#x27; + this.lastName
			&#125;
		&#125;,
	&#125;)
&lt;/script&gt;

</html>

计算属性实现示例代码

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>姓名案例_计算属性实现</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
测试:<input type="text" v-model="x"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
<!– 全名:<span>{{fullName}}</span> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
全名:<span>{{fullName}}</span> –>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			firstName:&#x27;张&#x27;,
			lastName:&#x27;三&#x27;,
			x:&#x27;你好&#x27;
		&#125;,
		methods: &#123;
			demo()&#123;
				
			&#125;
		&#125;,
		computed:&#123;
			fullName:&#123;
				//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
				//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
				get()&#123;
					console.log(&#x27;get被调用了&#x27;)
					// console.log(this) //此处的this是vm
					return this.firstName + &#x27;-&#x27; + this.lastName
				&#125;,
				//set什么时候调用? 当fullName被修改时。
				set(value)&#123;
					console.log(&#x27;set&#x27;,value)
					const arr = value.split(&#x27;-&#x27;)
					this.firstName = arr[0]
					this.lastName = arr[1]
				&#125;
			&#125;
		&#125;
	&#125;)
&lt;/script&gt;

</html>

解释:fullName 是一个计算属性,它通过 firstName 和 lastName 动态计算得出。如果用户输入一个新的全名,它也会自动更新 firstName 和 lastName。

计算属性实现简写

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>姓名案例_计算属性实现</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			firstName:&#x27;张&#x27;,
			lastName:&#x27;三&#x27;,
		&#125;,
		computed:&#123;
			//完整写法
			/* fullName:&#123;
				get()&#123;
					console.log(&#x27;get被调用了&#x27;)
					return this.firstName + &#x27;-&#x27; + this.lastName
				&#125;,
				set(value)&#123;
					console.log(&#x27;set&#x27;,value)
					const arr = value.split(&#x27;-&#x27;)
					this.firstName = arr[0]
					this.lastName = arr[1]
				&#125;
			&#125; */
			//简写
			fullName()&#123;
				console.log(&#x27;get被调用了&#x27;)
				return this.firstName + &#x27;-&#x27; + this.lastName
			&#125;
		&#125;
	&#125;)
&lt;/script&gt;

</html>

原理

底层借助了Objcet.defineproperty方法提供的gettersetter

get函数什么时候执行?

  • 初次读取时会执行一次。

  • 当依赖的数据发生改变时会被再次调用。

优势

与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

计算属性最终会出现在vm上,直接读取使用即可。
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

监视属性(Watchers)

功能

监视属性用于监听某个数据属性的变化,并在其变化时执行回调函数。适合用于执行异步操作或在数据变化时需要执行复杂逻辑的场景。

特点

灵活性:可以监听单个数据属性的变化,也可以监听多个数据属性,甚至可以使用深度监听(deep)来监控对象内部的变化。

无缓存:与计算属性不同,监视属性没有缓存机制,每次数据变化都会触发回调。

语法:

  • new Vue时传入watch配置

  • 通过vm.$watch监视

天气案例示例代码(计算属性)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>天气案例</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
<h2>今天天气很{{info}}</h2>
<!– 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 –>
<!– <button @click="isHot = !isHot">切换天气</button> –>
<button @click="changeWeather">切换天气</button>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	
	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			isHot:true,
		&#125;,
		computed:&#123;
			info()&#123;
				return this.isHot ? &#x27;炎热&#x27; : &#x27;凉爽&#x27;
			&#125;
		&#125;,
		methods: &#123;
			changeWeather()&#123;
				this.isHot = !this.isHot
			&#125;
		&#125;,
	&#125;)
&lt;/script&gt;

</html>

天气案例示例代码(监视属性)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>天气案例_监视属性</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!–
监视属性watch:
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法:
(1).new Vue时传入watch配置
(2).通过vm.$watch监视
–>
<!– 准备好一个容器–>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	
	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			isHot:true,
		&#125;,
		computed:&#123;
			info()&#123;
				return this.isHot ? &#x27;炎热&#x27; : &#x27;凉爽&#x27;
			&#125;
		&#125;,
		methods: &#123;
			changeWeather()&#123;
				this.isHot = !this.isHot
			&#125;
		&#125;,
		/* watch:&#123;
			isHot:&#123;
				immediate:true, //初始化时让handler调用一下
				//handler什么时候调用?当isHot发生改变时。
				handler(newValue,oldValue)&#123;
					console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue)
				&#125;
			&#125;
		&#125; */
	&#125;)

	vm.$watch(&#x27;isHot&#x27;,&#123;
		immediate:true, //初始化时让handler调用一下
		//handler什么时候调用?当isHot发生改变时。
		handler(newValue,oldValue)&#123;
			console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue)
		&#125;
	&#125;)
&lt;/script&gt;

</html>

深度监视

  • Vue中的watch默认不监测对象内部值的改变(一层)。

  • 配置deep:true可以监测对象内部值改变(多层)。

备注:(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!(2).使用watch时根据数据的具体结构,决定是否采用深度监视。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>天气案例_深度监视</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr/>
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
{{numbers.c.d.e}}
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	
	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			isHot:true,
			numbers:&#123;
				a:1,
				b:1,
				c:&#123;
					d:&#123;
						e:100
					&#125;
				&#125;
			&#125;
		&#125;,
		computed:&#123;
			info()&#123;
				return this.isHot ? &#x27;炎热&#x27; : &#x27;凉爽&#x27;
			&#125;
		&#125;,
		methods: &#123;
			changeWeather()&#123;
				this.isHot = !this.isHot
			&#125;
		&#125;,
		watch:&#123;
			isHot:&#123;
				// immediate:true, //初始化时让handler调用一下
				//handler什么时候调用?当isHot发生改变时。
				handler(newValue,oldValue)&#123;
					console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue)
				&#125;
			&#125;,
			//监视多级结构中某个属性的变化
			/* &#x27;numbers.a&#x27;:&#123;
				handler()&#123;
					console.log(&#x27;a被改变了&#x27;)
				&#125;
			&#125; */
			//监视多级结构中所有属性的变化
			numbers:&#123;
				deep:true,
				handler()&#123;
					console.log(&#x27;numbers改变了&#x27;)
				&#125;
			&#125;
		&#125;
	&#125;)

&lt;/script&gt;

</html>

监视属性简写

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>天气案例_监视属性_简写</title>
<!– 引入Vue –>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!– 准备好一个容器–>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

&lt;script type=&quot;text/javascript&quot;&gt;
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	
	const vm = new Vue(&#123;
		el:&#x27;#root&#x27;,
		data:&#123;
			isHot:true,
		&#125;,
		computed:&#123;
			info()&#123;
				return this.isHot ? &#x27;炎热&#x27; : &#x27;凉爽&#x27;
			&#125;
		&#125;,
		methods: &#123;
			changeWeather()&#123;
				this.isHot = !this.isHot
			&#125;
		&#125;,
		watch:&#123;
			//正常写法
			/* isHot:&#123;
				// immediate:true, //初始化时让handler调用一下
				// deep:true,//深度监视
				handler(newValue,oldValue)&#123;
					console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue)
				&#125;
			&#125;, */
			//简写
			/* isHot(newValue,oldValue)&#123;
				console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue,this)
			&#125; */
		&#125;
	&#125;)

	//正常写法
	/* vm.$watch(&#x27;isHot&#x27;,&#123;
		immediate:true, //初始化时让handler调用一下
		deep:true,//深度监视
		handler(newValue,oldValue)&#123;
			console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue)
		&#125;
	&#125;) */

	//简写
	/* vm.$watch(&#x27;isHot&#x27;,(newValue,oldValue)=&gt;&#123;
		console.log(&#x27;isHot被修改了&#x27;,newValue,oldValue,this)
	&#125;) */

&lt;/script&gt;

</html>

计算属性 vs 监视属性

1.computed能完成的功能,watch都可以完成。

2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

两个重要的小原则:

  • 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象

  • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象

计算属性变量在computed中定义,监视属性监听的是已经在 data 中定义的变量, 当该变量变化时,会触发 watch 中的方法.

computed 具有缓存功能,可以监听对象某个具体属性。watch可以进行深度监听,监听对象的变化

计算属性是声明式的描述一个值依赖了其他值,依赖的值改变后重新计算结果更新DOM。监视属性的是定义的变量,当定义的值发生变化时,执行相对应的函数