defineProperty
定義基礎數據function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
return val;
},
set: function(newVal) {
if (val === newval) {
return;
}
val = newVal;
}
});
}
複製代碼
在getter中觸發依賴收集,在setter中觸發依賴通知。javascript
function defineReactive(data, key, val) {
const dep = [];
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
//依賴收集
dep.push(window.target);
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
//依賴通知
for (let index = 0; index < dep.length; index++) {
dep[index].update(newVal, val);
}
val = newVal;
}
});
}
複製代碼
Dep對象將負責收集、保存依賴,在屬性變化時經過執行依賴的update()方法通知依賴。java
class Dep {
constructor() {
this.Deps = [];
}
addDep() {
if (window.target) {
this.Deps.push(window.target);
}
}
notify(newVal, val) {
for (let index = 0; index < this.Deps.length; index++) {
this.Deps[index].update(newVal, val);
}
}
}
複製代碼
這裏的window.target不只是依賴對象與Dep對象溝通的紐帶,同時也能夠經過判斷window.target值在後續讀取該屬性避免屢次重複綁定依賴。函數
defineReactive
function defineReactive(data, key, val) {
let deps = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
//依賴收集
deps.addDep();
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
//依賴通知
deps.notify();
val = newVal;
}
});
}
複製代碼
最終defineReactive函數將實現對傳入對象屬性的轉換,使其成爲響應式數據。然而defineReactive函數是以對象屬性爲單位進行轉換,咱們更但願傳入對象直接將其全部屬性轉換。ui
class Observer {
constructor(value) {
this.value = value;
if (!Array.isArray(value)) {
this.travel(value);
}
}
travel(value) {
const keys = Object.keys(value);
for (let index = 0; index < keys.length; index++) {
defineReactive(value, keys[index], value[keys[index]]);
}
}
}
複製代碼
什麼是依賴?this
依賴是一個對象,它能夠獲取、保存綁定的屬性值,該屬性值發生變化時它的update方法將被觸發,從而執行保存值的更新以及綁定的回調函數的執行。spa
依賴的用處?3d
經過依賴對象使外部代碼能夠與某屬性產生關聯,監聽其變化並綁定回調函數。code
class Watcher {
constructor(obj, proOrObj, fn) {
this.obj = obj;
this.getter = this.parsePath(proOrObj);
this.fn = fn;
this.val = this.get();
}
get() {
if (!this.val) {
window.target = this;
}
let val = this.getter.call(this.obj, this.obj);
window.target = undefined;
return val;
}
update() {
let oldVal = this.val;
this.val = this.get();
this.fn(this.val, oldVal);
}
parsePath(path) {
const pros = path.split(".");
return function(obj) {
for (let index = 0; index < pros.length; index++) {
if (!obj) {
return;
}
obj = obj[pros[index]];
}
return obj;
};
}
}
複製代碼
if (!this.val) {window.target=this;}
cdn
咱們須要將Watcher實例依賴添加到對象屬性中,而在Dep對象中添加依賴都是經過window.target
,因此咱們須要將Watcher實例賦值給window.target
,而添加依賴只需在初次讀取完成,後續若屢次讀取該屬性將重複添加相同依賴。server
let val=this.getter.call(this.obj,this.obj);
添加依賴是在屬性的getter中執行因此咱們須要讀取一次該屬性,同時保存該屬性值。這裏的getter是parsePath()方法返回的一個函數,該函數接收一個對象經過遞歸解析該對象的嵌套屬性路徑(如:proOrObj = "a.b.c"
)並返回該屬性值。
在屬性的setter中咱們經過執行依賴的update()方法通知依賴,這裏咱們將更新屬性值而且執行回調函數。
const man = {};
defineReactive(man, "age", 1);
const watcher = new Watcher(man, "age", function(newVal, oldVal) {
console.log("age變化:新數據" + newVal + "老數據" + oldVal);
});
man.age = 2; //age變化:新數據2老數據1
man.age = 3; //age變化:新數據3老數據2
man.age = 4; //age變化:新數據4老數據3
man.age = 5; //age變化:新數據5老數據4
複製代碼