Vue双向数据绑定之原理及实现1

本文深入探讨Vue的核心思想,解释如何通过getter/setter和Watcher实现双向数据绑定。Vue将data对象的属性转换为getter/setter方法,追踪依赖并在属性变化时更新视图。同时,Object.defineProperty用于数据劫持,确保在数据变更时能够通知到对应的DOM更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

还是否记得第一次使用Vue时的感觉?双向数据绑定,竟然那么神奇,可以让代码变的如此简单。

一、Vue核心思想

在了解Vue的双向绑定之前,你先要回忆一下Vue的核心思想。还记得getter/setter方法、还记得Watcher?
Vue核心思想

关于getter/setter方法
1.当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项 (图中就是 a 对象中的 b 属性,即 a.b),Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter 方法。
2. 用户看不到 getter/setter 方法,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
3. 同时 Vue 会对模板进行编译,解析之后会生成指令对象,也例如 v-text, v-hide 等。当指令中 v-text='a.b’时,其实就是触发 getter 方法,获取对应的数据。
另外每个属性都对应有一个 Watcher,它有什么作用?
1.当 data 选项中 a.b 值发生改变时,就会触发 setter 方法,会通知到对应的 watcher。
2. 之后再通知指令去调用 update 方法,由于指令是 DOM 的封装,所在在 update 时其实就是调用了原生 javascript 的 DOM 方法来更新界面。

二、关于Object.defineProperty

在讲解具体原理前,必须先要了解到的方法,可能你在看到这个方法之后,就有点感觉了。
Object.defineProperty该方法允许精确添加或修改对象的属性。

语法 Object.defineProperty(obj, prop, descriptor)
    obj: 要在其上定义属性的对象。
    prop: 要定义或修改的属性的名称。
    descriptor: 将被定义或修改的属性描述符。
        configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
        enumerable: 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
        get:  一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
        set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。

在Vue中,其实是使用Object.defineProperty进行了数据劫持,当获取数据或者修改数据时,都能通过自定义的getter/setter截取到。

<script type="text/javascript">
	document.onclick = function(){
		var user = {name: 'atom'}
		key = 'name'
		
		function defineProperty(obj, key, val){
			Object.defineProperty(obj, key, {
				enumerable: true, 
				
				get(){    // getter方法
					console.log('getter方法获取到的值:' + val)
					return val
				},
				set(newVal){    // setter方法
					if (val !== newVal){
						console.log(val + ' (setter方法)修改为: ' + newVal)
						val = newVal
					}
				}
			})
		}
		


		// 调用(即添加getter和setter方法)
		defineProperty(user, key, user[key])
		
		console.log(user.name)	// 获取值
		user.name = '阿童木'    // 设置值
	}
</script>

Snip20190603_8.png

三、DOM元素和数据的对应

从上面简单的例子可以看到,在获取值的时候触发get方法,在设置值的时候触发set方法。
即此时我们就可以在get/set方法中,截取到对应的数据。

接下来就要思考一下,在set方法中 对应的修改视图的函数,就能在data发生变化时,从而达到视图同步更新。相关问题:

1、如何获取到视图中的DOM元素?
    最简单,最暴力的方法 ---- 遍历

2、多个属性时,如何解决属性和对应DOM保持一致性问题?
    每个属性对应一个回调函数。
    当属性发生改变之后,直接调用自身的回调函数。
    
    类似: 一个公司中有100个员工,每个员工的名字不同、技能不一样,如何保证老板不弄错他们的姓名以及技能?这需要老板记住每个员工的相貌特征、对应的名字以及技能吗?
    解决: 只要每个员工挂一个工牌,添加上姓名、技能。当老板想知道你的时,只需要拿你的工牌看一下就可以了。 
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	
	<body>
		<div id="app">
			<h3>名字: {{name}}</h3>
			<h3>技能: {{skill}}</h3>
		</div>
		
		<div>
			请输入你的名字:<input type="text" class="text" value="" />
			<input type="button" class="bt" value="确定" />
		</div>
		
		
		<script type="text/javascript">
			function Vue(options){	// 动态原型方式  创建名字为Vue的类
				this.el = options.el		// 元素
				this.data = options.data 	// 数据
				this.watcher = {}	// 属性、数据、元素 的关联
				
				// 给每个属性添加get/set方法
				var self = this 
				Object.keys(this.data).forEach(function(key){	// 遍历所有key
					// 通过原型属性添加的方法
					self.defineProperty(self.data, key, self.data[key])	
				})
				
				// 通过原型属性添加的方法-解析DOM
				this.compile()	
			}
			
			
			Vue.prototype = {    // 添加原型属性
				defineProperty(obj, key, val){	// 属性添加get/set方法
					var self = this 		// 后续this指向问题的处理
					Object.defineProperty(obj, key, {
						enumerable: true, 
						get(){
							return val
						},
						set(newVal){
							if (val !== newVal){
								val = newVal
								self.watcher[key](newVal)	// 调用watcher中key对应的函数 进行更新
							}
						}
					})
				},
				
				compile(){	// 解析DOM即更新数据
					// 获取到对象绑定的元素
					var ele = document.querySelector(this.el);
					
					// 所有子元素
					var childEls = ele.childNodes;
					
					// 遍历所有子元素
					// Array.slice.call(arguments),目的是将arguments对象的数组提出来转化为数组,arguments本身并不是数组而是对象
					[].slice.call(childEls).forEach(el => {	// ES6写法,这就可以避免this指向问题(上面是没使用ES6写法)
						// 定义正则表达式,即两个花括号 括住的
						var reg = /\{\{(.*)\}\}/;
						
						// 文本内容
						var text = el.textContent;
						
						if(reg.test(text)){	// 正则匹配
							var key = reg.exec(text)[1]; // 获取到key
							el.textContent = this.data[key]; // data中存储的key值渲染到视图中
							
							// 更新的方法操作  每个key都对应有更新的方法
							// 当数据发生改变时,就有对应的方法进行更新
							this.watcher[key] = function(newVal){
								el.textContent = newVal
							}
						}
					})
				}
			}
			
			
			
			// 创建Vue对象
			var myVue = new Vue({
				el: '#app',
				data: {
					name: '阿童木',
					skill: 'web前端开发',
				}
			})
			
			// 修改名字
			document.querySelector('.bt').onclick = function(){
				myVue.data.name = document.querySelector('.text').value
			}
		</script>
	</body>
</html>
代码大致执行过程:
1、实例化对象
2、实例化过程中,代码中重要的点是遍历data选项中的每个key
3、在defineProperty中添加上get方法
4、在set方法中添加 界面更新的函数调用 【注意: set方法触发后才是调用,此时只是添加set方法】
5、解析DOM 【即将data中的值,先更新到界面中】
6、并给每个key对应上 更新方法  存储在 watcher 中

之后,只要数据发生改变,此时就是触发了set方法,再从watcher中找到key对应的方法进行更新页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值