先把这张图供起来
0. 什么是响应式
官网的解释(可以挑着读一下)
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
注:
- shim可以将新的API引入到旧的环境中,而且仅靠就环境中已有的手段实现。
1. 实现响应式的关键所在
- Object.defineProperty
- 订阅者设计模式
2. Object.defineProperty() 方法
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法
Object.defineProperty(obj, prop, descriptor)
参数
- obj
要定义属性的对象。 - prop
要定义或修改的属性的名称或 Symbol 。 - descriptor
要定义或修改的属性描述符。(对象)
返回值
被传递给函数的对象。
关键之处就在传递的 descriptor 参数里面的 set 和 get 方法
我们结合栗子来说
首先我们先定义一个对象,来模拟 vue 中的 data
var obj = {
message: '哈哈',
name: 'perry'
};
然后循环遍历里面的键,给 obj 里面的每一个属性调用 defineProperty 方法
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
set(newValue) { // 当改变obj中的的属性的时候调用set方法(关键)
console.log("set");
value = newValue
},
get() {// 当获取obj中的的属性的时候调用get方法(关键)
console.log("get");
return value
}
})
})
是不是有想法了,没错,我们可以在 set 和 get 函数里面大展手脚了
3. 发布-订阅模式
发布—订阅模式,它定义了对象间的一种 一对多 的关系,让多个观察者对象同时监听某一个主题对象,当主题对象发生改变时,所有依赖于它的对象都将得到通知。(这句话好好理解!!!)
看到一个例子帮助理解
你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的
(你的关注(订阅),A的发布动态(发布))。
直接来看一下它简单的模型:
// 发布者
class Dep {
constructor() {
this.Subscription = [] // 用来记录所有的订阅者
}
addSub(watcher) { // 加入订阅者
this.Subscription.push(watcher)
}
notify() { // 通知每一个订阅者更新
this.Subscription.forEach(item => {
item.update();
})
}
}
// 订阅者,也叫观察者
class Watcher {
constructor(name) {
this.name = name;
}
update() {
console.log(this.name + "发生update");
}
}
4. 两者相结合
- 我们在 set 中调用发布者的 notify ,即只要 obj 改变那么通知所有的订阅者更新
// 1. 遍历obj中的所有的键
Object.keys(obj).forEach(key => {
let value = obj[key]
// 2. 通过defineProperty方法监听改变
Object.defineProperty(obj, key, {
set(newValue) {
// 当改变obj中的的属性的时候在这里
value = newValue
dep.notify(); // 然后在这个地方通知相应dep(需要做判断)的订阅者发生改变,不同的值改变需要不同的dep发送通知(关键)
console.log('监听' + key + '改变' + ':' + value)
},
get() {
// 当获取obj中的的属性的时候在这里
console.log('获取' + key + '对应的值')
return value
}
})
})
- 然后我们创建发布者对象,并向里面添加订阅者
实例一个dep对象可以理解为当我们在data里面添加数据的时候需要做的事情
实例一个watcher对象并调用dep.addSub是在我们使用data数据的时候需要做的事情
// 实例一个dep对象,模拟一个添加了一个data中的数据
const name = new Dep();
const w1 = new Watcher('张三'); // 张三用了它
name.addSub(w1) // 张三就成为订阅者,被放到了subscription的数组里面
const w2 = new Watcher('李四'); //李四用了它
name.addSub(w2) // 李四就成为订阅者,被放到了subscription的数组里面
- 之后我们如果改变 obj 的属性则通知所有的订阅者
思路清晰了吗?
不清晰我们再来看一下完整的代码,只有两个类和一个关键逻辑而已
var obj = {
message: '哈哈',
name: 'why'
};
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
set(newValue) {
value = newValue
name.notify(); // 然后在这个地方通知相应dep订阅者发生改变
},
get() {
return value
}
})
})
// 发布者
class Dep {
constructor() {
this.Subscription = [] // 用来记录所有的订阅者
}
addSub(watcher) { // 加入订阅者
this.Subscription.push(watcher)
}
notify() { // 通知每一个订阅者更新
this.Subscription.forEach(item => {
item.update();
})
}
}
// 订阅者,也叫观察者
class Watcher {
constructor(name) {
this.name = name;
}
update() {
console.log(this.name + "发生update");
}
}
// 实例一个dep对象
const name= new Dep();
const w1 = new Watcher('张三');
name.addSub(w1) // 张三就成为订阅者,被放到了subscription的数组里面
const w2 = new Watcher('李四');
name.addSub(w2) // 李四就成为订阅者,被放到了subscription的数组里面
参考