MVVM
所谓响应式,其实本身就是对 MVVM (Modal View ViewModal)的理解
Vue 做了一层中间的 ViewModal,让视图上的改变能反映到 data 中, data 中的改变能反映到视图上。
ViewModal就是视图和数据的一个桥梁。
如同样是num + 1
在Vue 中,这个桥梁是你看不见的,因为 Vue 帮你完成了视图和数据的变化传递。
而 React 就是侵入式的,因为要显式地声明 setState ,通过它,来设置变量的同时,设置视图的改变。
Vue 的神奇之处,不需要我们手动地显示调用 setState ,也就是这个桥梁, Vue 已经帮我们桥接上了。
要让 data 改变的同时,视图也发生改变,所以,问题的所在,就是我们需要监听,什么时候,这个变量发生了变量。 ES5 中,就有一个特性,可以做到对于数据的劫持(监听)。
它就是 Object.defineProperty 。
Object.defineProperty方法
var obj = {};
Object.defineProperty(obj, 'a', {
get() {
console.log('a属性');
return 3
},
set() {
console.log('改变a的属性')
}
})
Object.defineProperty(obj, 'b', {
value: 4
})
obj.a = 7;
console.log(obj) // 此时发现a的值仍然没有变化 需要一个变量值来周转
getter/setter需要变量周转
var obj = {};
var temp;
Object.defineProperty(obj, 'a', {
get() {
console.log('a属性');
return temp
},
set(val) {
temp = val
console.log('改变a的属性')
}
})
Object.defineProperty(obj, 'b', {
value: 4
})
obj.a = 7;
console.log(obj)
defineReactive函数封装
let obj = {};
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可被配置,如delete
configurable: true,
get() {
console.log('访问' + key + '属性');
return val
},
set(newVal) {
if (val === newVal) return;
console.log('改变' + key + '的属性')
val = newVal
}
})
}
defineReactive(obj, 'a', 3);
defineReactive(obj, 'b', 4);
console.log(obj.a)
console.log(obj.b)
obj.b++;
console.log(obj.b)
递归侦测全部属性
当对象层级比较深,defineReactive方法不能直接改变某个层级,需要递归侦测每个属性
代码目录结构
utils.js
export const def = function(obj,key,value,enumerable) {
Object.defineProperty(obj,key,{
value,
enumerable,
writable: true,
configurable: true,
})
}
defineReactive.js
import observe from "./observe";
export default function defineReactive(data, key, val) {
if (arguments.length === 2) {
val = data[key];
}
// 子元素observe,递归。这个递归不是自己调用自己,而是多个函数,类循坏调用
let childOb = observe(val);
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可被配置,如delete
configurable: true,
get() {
console.log('访问' + key + '属性');
return val
},
set(newVal) {
if (val === newVal) return;
console.log('改变' + key + '的属性')
val = newVal;
// 设置的值也需要observe
childOb = observe(newVal)
}
})
}
Observer.js
import { def } from './utils';
import defineReactive from './defineReactive';
// 将一个正常的object转换成每个层级的属性都是响应式的,可以被zhence的object
export default class Observer {
constructor(value) {
def(value,'__ob__',this,false);
this.walk(value)
}
walk(value) {
for(let k in value) {
defineReactive(value,k)
}
}
}
observe.js
import Observer from "./Observer";
export default function (value) {
if (typeof value != 'object') return;
let ob;
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
index.js
import observe from './observe'
let obj = {
a: {
b: {
c: {
d: 5
}
}
},
e: 10,
f: {
g: {
h: 20
}
}
}
observe(obj);
obj.a.b.c = 1000;
console.log(obj.a.b.c) // 1000
数组的响应式处理
数组的七个方法被改写
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
vue中数组的变化侦测(响应式)如何实现?
以Array.prototype为原型,创建了arrayMethods对象,使用es6中Object.setPrototypeOf(o,arrayMethods)方法强制将数组的__proto__指向arrayMethods(o.__proto__ = arrayMethods)触发新定义的函数
array.js
import { def } from "./utils";
// 得到Array.prototype
const arrayPrototype = Array.prototype;
// 以Array.prototype为原型,创建了arrayMethods对象
export const arrayMethods = Object.create(arrayPrototype);
// 要被改写的七个数组方法
const methodsNeedChange = [
"push",
"pop",
"shift",
"unshift",
"sort",
"splice",
"reverse",
];
methodsNeedChange.forEach((v) => {
// 备份原来的方法
const original = arrayPrototype[v];
// 定义新方法
def(
arrayMethods,
v,
function () {
const result = original.apply(this, arguments);
const args = [...arguments];
// 数组已经添加了__ob__,
const ob = this.__ob__;
// 数组三种方法中push,unshift,splice能够插入新项,插入的新项也要oberve
let inserted = [];
switch (v) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
// 让插入的项也变成响应式的
if (inserted) {
ob.observeArray(inserted);
}
return result;
},
false
);
});
Observer.js
import { def } from "./utils";
import defineReactive from "./defineReactive";
import { arrayMethods } from "./array";
import observe from "./observe";
// 将一个正常的object转换成每个层级的属性都是响应式的,可以被侦测的object
export default class Observer {
constructor(value) {
def(value, "__ob__", this, false);
// 检测是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,将数组的原型指向arrayMethods
Object.setPrototypeOf(value, arrayMethods);
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(value) {
for (let k in value) {
defineReactive(value, k);
}
}
observeArray(arr) {
for (let i = 0; (length = arr.length), i < length; i++) {
// 每个都进行observe
observe(arr[i]);
}
}
}
index.js
import observe from './observe'
let obj = {
a: {
b: {
c: {
d: 5
}
}
},
e: 10,
f: {
g: {
h: 20
}
},
k:[1,2,3]
}
observe(obj);
obj.a.b.c = 1000;
console.log(obj.a.b.c)
obj.k.splice(2,1,88,99)
console.log(obj.k)
依赖收集
需要用到数据的地方,称为依赖
在getter中收集依赖,在setter中触发依赖
Dep类和Watcher类
把依赖收集的代码封装成一个Dep类,转么管理依赖,每个Observe的实例,成员都有一个Dep实例 Watcher是一个中介,数据发生变化时通过Watcher中转,通知组件
Dep 和 Watcher 在设计模式中,就是发布-订阅者的模式。
Dep
Dep 发布者,它的工作就是依赖管理,它会有多个订阅者。每个变量有属于自己的 Dep ,每个变量所在的依赖位置不一样,所以他们的订阅者也不一样。然后在变量更新之后,就去通知所有的订阅者(Watcher),变量更新,触发视图更新;
Watcher
Watcher 订阅者,它接受 Dep 发过来的更新通知之后,就去执行视图更新了。本质是 watch 监听器,变量改变之后,执行一个回调函数。
Dep.js
var uid = 0;
export default class Dep {
constructor() {
console.log('我是DEP类的构造器');
this.id = uid++;
// 用数组存储自己的订阅者。subs是英语subscribes订阅者的意思。
// 这个数组里面放的是Watcher的实例
this.subs = [];
}
// 添加订阅
addSub(sub) {
this.subs.push(sub);
}
// 添加依赖
depend() {
// Dep.target就是一个我们自己指定的全局的位置
if (Dep.target) {
this.addSub(Dep.target);
}
}
// 通知更新
notify() {
// 浅克隆一份
const subs = this.subs.slice();
// 遍历
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
};
Watcher.js
import Dep from "./Dep";
var uid = 0;
export default class Watcher {
constructor(target, expression, callback) {
console.log('我是Watcher类的构造器');
this.id = uid++;
this.target = target;
this.getter = parsePath(expression);
this.callback = callback;
this.value = this.get();
}
update() {
this.run();
}
get() {
// 进入依赖收集阶段。让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段
Dep.target = this;
const obj = this.target;
var value;
// 只要能找,就一直找
try {
value = this.getter(obj);
} finally {
Dep.target = null;
}
return value;
}
run() {
this.getAndInvoke(this.callback);
}
getAndInvoke(cb) {
const value = this.get();
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value;
this.value = value;
cb.call(this.target, value, oldValue);
}
}
};
function parsePath(str) {
var segments = str.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
};
}
Observer.js
import { def } from "./utils";
import defineReactive from "./defineReactive";
import { arrayMethods } from "./array";
import observe from "./observe";
import Dep from './Dep'
// 将一个正常的object转换成每个层级的属性都是响应式的,可以被侦测的object
export default class Observer {
constructor(value) {
this.dep = new Dep();
def(value, "__ob__", this, false);
// 检测是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,将数组的原型指向arrayMethods
Object.setPrototypeOf(value, arrayMethods);
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(value) {
for (let k in value) {
defineReactive(value, k);
}
}
observeArray(arr) {
for (let i = 0; (length = arr.length), i < length; i++) {
// 每个都进行observe
observe(arr[i]);
}
}
}
defineReactive.js
import observe from "./observe";
import Dep from "./Dep";
export default function defineReactive(data, key, val) {
let dep = new Dep();
if (arguments.length === 2) {
val = data[key];
}
// 子元素observe,递归。这个递归不是自己调用自己,而是多个函数,类循坏调用
let childOb = observe(val);
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可被配置,如delete
configurable: true,
get() {
console.log("访问" + key + "属性");
// Dep.target 是我们弄的唯一标识,当有这个标识的时候,添加依赖
if (Dep.target) {
// 添加依赖
dep.depend();
// 如果有子属性,也要将它加入依赖
if (childOb) {
// 给子属性添加依赖
childOb.dep.depend();
}
}
return val;
},
set(newVal) {
if (val === newVal) return;
console.log("改变" + key + "的属性");
val = newVal;
// 设置的值也需要observe
childOb = observe(newVal);
// notify val=newVal 之后,不然在 callback 回调中一直是旧值
dep.notify();
},
});
}
index.js
import observe from './observe'
import Watcher from './Watcher'
let obj = {
a: {
b: {
c: {
d: 5
}
}
},
e: 10,
f: {
g: {
h: 20
}
},
k:[1,2,3]
}
observe(obj);
console.log(obj.a.b.c)
new Watcher(obj,'a.b.c', (val) => {
console.log('val----------',val)
})
obj.a.b.c = 1000;
console.log('obj.a.b.c----------',obj.a.b.c)
总结
当创建Vue实例时,vue会遍历data选项的属性,利用Object.defineProperty为属性添加getter和setter对数据的读取进行劫持(getter用来依赖收集,setter用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
每个组件实例会有相应的watcher实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有computed watcher,user watcher实例),之后依赖项被改动时,setter方法会通知依赖与此data的watcher实例重新计算(派发更新),从而使它关联的组件重新渲染