vue源码之数据响应式原理
使用Object.defineProperty
var obj = {}
Object.defineProperty(obj, 'a', {
get() {
console.log('你试图访问obj的a属性');
return 7;
},
set(newVal) {
console.log('你试图修改obj的a属性', newVal);
}
})
console.log(obj.a); // 7
obj.a = 10;
console.log(obj.a); // 7
// getter和setter 需要一个临时变量来周转这个get和set值,写法如下:
var obj = {}
var temp;
Object.defineProperty(obj, 'a', {
get() {
console.log('你试图访问obj的a属性');
return temp;
},
set(newVal) {
console.log('你试图修改obj的a属性', newVal);
temp = newVal
}
})
console.log(obj.a); // undefined
obj.a = 10;
console.log(obj.a); // 10
// 封装 defineReactive 闭包环境 内外两个环境
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以配置 delete
configurable: true,
// getter
get() {
console.log('你试图访问obj的' + key + '属性');
return val;
},
// setter
set(newVal) {
console.log('你试图修改obj的' + key + '属性', newVal);
if (val == newVal) {
return;
}
val = newVal
}
})
}
var obj = {}
defineReactive(obj, 'a', 10);
console.log(obj.a); //10
obj.a = 11;
console.log(obj.a); // 11
递归侦测对象全部属性
新建defineReactive.js文件
import observe from './observe';
// 封装 defineReactive 闭包环境 内外两个环境
export default function defineReactive(data, key, val) {
console.log('我是defineReactive', data, key);
if (arguments.length == 2) {
val = data[key]
}
// 子元素要进行observe 至此形成了递归,这个递归不是函数自己,而是多个循环调用
let childrenOb = observe(val);
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以配置 delete
configurable: true,
// getter
get() {
console.log('你试图访问' + key + '属性');
return val;
},
// setter
set(newVal) {
console.log('你试图修改' + key + '属性', newVal);
if (val == newVal) {
return;
}
val = newVal;
// 当设置了新值,这个新值也要被observe
childrenOb = observe(newVal);
}
})
}
新建Observer.js文件
import { def } from './utils';
import defineReactive from './defineReactive';
import { arrayMethods } from './array';
import observe from './observe';
// Observer类 将一个正常的object转换为每个层级的属性都是被响应式(可以被侦测的)object
export default class Observer {
constructor(value) {
// 给实例 this 这里的this 构造函数的this不是表示类本身,而是表示实例
// 给实例添加一个__ob__属性,值是这次的 new 的实例
def(value, '__ob__', this, false);
console.log('我是Observer', value);
// 检查是否为数组还是对象
if (Array.isArray(value)) {
// 如果是数组, 将数组的原型指向 arrayMethods
Object.setPrototypeOf(value, arrayMethods);
// 让数组变的observe
this.ObserveArray(value)
} else {
this.walk(value);
}
}
// 遍历
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
ObserveArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项进行observe
observe(arr[i])
}
}
}
新建observe.js文件
import { def } from './utils';
import defineReactive from './defineReactive';
import observe from './observe';
// Observer类 将一个正常的object转换为每个层级的属性都是被响应式(可以被侦测的)object
export default class Observer {
constructor(value) {
// 给实例 this 这里的this 构造函数的this不是表示类本身,而是表示实例
// 给实例添加一个__ob__属性,值是这次的 new 的实例
def(value, '__ob__', this, false);
console.log('我是Observer', value);
this.walk(value);
}
// 遍历
walk(value){
for(let k in value){
defineReactive(value,k)
}
}
}
新建utils.js文件
// utils 用于遍历的工具函数
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
index.js文件
import defineReactive from './defineReactive';
import Observer from './Observer';
import observe from './observe';
var obj = {
a: {
b: {
m: {
n: 7
}
}
},
c:1
};
observe(obj)
// obj.c = 10;
console.log(obj.a.b.m.n);
数组的响应式处理
数组响应式的方法有七个改写的 :pup 、pop、shift、unshift、splice、 reverse 、 sort ;
import { def } from './utils';
// 数组响应式的方法有七个改写的 :pup 、pop、shift、unshift、splice、 reverse 、 sort ;
// 改写 七 个方法 基于 Array.prototype
// 问:数组的响应式原理怎么实现的 ?
// 答:以 Array.prototype为原型,创建了arrayMethods 对象,然后Object.setPrototypeOf(o,arrayMethods)
// 强制让数组 的 __proto__指向了Array.prototype 这样可以触发新创建的函数
const arrayPrototype = Array.prototype;
// 以 Array.prototype为原型,创建了arrayMethods 对象 并暴露
export const arrayMethods = Object.create(arrayPrototype);
// console.log(arrayPrototype);
// 要被改写的7个方法
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'reverse',
'sort'
];
methodsNeedChange.forEach(methodName => {
// console.log(11);
// 备份原的方法
const original = arrayPrototype[methodName];
// 定义新的方法
def(arrayMethods, methodName, function () {
console.log('数组方法');
// 恢复原来的功能
const result = original.apply(this, arguments);
// 把类数组对象变为数组
const args = [...arguments];
const ob = this.__ob__;
// 有三种方法push 、unshift、splice 能够插入新项,现在要把插入的也要变为observe
let inserted = [];
switch(methodName){
case 'push' :
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.silce(2)
break;
}
// 判断有没有要插入的项,让新项也变为响应的
if(inserted){
ob.ObserveArray(inserted)
}
return result;
}, false)
})
index.js
import observe from './observe';
var obj = {
a: {
b: {
m: {
n: 7
}
}
},
c:1,
g:[11,22,33,44,55]
};
observe(obj)
obj.g.push(66);
console.log(obj.g);
依赖收集
用到数据的地方,叫做依赖
1、vue1.x ,细粒度依赖,用到数据的DOM都是依赖;
2、vue2.x ,中等细粒度依赖,用到数据的组件都是依赖;
在getter中收集依赖,在setter中触发依赖;
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() {
// 进入依赖收集,让全局的Eep.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;
}
}
Dep.js
var uid = 0;
export default class Dep {
constructor() {
console.log('我是Dep类的构造器');
this.id = uid++;
// 用数组存储自己的订阅者,subs是 subscribes 订阅者的意思
// 这个数组里面放的是 wather实例 是发布者
this.subs = [];
}
// 添加订阅
addSubs(sub) {
this.subs.push(sub)
}
// 移除更新
// 添加依赖
depend(){
// Dep.target 自己指定的全局的位置,用window.target 也可以,只要是唯一的就可以
if(Dep.target){
this.addSubs(Dep.target)
}
}
// 通知更新
notify() {
// 浅克隆
const subs = this.subs.slice()
// 遍历
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
};
defineReactive.js 引入 Dep new Dep()
import observe from './observe';
import Dep from './Dep';
// 封装 defineReactive 闭包环境 内外两个环境
export default function defineReactive(data, key, val) {
const dep = new Dep();
console.log('我是defineReactive', data, key);
if (arguments.length == 2) {
val = data[key]
}
// 子元素要进行observe 至此形成了递归,这个递归不是函数自己,而是多个类循环调用
let childrenOb = observe(val);
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以配置 delete
configurable: true,
// getter
get() {
console.log('你试图访问' + key + '属性');
// 如果处于依赖收集阶段
if(Dep.target){
dep.depend();
if(childrenOb){
childrenOb.dep.depend()
}
}
return val;
},
// setter
set(newVal) {
console.log('你试图修改' + key + '属性', newVal);
if (val == newVal) {
return;
}
val = newVal;
// 当设置了新值,这个新值也要被observe
childrenOb = observe(newVal);
// 发布订阅者 通知dep
dep.notify();
}
})
}
Observer.js 引入 Dep new Dep()
import { def } from './utils';
import defineReactive from './defineReactive';
import {arrayMethods} from './array';
import observe from './observe';
import Dep from './Dep';
// Observer类 将一个正常的object转换为每个层级的属性都是被响应式(可以被侦测的)object
export default class Observer {
constructor(value) {
// 每个 Observer 的实例身上都有一个 dep
this.dep = new Dep();
// 给实例 this 这里的this 构造函数的this不是表示类本身,而是表示实例
// 给实例添加一个__ob__属性,值是这次的 new 的实例
def(value, '__ob__', this, false);
console.log('我是Observer', value);
// 检查是否为数组还是对象
if (Array.isArray(value)) {
// 如果是数组, 将数组的原型指向 arrayMethods
Object.setPrototypeOf(value, arrayMethods);
// 让数组变的observe
this.ObserveArray(value)
} else {
this.walk(value);
}
}
// 遍历
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
ObserveArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项进行observe
observe(arr[i])
}
}
}
index.js触发Watcher
import observe from './observe';
import Watcher from './Watcher';
var obj = {
a: {
b: {
m: {
n: 7
}
}
},
c: 1,
g: [11, 22, 33, 44, 55]
};
observe(obj)
// obj.c++;
// obj.a.b = 10;
// console.log(obj.a.b.m.n);
// obj.g.push(66);
// console.log(obj.g);
// console.log(obj);
new Watcher(obj, 'a.b.m.n', (val) => {
console.log('监控a.b.m.n', val);
});
obj.a.b.m.n = 100;