一、Object.defineProperty()方法
彻底弄懂Vue2的数据更新原理,说白了,就是弄清楚MVVM是怎么实现的,手写相关实现代码,让相关知识不再处于“忽悠阶段”。

1.1 从MVVM模式说开去

1.2 侵入式和非侵入式

1.3 尤雨溪发现了“上帝的钥匙”
Object.defineProperty()是“上帝的钥匙”,可以做 数据劫持/ 数据代理;
Object.defineProperty()其实是利用JavaScript引擎赋予的功能, 检测对象属性变化;
仅有“上帝的钥匙” 不够, 还需要设计一套精密的系统;
1.4 Object.defineProperty()方法———定义及更新对象属性
Object.defineProperty()方法会直接在一个对象上定义一个新属性, 或者修改一个对象的现有属性, 并返回此对象;
思考:为什么要通过Object.defineProperty()来定义,或者,为什么javascript要提供这么一个方法,直接obj.a=3不香吗?因为这样,我们就可以为该对象设置一些额外隐藏的属性。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 3
});
Object.defineProperty(obj, 'b', {
value: 5
});
console.log(obj);
console.log(obj.a, obj.b);
1.5 Object.defineProperty()方法———设置额外隐藏的属性
Object.defineProperty()方法可以设置一些额外隐藏的属性;
比如:get、set、value、writable、enumerable、configurable
var obj = {};
Object.defineProperty(obj, 'a', {
value: 3,
writable: false
});
Object.defineProperty(obj, 'b', {
value: 5,
enumerable: false
});
console.log(a++);
for(var key in obj) {
console.log(k);
}
1.6 大名术语读的时候getter/setter,写的时候是get/set
getter get() 数据劫持,一旦某属性被访问了,就会无条件的执行其对应getter函数
setter set() 数据代理,一旦某属性的值被改变/设置了,就会无条件的执行其对应setter函数

var obj = {};
Object.defineProperty(obj, 'a', {
get() {
console.log('你试图访问obj的a属性');
},
set() {
console.log('你试图改变obj的a属性');
}
});
obj.a = 10;
console.log(obj.a);
二、defineReactive函数
为什么要定义一个defineReactive函数???
2.1 getter/setter需要变量周转才能工作
var obj = {};
var temp;
Object.defineProperty(obj, 'a', {
get() {
console.log('你试图访问obj的a属性');
return temp;
},
set(newValue) {
console.log('你试图改变obj的a属性', newValue);
temp = newValue;
}
});
console.log(obj.a);
obj.a++;
console.log(obj.a);
2.2 使用defineReactive函数不需要设置临时变量了, 而是用闭包
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('你试图访问'+ data + '的' + key + '属性');
return val;
},
set(newValue) {
console.log('你试图改变'+ data + '的' + key + '属性', newValue);
if (val === newValue) {
return;
}
val = newValue;
}
});
};
defineReactive(obj, 'a', 666)
console.log(obj.a);
obj.a = 10;
obj.a++;
console.log(obj.a);
三、递归侦测对象全部属性

