知識點鋪墊
var obj = {};
//getter和setter需要變量中轉(zhuǎn)才能使用,所以不合適
var temp;
Object.defineProperty(obj, 'a', {
//getter
get() {
return temp;
},
//setter
set(val) {
temp = val;
}
// value: 3,//value和get不能同時存在
// writable:true,
// enumerable:true
});
Object.defineProperty(obj, 'b', {
value: 5,
//是否可被枚舉
// enumerable:false
});
// obj.a++;
// for (var item in obj) {
// console.log(item);//只輸出a
// }
obj.a = 10;
console.log(obj);
- 閉包形式演化
避免單獨的temp變量
var obj = {};
//通過閉包,避免單獨變量temp
function defindReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable:true,
configurable:true,//可配置,比如能被外部delete
get() {
return val;
},
set(newval) {
if (newval === val) {
return;
}
val = newval;
}
});
}
defindReactive(obj,'a',10);
obj.a++;
console.log(obj);
殊理
實際上整體殊理邏輯要分門別類:例如,arr和object分開討論;
同時整體看起來好像是單向,但看對象的話實際上observe->Observer-> defineReactive,如果有set操作則又進入observe其中如果defineReactive如果知道對象有子對象,會繼續(xù)從observe開始循環(huán);即每次都是處理一層而已
至于Dep(依賴收集)和Watcher是收集依賴和觸發(fā)更新的,殊理邏輯可以最后再看

1.png
什么是依賴
- 需要用的數(shù)據(jù)的地方,稱為依賴
- Vue2.x,中等依賴,用到數(shù)據(jù)的組件是依賴
- 在getter中收集依賴,在setter中觸發(fā)依賴
Dep類和Watcher類
- 依賴就是Watcher,只有在Watcher觸發(fā)getter才會收集依賴,哪個Watcher觸發(fā)了getter,就把哪個Watcher收集到Dep中
- Dep使用發(fā)布訂閱模式,當(dāng)數(shù)據(jù)發(fā)生變化時候,會循環(huán)依賴列表,把所有Watcher都通知一遍
- 代碼實現(xiàn)的巧妙之處:Watcher把自己設(shè)置到全局的一個指定位置,然后讀取數(shù)據(jù),因為讀取了數(shù)據(jù),所以會觸發(fā)這個數(shù)據(jù)的getter。在getter中就能得到當(dāng)前正在讀取數(shù)據(jù)的Watcher,并把這個Watcher收集到Dep中。

