Vue响应式原理(MVVM)
Vue实现的核心逻辑,跟Watcher,Observer,Dep三者密切相关,而这三者的关系其实是一个简单的发布-订阅的设计模式。也就是,数据状态一旦发生了变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑。
下面来梳理Watcher,Observer,Dep三者:
Observer观察者,用来观察数据源的变化;
Dep一个观察者和多个订阅者的依赖中心,管理某个观察者和其所有对应的订阅者的关系,可进行消息调度和依赖管理;
Watcher订阅者,当观察者数据发生变化,经过消息调度中心,最终传递到订阅者,然后这些订阅者分别执行自身的业务回调。
实现一个响应式对象
1) Observer这一块说的是将plain object给处理成reactive object的,即Vue如何拦截对象的get/set。
用 Object.defineProperty 拦截数据的 get/set 是 vue 的核心逻辑之一。
Observer.js
export class Observer { constructor(obj) { this.obj = obj; this.transform(obj); } // 将 obj 里的所有层级的 key 都用 defineProperty 重新定义一遍, 使之 reactive transform(obj) { const _this = this; for (let key in obj) { const value = obj[key]; makeItReactive(obj, key, value); } } } function makeItReactive(obj, key, val) { // 如果某个 key 对应的 val 是 object, 则重新迭代该 val, 使之 reactive if (isObject(val)) { const childObj = val; new Observer(childObj); } // 如果某个 key 对应的 val 不是 Object, 而是基础类型,我们则对这个 key 进行 defineProperty 定义 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { console.info(`get ${key}-${val}`) return val; }, set: (newVal) => { // 如果 newVal 和 val 相等,则不做任何操作(不执行渲染逻辑) if (newVal === val) { return; } // 如果 newVal 和 val 不相等,且因为 newVal 为 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替换掉 val, 再执行对应操作(渲染逻辑) else if (isObject(newVal)) { console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`); new Observer(newVal); val = newVal; } // 如果 newVal 和 val 不相等,且因为 newVal 为基础类型, 所以用 newVal 替换掉 val, 再执行对应操作(渲染逻辑) else if (!isObject(newVal)) { console.info(`set ${key} - ${val} - ${newVal} - newVal is Basic Value`); val = newVal; } } }) } function isObject(data) { if (typeof data === 'object' && data != 'null') { return true; } return false; }
index.js
import { Observer } from './source/Observer.js';
// 声明一个 obj,为 plain Object
const obj = {
a: {
aa: 1
},
b: 2,
}
// 将 obj 整体 reactive 化
new Observer(obj);
// 无输出
obj.b = 2;
// set b - 2 - 3 - newVal is Basic Value
obj.b = 3;
// set b - 3 - [object Object] - newVal is Object
obj.b = {
bb: 4
}
// get b-[object Object]
obj.b;
// get a-[object Object]
obj.a;
// get aa-1
obj.a.aa
// set aa - 1 - 3 - newVal is Basic Value
obj.a.aa = 3
Object.defineProperty和ES6的Proxy的区别:
Proxy 是 JavaScript 2015 的一个新特性。Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性,Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外Proxy支持代理数组的变化。
function render() { console.log('模拟视图的更新') } let obj = { name: '前端工匠', age: { age: 100 }, arr: [1, 2, 3] } let handler = { get(target, key) { // 如果取的值是对象就在对这个对象进行数据劫持 if (typeof target[key] == 'object' && target[key] !== null) { return new Proxy(target[key], handler) } return Reflect.get(target, key) }, set(target, key, value) { if (key === 'length') return true render() return Reflect.set(target, key, value) } } let proxy = new Proxy(obj, handler) proxy.age.name = '浪里行舟' // 支持新增属性 console.log(proxy.age.name) // 模拟视图的更新 浪里行舟 proxy.arr[0] = '浪里行舟' //支持数组的内容发生变化 console.log(proxy.arr) // 模拟视图的更新 ['浪里行舟', 2, 3 ] proxy.arr.length-- // 无效
2)Watcher
实现的伪代码:
// 传入 data 参数新建新建一个 vue 对象 const v = new Vue({ data: { a:1, b:2, } }); // watch data 里面某个 a 节点的变动了,如果变动,则执行 cb v.$watch('a',function(){ console.info('the value of a has been changed !'); }); // watch data 里面某个 b 节点的变动了,如果变动,则执行 cb v.$watch('b',function(){ console.info('the value of b has been changed !'); })
对某key的$watch方法新建了一个watcher实例,在Vue.js中
// 引入将上面中实现的 Observer import { Observer } from './Observer.js'; import { Watcher } from './Watcher.js'; export default class Vue { constructor(options) { // 在 this 上挂载一个公有变量 $options ,用来暂存所有参数 this.$options = options // 声明一个私有变量 _data ,用来暂存 data let data = this._data = this.$options.data // 在 this 上挂载所有 data 里的 key 值, 这些 key 值对应的 get/set 都被代理到 this._data 上对应的同名 key 值 Object.keys(data).forEach(key => this._proxy(key)); // 将 this._data 进行 reactive 化 new Observer(data, this) } // 对外暴露 $watch 的公有方法,可以对某个 this._data 里的 key 值创建一个 watcher 实例 $watch(expOrFn, cb) { // 注意,每一个 watcher 的实例化都依赖于 Vue 的实例化对象, 即 this new Watcher(this, expOrFn, cb) } // 将 this.keyName 的某个 key 值的 get/set 代理到 this._data.keyName 的具体实现 _proxy(key) { var self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter() { return self._data[key] }, set: function proxySetter(val) { self._data[key] = val } }) } }
Watch.js
// 引入Dep.js, 是什么我们待会再说 import { Dep } from './Dep.js'; export class Watcher { constructor(vm, expOrFn, cb) { this.cb = cb; this.vm = vm; this.expOrFn = expOrFn; // 初始化 watcher 时, vm._data[this.expOrFn] 对应的 val this.value = this.get(); } // 用于获取当前 vm._data 对应的 key = expOrFn 对应的 val 值 get() { Dep.target = this; const value = this.vm._data[this.expOrFn]; Dep.target = null; return value; } // 每次 vm._data 里对应的 expOrFn, 即 key 的 setter 被触发,都会调用 watcher 里对应的 update方法 update() { this.run(); } run() { // 这个 value 是 key 被 setter 调用之后的 newVal, 然后比较 this.value 和 newVal, 如果不相等,则替换 this.value 为 newVal, 并执行传入的cb. const value = this.get(); if (value !== this.value) { this.value = value; this.cb.call(this.vm); } } }
3)Dep收集依赖
对于watcher实例来说,实际上Vue 的 v-model, v-bind , {{ mustache }}, computed, watcher 等等本质上是分别对 data 里的某个 key 节点声明了一个 watcher 实例。
<input v-model="abc">
<span>{{ abc }}</span>
<p :data-key="abc"></p>
...
const v = new Vue({
data:{
abc: 111,
}
computed:{
cbd:function(){
return `${this.abc} after computed`;
}
watch:{
abc:function(val){
console.info(`${val} after watch`)
}
}
}
})
这里,Vue 一共声明了 4 个 watcher 实例来监听abc, 1个 watcher 实例来监听 cbd. 如果 abc 的值被更改,那么 4 个 abc - watcher 的实例会执行自身对应的特定回调(比如重新渲染dom,或者是打印信息等等)
不过,Vue是从何得知某个key对应了多少个watcher,并且当key对应的value发生变化后,又怎么来通知watcher来执行对应的不同回调。
export class Dep { constructor() { this.subs = []; } // 将 watcher 实例置入队列 addSub(sub) { this.subs.push(sub); } // 通知队列里的所有 watcher 实例,告知该 key 的 对应的 val 被改变 notify() { this.subs.forEach((sub, index, arr) => sub.update()); } } // Dep 类的的某个静态属性,用于指向某个特定的 watcher 实例. Dep.target = null observer.js import {Dep} from './dep' function makeItReactive(obj, key, val) { var dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { // 收集依赖! 如果该 key 被某个 watcher 实例依赖,则将该 watcher 实例置入该 key 对应的 dep 实例里 if(Dep.target){ dep.addSub(Dep.target) } return val }, set: (newVal) => { if (newVal === val) { return; } else if (isObject(newVal)) { new Observer(newVal); val = newVal; // 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 实例发送通知 dep.notify() } else if (!isObject(newVal)) { val = newVal; // 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 发送通知 dep.notify() } } }) }
watcher.js
import { Dep } from './Dep.js';
export class Watcher {
constructor(vm, expOrFn, cb) {
this.cb = cb;
this.vm = vm;
this.expOrFn = expOrFn;
this.value = this.get();
}
get() {
// 在实例化某个 watcher 的时候,会将Dep类的静态属性 Dep.target 指向这个 watcher 实例
Dep.target = this;
// 在这一步 this.vm._data[this.expOrFn] 调用了 data 里某个 key 的 getter, 然后 getter 判断类的静态属性 Dep.target 不为null, 而为 watcher 的实例, 从而把这个 watcher 实例添加到 这个 key 对应的 dep 实例里。 巧妙!
const value = this.vm._data[this.expOrFn];
// 重置类属性 Dep.target
Dep.target = null;
return value;
}
// 如果 data 里的某个 key 的 setter 被调用,则 key 会通知到 该 key 对应的 dep 实例, 该Dep实例, 该 dep 实例会调用所有 依赖于该 key 的 watcher 实例的 update 方法。
update() {
this.run();
}
run() {
const value = this.get();
if (value !== this.value) {
this.value = value;
// 执行 cb 回调
this.cb.call(this.vm);
}
}
}
————————————————
版权声明:本文为CSDN博主「开心大表哥」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a419419/article/details/80976218
浙公网安备 33010602011771号