import observe from './observe.js';
import Watcher from './Watcher.js';
var obj = {
a: {
m: {
n: 5
}
},
b: 10,
c: {
d: {
e: {
f: 6666
}
}
},
g: [22, 33, 44, 55]
};
observe(obj);
new Watcher(obj, 'a.m.n', (val) => {
console.log('★我是watcher,我在监控a.m.n', val);
});
obj.a.m.n = 88;
obj.g.push(66);
console.log(obj);
import Observer from './Observer.js';
export default function (value) {
if (typeof value != 'object') return;
var ob;
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
import { def } from './utils.js';
import defineReactive from './defineReactive.js';
import { arrayMethods } from './array.js';
import observe from './observe.js';
import Dep from './Dep.js';
export default class Observer {
constructor(value) {
this.dep = new Dep();
def(value, '__ob__', this, false);
if (Array.isArray(value)) {
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, l = arr.length; i < l; i++) {
observe(arr[i]);
}
}
};
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
});
};
import observe from './observe.js';
import Dep from './Dep.js';
export default function defineReactive(data, key, val) {
const dep = new Dep();
if (arguments.length == 2) {
val = data[key];
}
let childOb = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('你试图访问' + key + '属性');
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set(newValue) {
console.log('你试图改变' + key + '属性', newValue);
if (val === newValue) {
return;
}
val = newValue;
childOb = observe(newValue);
dep.notify();
}
});
};
四、数组的响应式处理
4.1 setPrototypeOf 与 Object.create区别
设置原型的目的是进行对象间的委托,可以让一个对象获得另一个对象的一些属性或者方法。关于设置一个对象的原型,JS提供了俩种方式。一种是通过setPrototypeOf,另一种是Object.create,通过了解二者的区别可以让我们能够根据情况去选择适合的方式。
用法
若存在A和B俩个函数,让A的原型指向B
setPrototypeOf
Object.setPrototypeOf(A.prototype,B.prototype)
Create
A.prototype = Object.create(B.prototype)
区别
使用Object.create,A.prototype将会指向一个空对象,空对象的原型属性指向B的prototytpe。所以我们不能再访问A的原有prototypoe中的属性。Object.create的使用方式也凸显了直接重新赋值。
使用Object.setPrototypeOf则会将A.prototype将会指向A原有的prototype,然后这个prototype的prototype再指向B的prototytpe。所以我们优先访问的A,然后再是B。
在进行俩个原型之间的委托时使用setPrototypeOf更好,Object.create更适和直接对一个无原生原型的对象快速进行委托。
4.2 改写数组的七个方法
到目前为止,以上的代码,访问数组时可以被检测到,但是改了数组,却没有得到检测,那么,如何实现对于数组的响应式,实际上,vue底层改写了数组的七大方法以实现响应式,七大方法为:push pop shift unshift splice sort reverse
这七个方法都是Array.prototype上的方法,现在我们就是要备份这个七个方法,在此基础上再添加一些新方法、或者重写。
在Observer.js里使用Object.setPrototypeOf(value,arrayMethods)
在array.js里使用arrayMethods = Object.create(Array.prototype)

import { def } from './utils.js';
const arrayPrototype = Array.prototype;
export const arrayMethods = Object.create(arrayPrototype);
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsNeedChange.forEach(methodName => {
const original = arrayPrototype[methodName];
def(arrayMethods, methodName, function () {
const result = original.apply(this, arguments);
const args = [...arguments];
const ob = this.__ob__;
let inserted = [];
switch (methodName) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) {
ob.observeArray(inserted);
}
console.log('啦啦啦');
ob.dep.notify();
return result;
}, false);
});
五、依赖收集
5.1 什么是依赖?
需要用到数据的地方, 称为依赖
Vue1.x, 细粒度依赖, 用到数据的DOM都是依赖;
Vue2.x, 中等粒度依赖, 用到数据的组件是依赖;
在getter中收集依赖, 在setter中触发依赖
5.2 Dep类和Watcher类
把依赖收集的代码封装成一个Dep类, 它专门用来管理依赖, 每个Observer的实例,成员中都有一个Dep的实例;
Watcher是一个中介, 数据发生变化时通过Watcher中转, 通知组件;

依赖就是Watcher。 只有Watcher触发的getter才会收集依赖, 哪个Watcher触发了getter, 就把哪个Watcher收集到Dep中。
Dep使用发布订阅模式, 当数据发生变化时, 会循环依赖列表, 把所有的Watcher都通知一遍。
代码实现的巧妙之处: Watcher把自己设置到全局的一个指定位置,然后读取数据, 因为读取了数据, 所以会触发这个数据的getter。 在getter中就能得到当前正在读取数据的Watcher, 并把这个Watcher收集到Dep中。

var uid = 0;
export default class Dep {
constructor() {
console.log('我是DEP类的构造器');
this.id = uid++;
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
this.addSub(Dep.target);
}
}
notify() {
console.log('我是notify');
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
};
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 = 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;
};
}