4.png
代碼
- index.js
import { observe } from './observe.js'
import Watcher from './Watcher.js'
var obj = {
a: {
m: {
n: 5
}
},
b: 10,
g:[2,3,4]
};
observe(obj);
// obj.b++;
obj.g.push(5);
console.log(obj);
new Watcher(obj,'a.m.n',val=>{
console.log(1111,val);
})
- observe.js
import Observer from './Observer.js'
//創(chuàng)建observer函數(shù),起輔助判別的作用:看obj身上有沒有__ob__
export const observe = function (value) {
//如果value不是對象,什么也不做;因為直接數(shù)值時候沒有必要
if (typeof value !== 'object') {
return
}
var ob;
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;//不希望和常見屬性重名
} else {
ob = new Observer(value);
}
return ob;
}
- Observer.js
import { def } from './utils.js';
import defineReactive from './defineReactive.js'
import { arrayMethods } from './arr.js'
import { observe } from './observe.js'
import Dep from './Dep.js'
//Observer 將一個正常的object轉(zhuǎn)換為每個層級的屬性都是響應(yīng)式(可以被偵測)的object
export default class Observer {
constructor(value) {
//每個Observer的實例,成員中都有一個Dep的實例
this.dep = new Dep();
def(value, '__ob__', this, false);
//檢查它是數(shù)組還是對象
if (Array.isArray(value)) {
//如果是數(shù)組,將這個數(shù)組的原型,指向arrayMethods
Object.setPrototypeOf(value, arrayMethods);
//讓這個數(shù)組變得observe
this.observeArray(value);
} else {
this.walk(value);
}
}
//遍歷
walk(value) {
for (let key in value) {
defineReactive(value, key);
}
}
//數(shù)組特殊遍歷
observeArray(arr) {
for (let i = 0; i < arr.length; i++) {
observe(arr[i]);
}
}
};
- defineReactive.js
import { observe } from './observe.js'
import Dep from './Dep.js'
export default function defindReactive(data, key, val) {
//此處dep是閉包中的
const dep = new Dep();
if (arguments.length == 2) {
val = data[key];
}
//子元素要進行observe,至此形成了遞歸;這個遞歸不是函數(shù)調(diào)用自己,是多個函數(shù)形成循環(huán)調(diào)用
let childOb = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
//如果現(xiàn)在處于依賴收集階段
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set(newval) {
if (newval === val) {
return;
}
val = newval;
//當(dāng)設(shè)置了新值,新值也要被observe,不然不是響應(yīng)式的
childOb = observe(newval);
//發(fā)布訂閱模式,通知dep
dep.notify();
}
});
}
- arr.js
import { def } from './utils.js';
const arrayPrototype = Array.prototype;
//以Array.prototype為原型,創(chuàng)建arrayMethods對象
// 針對數(shù)組形成響應(yīng)式,需要改寫數(shù)組的七種方法
// push
// pop
// shift
// unshift
// splice
// sort
// reverse
export const arrayMethods = Object.create(arrayPrototype);
// Object.setPrototypeOf(o,arrayMethods);
// o.__proto__=arrayMethods;
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'];
methodsNeedChange.forEach(methodName => {
//備份原來的方法
const original = arrayPrototype[methodName];
//定義新的方法
def(arrayMethods, methodName, function () {
//把類數(shù)組對象變?yōu)閿?shù)組,不然下面的slice無法調(diào)用
const args=[...arguments];
//把這個數(shù)組身上的__ob__取出來,__ob__已經(jīng)被添加了,為什么已經(jīng)被添加了?因為數(shù)組肯定不是最高層
//比如obj.g屬性是數(shù)組,obj不能是數(shù)組,第一次遍歷obj這個對象的第一層的時候,已經(jīng)給g屬性,添加了__ob__屬性
const ob = this.__ob__;
// 'push',
// 'unshift',
// 'splice',
//這三者涉及插入新項,所以也需要變成observe
let inserted = [];
switch (methodName) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
//判斷有沒有需要插入新項,讓新項變成響應(yīng)的
if (inserted) {
ob.observeArray(inserted);
}
//此處this是被數(shù)組打點調(diào)用的,而且不能用箭頭函數(shù)
original.apply(this, arguments);
ob.dep.notify();
}, false);
})
- Dep.js
var uid = 0;
export default class Dep {
constructor() {
this.id = uid++;
//用數(shù)組存儲自己的訂閱者.放的是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; i < subs.length; i++) {
subs[i].update();
}
}
}
- Watcher.js
import Dep from "./Dep";
var uid = 0;
export default class Watcher {
constructor(target, expression, callback) {
this.id = uid++;
this.target = target;
this.gettr = parsePath(expression);//'a.b.c.d'
this.callback = callback;
this.value = this.get();
}
update() {
this.getAndInvoke(this.callback);
}
get() {
//進入依賴收集階段,讓全局的Dep,target設(shè)置為watcher本身,那么就是進入依賴收集階段
Dep.target = this;
const obj = this.target;
var value;
try {
value = this.gettr(obj);
} finally {
//收集完畢,退出,其他watcher需要
Dep.target = null;
}
return value;
}
//獲取數(shù)據(jù)并更新
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) {
//作用是:把'a.b.c.d'這種轉(zhuǎn)換成a{b:{c:{d:{}}}}
var segments = str.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) {
return;
}
obj = obj[segments[i]];
}
return obj;
}
}