最近看了 Vue 源碼和源碼分析類的文章,感受明白了不少,可是仔細想一想卻說不出個因此然。vue
因此打算把本身掌握的知識,試着組織成本身的語言表達出來dom
不打算平鋪直敘的寫清楚 vue 源碼的來龍去脈和所有細節,而是以自問自答的形式,回答我本身以前的疑惑,異步
若是有錯誤的地方,歡迎指正哈~函數
Vue 實現響應式的核心 API 是 ES5 的 Object.defineProperty(obj,key,descriptor),Vue 的「響應式」和「依賴收集」都依靠這個 API源碼分析
它接受 3 個參數,分別是 obj / key / 描述符,返回的是一個包裝後的對象性能
它的做用就是,用這個 API 包裝事後的對象能夠擁有 getter 和 setter 函數。this
getter 會在對象的這個 key 被獲取時觸發,setter 會在這個對象的 key 被修改時觸發。code
一個 Vue 項目的開始, 一般是從 Vue 構造函數的實例化開始的。對象
new Vue()的時候會執行一個_init()方法,會初始化屬性,好比 props/event/生命週期鉤子,也包括 data 對象的初始化。遞歸
Vue 在初始化時,將 data 對象上的全部 key,都包裝成擁有 getter 和 setter 的屬性。
function cb() { console.log("更新視圖"); } function defineReactve(obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { console.log("觸發了getter"); return val; }, set: newVal => { console.log("觸發了setter"); if (newVal === val) return; val = newVal; cb() } }); } function observe(data) { function walk(data) { Object.keys(data).forEach(key => { if (typeof data[key] === "object") { walk(data[key]); } else { defineReactve(data, key, data[key]); } }); } walk(data); } class Vue { constructor(options) { this._data = options.data; observe(this._data); } } var vm = new Vue({ data: { msg: "test", person: { name: "ziwei", age: 18 } } }); vm._data.person.name = 'hello' // 觸發setter和cb函數,從而視圖更新
function defineReactve( obj, key, val ) { const dep = new Dep() Object.defineProperty( obj, key, { enumerable: true, configurable: true, get: () => { console.log( "觸發了getter" ); dep.addSub(Dep.target) return val; }, set: newVal => { console.log( "觸發了setter" ); if ( newVal === val ) return; val = newVal; dep.notify() // 通知隊列的wather去update視圖 } } ); } function observe( data ) { function walk( data ) { Object.keys( data ).forEach( key => { if ( typeof data[ key ] === "object" ) { walk( data[ key ] ); } else { defineReactve( data, key, data[ key ] ); } } ); } walk( data ); } class Dep{ constructor(){ this.subs = [] } addSub(){ this.subs.push(Dep.target) } notify(){ this.subs.forEach(sub => { sub.update() }) } } Dep.target = null class Watcher{ constructor(){ Dep.target = this } update(){ console.log('update更新視圖啦~') } } class Vue { constructor( options ) { this._data = options.data; observe( this._data ); new Watcher() // 模擬頁面渲染,觸發getter,依賴收集的效果 this._data.person.name } } var vm = new Vue( { data: { msg: "test", person: { name: "ziwei", age: 18 } } } ); vm._data.person.name = 'hello'
這樣就是 Vue 響應式的一個基本原理,不過我描述的過程當中,也省略了不少環節,好比
Vue 是如何實現給 data 對象上的屬性都擁有 getter 和 setter 的
經過循環data對象,給對象的每個key,用Object.defineProperty包裝 遍歷時,若是發現data[key]也是對象的話,須要用遞歸
爲何要進行「依賴收集」?
舉2個場景的栗子🌰
但問題在於,你並不知道誰依賴我?我應該更新哪幾個地方
如何避免重複「收集依賴」
在往Dep中放wather對象是,實際上wather的update方法時,會把其放入queue隊列中,會經過watch.id判斷是否重複了,重複的wather就不會被推入隊列
watcher 調用 update,也並非直接更新視圖。實現上中間還有 patch 的過程以及使用隊列來異步更新的策略。
異步更新的策略,相似於setTimeout(fn,0) ,目的就是爲了不頻繁的更新dom,讓頁面的渲染的性能更好。