我讀vue響應式
vue生命週期:
init:初始化props,methods,data,computed和watch;
1.this是如何直接訪問data、props、methods裡的屬性和方法的?
注:為避免衝突和覆蓋,data、props、methods裡不能定義相同名字的屬性,
優先順序:props> methods> data(即如果一個 key 在 props 中有定義了那麼就不能在 data 和 methods 中出現了;如果一個 key 在 data 中出現了那麼就不能在 methods 中出現了)
原始碼中的initState函式:
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
由以上原始碼可以看到 initState 其實是很多選項初始化的彙總,包括:props、methods、data、computed 和 watch;
2.實現一個簡易的響應式系統
Object.defineProperty:在物件上定義或修改屬性
Object.observe:觀察物件屬性的更改
Vue通過Object.defineProperty的 getter/setter 對收集的依賴項進行監聽,在屬性被訪問和修改時通知變化,進而更新檢視資料;
即資料觀測、依賴收集、檢視更新。
observer的實現
讓object的物件都用Object.defineProperty來定義,以達到獲取或修改的時候能夠呼叫屬性裡的get和set
class Observer { constructor(value) { this.value = value this.walk(this.value) } walk (value) { // 遞迴遍歷value的屬性 Object.keys(value).forEach((key) = > { defineReactive(value, key, value[key]) }) } } // 將物件的每一個屬性都新增get和set方法 function defineReactive(obj, key ,val) { let childOb = observe(val) Obeject.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log('') return val }, // 物件改變時,新物件的每個屬性也新增get和set方法 set(newVal) { console.log('set') val = newVal childOb = observe(val) } }) } // 屬性型別觀測判斷 function observe (value) { if (typeof value === 'object' && !Array.isArray(value)) { value = new Observer(value); return value; } }
以上程式碼可以看出,我們是通過console來判斷是否執行get和set的,為了方便,我們使用訊息訂閱來實現通知變化
Dep的實現
收集屬性值的變化,一旦set觸發就更新檢視
class Dep { constructor(){ //訂閱的資訊 this.subs = []; } addSub(sub){ this.subs.push(sub); } removeSub (sub) { remove(this.subs, sub); } //更新收集 depend(){ if (Dep.target) { Dep.target.addDep(this); } } //派發通知 notify(){ const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } } // 訂閱完成後將target清掉 Dep.target = null;
Watcher的實現
狀態發生改變時更新檢視
const watcher = { addDep:function (dep) { dep.addSub(this); }, update:function(){ html(); } }
以上,我們完成來Observe、Dep、Watcher的簡單實現,我們看到Observe和Dep裡都呼叫裡Watcher裡的update,為了讓其各司其職,我們將其改為Observe裡一旦觸發set就通知Dep並呼叫notify派發更新任務。
將以上實現進行簡單修改和串聯,以實現響應式,全部程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body></body> <script> class Observer { constructor(value) { this.value = value; this.walk(this.value); } walk (value) { // 遞迴遍歷value的屬性 Object.keys(value).forEach((key) => { defineReactive(value, key, value[key]); }) } } // 將物件的每一個屬性都新增get和set方法 function defineReactive(obj, key ,val) { // new一個Dep物件,後面要呼叫dep的方法 const dep = new Dep(); let val = observe(val); Object.defineProperty(obj,key,{ enumerable: true, configurable: true, get: function () { if(Dep.target){ //收集依賴 dep.depend() } return val; }, set: function (newVal) { if(newVal === val || (newVal !== newVal && val !== val)){ return ; } val = observe(newVal); //釋出改變 dep.notify(); } }); } // 屬性型別觀測判斷 function observe (value) { if (typeof value === 'object' && !Array.isArray(value)) { value = new Observer(value); return value; } } class Dep { constructor(){ //訂閱的資訊 this.subs = []; } addSub(sub){ this.subs.push(sub); } removeSub (sub) { remove(this.subs, sub); } //此方法的作用等同於 this.subs.push(Watcher); depend(){ if (Dep.target) { Dep.target.addDep(this); } } //這個方法就是釋出通知了 告訴你 有改變啦 notify(){ const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } } // 訂閱完成後將target清掉 Dep.target = null; const watcher = { addDep:function (dep) { dep.addSub(this); }, update:function(){ html(); } } let obj = { a: '仔細看' }; function html () { document.querySelector('body').innerHTML = obj.a; } Dep.target = watcher; html();//第一次渲染介面 Dep.target = null; setTimeout(function() { obj.a = '我變了'; html(); },5000) </script>
注:主要修改都在defineReactive裡,以上程式碼可直接執行。