vue数据双向绑定的简单实现

本文详细解析了数据双向绑定的实现原理,通过自定义代码展示了从输入框到数据显示的完整流程,包括视图到数据、数据到视图的双向同步机制。

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

对数据双向绑定一直都是半知半解,今天就打算自己写个简单的实现。
可能有理解不到位甚至是错误的地方,如果有人发现的话可以指正~谢谢

数据的双向绑定,即视图和数据之间

// html
<input type="text" id="input"/>
<p id="output"></p>

// js
var oInput = document.getElementById('input')
var oOutput = document.getElementById('output')

var data = {
	content: ''
}

以上准备了一个用于修改数据的input输入框,用于展示数据的p标签以及用于存储相关数据的data对象;当input输入框的值改变时,值将同步更新于P标签。

  • 视图 -> 数据

这个比较简单,,当输入的内容变化时,就将输入框内容复制给data对象

// js
oInput.addEventListener('keyup', function(e) {
	data.content = e.target.value
},false)
  • 数据 -> 视图

vue里用了Object.defineProperty(),结合订阅/发布模式实现

个人的理解如下图:

在这里插入图片描述

1. 将需要取值的元素定义为数据的订阅者,在上面的例子中就是P标签。

订阅者的任务就是更新视图,即更新P标签展示的内容。

function Watcher(obj, key, cb) {
	/*
	 * 更新视图
	 */
	TARGET = this //TARGET为订阅者标志
	this._obj = obj
	this._key = key
	this._cb = cb
	this._value = obj[key]
	TARGET = null
}

Watcher.prototype.update = function() {
	let value = this._obj[this._key]
	this._cb.call(this._obj, value)
}
2. 那么多订阅者由谁来统一管理?它们订阅的信息由谁来统一发布信息呢?对于a数据,可能有a1,a2,a3…订阅者,a1,a2,a3…都需要展示a数据,那么这些a1,a2,a3…就由发布者A来统一管理;同理对于b数据,就由发布者B来管理b的订阅者b1,b2,b3。

发布者的任务就是收集订阅者和通知订阅者更新视图

function Dep() {
	/*
	 * 发布者有两件事要做:
	 * 1. 吸纳订阅者
	 * 2. 将数据更新信息告知订阅者
	 */
	
	//订阅者队列
	this.subs = []
}


Dep.prototype.notify = function(newVal) {
	//通知订阅者
	for (var i=0; i<this.subs.length; i++) {
		this.subs[i].update()
	}
}
Dep.prototype.addSubs = function(watcher) {
	//吸纳订阅者
	this.subs.push(watcher)
}
3. 对data对象里每个数的取值、赋值操作进行拦截,当其中某个数据被取值、赋值时,相应地做出某些事;同时,也为每一个数据配备一个发布者,当这个数据有变化时,都由这个发布者统一发布。
function observe(obj) {
		/*
		 * 观察者有两件事要做:
		 * 1. 当数据被取,通知中间件去吸纳订阅者
		 * 2. 当数据被设置更新,通知中间件数据更新了
		 * 那么如何知道数据被取或者被设置呢?用Object.defineProperty的get()、set()劫持
		 */
		this.obj = obj
		for(var key in obj) {
			defineReactive(obj, key, obj[key])
		}
		
	}
	
	function defineReactive(obj, key, val) {
	 	var dep = new Dep()
			
		Object.defineProperty(obj, key, {
			get() {
				//通知发布者去吸纳订阅者
				if(TARGET) {
					dep.addSubs(TARGET)
				}
				return val
			},
			set(newVal) {
				//通知发布者要更新数据
				val = newVal
				dep.notify()
			},
		})
	}

现在各路神仙已经到位啦!

1. 现在我们知道P标签要运用到的是data对象,先给data里的数据加上set/get来拦截存取操作;并添加观察者来更新P视图。
observe(data)

new Watcher(data, 'content', val => {
	oOutput.innerHTML = val
})

在Watcher里,有一个操作this._value = obj[key],obj[key]在这里也就是data.content。

这个操作被get()发现了!get()赶紧通知content的发布者。

get:“dep!这里有人要取我的值,它是一个TARGET,快来吸纳它!”

dep:“我来啦!dep.addSubs(TARGET),我已经把它加入了我的订阅者数组里啦!”

2. 现在我们在输入框输入数据,触发了keyup事件,执行了data.content = e.target.value
  • data.content = ???,这是要对content进行修改。之前我们已经对data里的数据进行了拦截,当要对它进行修改时,被set()发现了。

set:“dep,content被修改了,你快通知你的订阅者们”。

dep:“我来啦!dep.notify(),我去订阅数组里看看都有谁,再一个个通知它们更新视图去!”

3. 相关的Watcher们都听到了dep的呼唤this.subs[i].update(),乖乖更新视图去了。
这里的Watcher是手动添加的,在vue里,通过编译模板,识别了v-model以及{{}},自动添加Watcher

js

window.onload = function() {
	
	var oInput = document.getElementById('input')
	var oOutput = document.getElementById('output')
	
	var data = {
		content: ''
	}
	
	// Observe
	function observe(obj) {
		/*
		 * 观察者有两件事要做:
		 * 1. 当数据被取,通知中间件去吸纳订阅者
		 * 2. 当数据被设置更新,通知中间件数据更新了
		 * 那么如何知道数据被取或者被设置呢?用Object.defineProperty的get()、set()劫持
		 */
		this.obj = obj
		for(var key in obj) {
			defineReactive(obj, key, obj[key])
		}
		
	}
	
	function defineReactive(obj, key, val) {
	 	var dep = new Dep()
			
		Object.defineProperty(obj, key, {
			get() {
				//通知发布者去吸纳订阅者
				if(TARGET) {
					dep.addSubs(TARGET)
				}
				return val
			},
			set(newVal) {
				//通知发布者要更新数据
				val = newVal
				dep.notify()
			},
		})
	}
	
	// Dep
	function Dep() {
		/*
		 * 发布者有两件事要做:
		 * 1. 吸纳订阅者
		 * 2. 将数据更新信息告知订阅者
		 */
		
		//订阅者队列
		this.subs = []
	}
	
	
	Dep.prototype.notify = function(newVal) {
		//通知订阅者
		for (var i=0; i<this.subs.length; i++) {
			this.subs[i].update()
		}
	}
	Dep.prototype.addSubs = function(watcher) {
		//吸纳订阅者
		this.subs.push(watcher)
	}
	
	// Watcher
	function Watcher(obj, key, cb) {
		/*
		 * 更新视图
		 */
		TARGET = this //TARGET为订阅者标志
		this._obj = obj
		this._key = key
		this._cb = cb
		this._value = obj[key]
		TARGET = null
	}
	
	Watcher.prototype.update = function() {
		let value = this._obj[this._key]
		this._cb.call(this._obj, value)
	}
	
	
	observe(data)
	new Watcher(data, 'content', val => {
		oOutput.innerHTML = val
	})
	
	oInput.addEventListener('keyup', function(e) {
		data.content = e.target.value
	},false)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值