本文是受當面試官問你Vue響應式原理,你能夠這麼回答他
啓發自行繪製的vue動態響應原理圖解 畫風詭異,僅表明我的理解若是有不當的地方,望請斧正。vue
官方大圖
面試
JserWang的原理代碼,爲了看懂我自行添加了好多註釋與console。bash
//3.
const Observer = function(data) {
// 循環修改成每一個屬性添加get set
for (let key in data) {
console.log("給value的沒一個key設置一個收集器");
defineReactive(data, key);
}
}
//4.
const defineReactive = function(obj, key) {
// 局部變量dep,用於get set內部調用
console.log("建立收集器***********");
const dep = new Dep();
// 獲取當前值
let val = obj[key];
console.log("observe 開始重寫data每一個屬性的get/set");
Object.defineProperty(obj, key, {
// 設置當前描述屬性爲可被循環
enumerable: true,
// 設置當前描述屬性可被修改
configurable: true,
get() {
console.log('observe get觸發');
// 調用依賴收集器中的addSub,用於收集當前屬性與Watcher中的依賴關係
console.log("observe dep.depend收集當前屬性和watcher的依賴關係");
console.log("當^^^^^^^^^^^^^^^^^^^^^^^^前屬性:", key);
dep.depend();
return val;
},
set(newVal) {
if (newVal === val) {
console.log("observe set 設置屬性值未變化");
return;
}
val = newVal;
// 當值發生變動時,通知依賴收集器,更新每一個須要更新的Watcher,
// 這裏每一個須要更新經過什麼判定?dep.subs
console.log("observe 設置屬性值變化通知依賴收集器更新watcher");
dep.notify();
}
});
}
const observe = function(data) {
return new Observer(data);
}
//1.
const Vue = function(options) {
const self = this;
self.a = 1;
self.b = 1;
// 將data賦值給this._data,源碼這部分用的Proxy因此咱們用最簡單的方式臨時實現
if (options && typeof options.data === 'function') {
console.log("Vue 將傳入配綁定給新建的vue對象:", this);
this._data = options.data.apply(this);
}
// 掛載函數
this.mount = function() {
let tmp = self.a;
self.a += 1;
console.log("Vue 掛載watcher實例到當前vue對象", tmp);
new Watcher(self, self.render);
}
// 渲染函數
this.render = function() {
let tmp = self.b;
self.b += 1;
console.log("Vue 觸發渲染vue對象", tmp);
console.log('Vue 看該屬性是否被組件引用,引用則從新渲染');
// with(self) {
// _data.text;//獲取當前的data中的屬性
// _data.a;
// _data.b;
// }
console.log(self._data.text);
console.log(self._data.text1);
console.log(self._data.text2)
}
// 監聽this._data
//2.
console.log("Vue 設置觀察對象, 重寫set、get");
observe(this._data);
}
const Watcher = function(vm, fn) {
const self = this;
//保存傳入的data對象
this.vm = vm;
// 將當前Dep.target指向當前watcher
console.log("Watcher 將當前Dep.target指向當前watcher");
console.log("-----------------------臨時關聯watcher----------------------------");
Dep.target = this;
// 向Dep方法添加當前Wathcer
this.addDep = function(dep) {
console.log("watcher向當前收集器dep添加當前Wathcer");
dep.addSub(self);
}
// 更新方法,用於觸發vm._render
this.update = function() {
console.log("watcher 觸發render");
fn();//vue.render
}
// 這裏會首次調用vm._render,從而觸發text的get
// 從而將當前的Wathcer與Dep關聯起來
//vue.render 首次渲染
console.log("-----------------------首次渲染----------------------------");
this.value = fn();
// 這裏清空了Dep.target,爲了防止notify觸發時,不停的綁定Watcher與Dep,
// 形成代碼死循環
console.log("————————————————————清空Dep.target");
Dep.target = null;
}
//5.
const Dep = function() {
console.log("Dep新建收集器");
const self = this;
// 收集目標
this.target = null;
// 存儲收集器中須要通知的Watcher
this.subs = [];
// 當有目標時,綁定Dep與Wathcer的關係
this.depend = function() {
//確認實例是否綁定watcher
if (Dep.target) {
// 這裏其實能夠直接寫self.addSub(Dep.target),
// 沒有這麼寫由於想還原源碼的過程。
console.log("Dep向當前收集器dep添加當前Wathcer");
Dep.target.addDep(self);
}
}
// 爲當前收集器添加Watcher
this.addSub = function(watcher) {
console.log("Dep addSub爲當前收集器添加watcher");
self.subs.push(watcher);
}
// 通知收集器中所的全部Wathcer,調用其update方法
this.notify = function() {
console.log("Dep 通知全部的watcher 觸發更新");
for (let i = 0; i < self.subs.length; i += 1) {
self.subs[i].update();
console.log(`第${i}個watcher`, self.subs[i]);
}
}
}
const vue = new Vue({
data() {
return {
text: 'hello world',
text1: "aaa",
text2: "333"
};
}
})
console.log("觸發掛載-----------------------");
vue.mount(); // in get
console.log(vue._data.text);
console.log(vue._data.text1);
console.log(vue._data.text2);
console.log('data set調用------------------------------------------------------');
vue._data.text = '123'; // in watcher update /n in get
console.log('data set調用-----------------------------------------------------------------');
vue._data.text = 'aaaaa';
console.log(vue._data.text);
console.log(vue._data.text1);
console.log(vue._data.text2);
複製代碼