Vue响应式原理

Object.defineProperty()

Vue 内部使用了 Object.defineProperty() 来实现数据响应式,通过这个函数可以监听到 set 和 get 的事件。

先定义一个对象 以及属性

var data = {name: 'john'};
observe(data); // 监听 这个对象
console.log(data.name);
data.name = 'lily';
console.log(data.name);

定义observe

function observe(obj){
 // 先进行类型判断
 if(!obj || typeof obj !== 'object') return
 Object.keys(obj).forEach(key => {
 	defineReactive(obj, key, obj[key])
 })
}

定义defineReactive

function defineReactive(obj, key, val){
  observe(obj); // 进行递归(如果值是对象的话)
  Object.defineProperty(obj, key, {
  	enumerable: true,
  	configurable: true,
  	get: function reactiveGetter() {
		return val
	},
	set: function reactiveSetter(newval) {
		val = newval
	}
  })

}

以上就只是 进行监听数据的set 与 get事件。但是仅仅如此是不够的,因为自定义的函数一开始是不会执行的。只有先执行了依赖收集,才能在属性更新的时候派发更新,所以接下来我们需要先触发依赖收集。

<div>{{name}}</div>

在解析如上模板代码时,遇到 {{name}} 就会进行依赖收集。
接下来我们先来实现一个 Dep 类,用于解耦属性的依赖收集和派发更新操作。

class Dep{
	constructoe(){
		this.subs  = [];
	},
	// 添加依赖
	addSub(sub) {
		this.subs.push(sub)
	},
	// 更新
	notify() {
		this.subs.forEach(sub => {
			sub.update()
		})
	}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null

以上的代码实现很简单,当需要依赖收集的时候调用 addSub,当需要派发更新的时候调用 notify。

接下来我们先来简单的了解下 Vue 组件挂载时添加响应式的过程。在组件挂载时,会先对所有需要的属性调用 Object.defineProperty(),然后实例化 Watcher,传入组件更新的回调。在实例化过程中,会对模板中的属性进行求值,触发依赖收集。

class Watcher{
	constructor(obj, key, cb){
		// 将 Dep.target 指向自己
		// 然后触发属性的 getter 添加监听
		// 最后将 Dep.target 置空
		Dep.traget = this
		this.cb = cb
		this.obj = obj
		this.key = key
		this.value = obj[key]
		Dep.target = null
	}
	update() {
		// 获得新值
		this.value = this.obj[this.key]
		// 调用 update 方法更新 Dom
		this.cb(this.value)
	}
}

以上就是 Watcher 的简单实现,在执行构造函数的时候将 Dep.target 指向自身,从而使得收集到了对应的 Watcher,在派发更新的时候取出对应的 Watcher 然后执行 update 函数。

接下来,需要对 defineReactive 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码。

function defineReactive(obj, key, val) {
	observe(val);
	let dp = new Dep();
	Object.defineProperty(obj, key, {
		enumerable: true,
		configurable: true,
		get: function reactiveGetter(){
		    // 将 Watcher 添加到订阅
			if(Dep.target){
				dp.addsub(Dep.target)
			}
			return val
		}
		set: function reactiveSetter(newVal){
			val = newVal
			dp.notify()
		}
	})
}

以上所有代码实现了一个简易的数据响应式,核心思路就是手动触发一次属性的 getter 来实现依赖收集。

进行验证

var data = { name: 'yck' }
observe(data)
function update(value) {
document.querySelector('div').innerText = value
}
// 模拟解析到 `{{name}}` 触发的操作
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy'
Object.defineProperty 的缺陷

基于以上,如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

如何解决呢:
vue 官方文档中有贴:
在这里插入图片描述
在这里插入图片描述
手动触发视图,这$ forceUpdate()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值