前言
最近在研究Vue的源码,探索一下Vue的响应式原理。研究它是怎么实现的。Vue2.0是用 Object.defineProperty 去实现的, Vue3.0是用Proxy 去实现的。然后再结合观察者模式去实现数据跟视图的更新。本节先分享一下基础原理,下一节分享一下具体的实现。
数据响应式的核心原理
Vue2.x
- Vue2.x深入响应式原理
- MDN-Object.defineProperty
- 兼容IE8以上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue2.0响应式原理</title>
</head>
<body>
<div id="app">hello</div>
<script>
// 绑定单个
let data = {
msg: 'hello word',
count: 10
}
let vm = {}
Object.defineProperty(vm, 'msg', {
configurable: true, // 是否能被改变,是否能被delete删除
enumerable: true, // 是否可枚举,遍历
get() {
console.log('get:' + data.msg)
return data.msg
},
set(newVal) {
if(data.msg === newVal) return
data.msg = newVal
console.log('set:' + data.msg)
document.getElementById('app').innerText = data.msg
}
})
console.log(vm.msg)
vm.msg = 'xxx'
console.log('===================')
// 绑定多个
let data2 = {
msg: 'hello word',
count: 10,
obj: {
name: '你好啊'
},
arr: [1, 2, 3]
}
let vm2 = {
data: data2
}
function defineProperty(obj, key, val) {
// 如果value是对象,则继续对他下级成员进行响应式监听
observer(val)
Object.defineProperty(obj, key, {
configurable: true, // 是否能被改变,是否能被delete删除
enumerable: true, // 是否可枚举,遍历
get() {
console.log('get:' + val)
return val
},
set(newVal) {
if(val === newVal) return
// 如果新设置的值是对象,则继续对他下级成员进行响应式监听
observer(newVal)
val = newVal
console.log('set:' + key + ':' + val)
}
})
}
function observer(data) {
// 如果不是对象,则返回
if(!data || typeof data != 'object' ) return
Object.keys(data).forEach(key => {
defineProperty(data, key, data[key])
})
}
observer(vm2.data)
console.log(vm2)
</script>
</body>
</html>
Vue3.x
- MDN-Proxy
- 直接监听对象,而不是属性
- IE不支持,性能由浏览器优化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue3.0响应式原理</title>
</head>
<body>
<div id="app">hello</div>
<script>
let data = {
msg: 'hello word',
count: 10,
obj: {
name: '你好啊'
},
arr: [1, 2, 3]
}
let vm = new Proxy(data, {
get(target, key) {
console.log('get', key, target[key])
return target[key]
},
set(target, key, newVal) {
if(target[key] == newVal) return
console.log('set', key, newVal)
target[key] = newVal
}
})
console.log(vm.msg)
</script>
</body>
</html>
发布订阅模式
发布订阅模式由三者组成:发布者、订阅者、信号中心。我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)
- 我们先看下Vue的自定义事件$on和$emit
let vm = new Vue()
vm.$on('dataChange', () => {
console.log('dataChange')
})
vm.$on('dataChange', () => {
console.log('dataChange1')
})
vm.$emit('dataChange')
- 自己实现一个发布订阅模式的思路
- 定义一个类,初始化的时候,定义一个对象subs,用来存储各种事件,以及订阅这个事件的数组对象
- 创建一个订阅方法,把订阅的事件和对象存储到subs中,如果没有这个事件,则创建一个,有的话,则添加到订阅的数组对象中
- 创建一个发布方法,把发布事件,所订阅的所有对象,都通知一遍
class EventEmitter {
constructor() {
// 存储所有事件对象
this.subs = {}
}
$on(eventType, fn) { // 订阅
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(fn)
}
$emit(eventType, ...params) { // 发布
if(this.subs[eventType]) {
this.subs[eventType].forEach(fn => {
fn(...params)
})
}
}
}
let eve = new EventEmitter()
eve.$on('click', function(data, two) {
console.log('click1', data, two)
})
eve.$on('click', function(data) {
console.log('click2', data)
})
eve.$on('change', function() {
console.log('change')
})
eve.$emit('click', 1, 2)
eve.$emit('change')
观察者模式
- 观察者 -- Watcher
- update() 这里处理当事件发生时,所要做的事情
- 目标 -- Dep
- subs 存储所有观察者的数组
- addSub() 添加观察者的方法,参数是观察者对象
- notify() 循环subs,通知所有观察者,调用观察者的update方法
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
if(sub && sub.update) {
this.subs.push(sub)
}
}
notify() {
if(!this.subs.length) return
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
update() {
console.log('update')
}
}
let dep = new Dep()
let watch = new Watcher()
dep.addSub(watch)
dep.notify()
总结
- 观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
- 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。
实现一个简单的vue-响应式模拟(二)
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!