關於 Vue.js 的原理一直以來都是一個話題。通過幾天的源碼學習和資料介紹,我將一些我的理解的經驗給寫下來,但願可以與你們共勉。javascript
附上GITHUB源碼地址, 若是有任何不解 能夠在 文章下面提出或者寫下issue, 方便你們回答和學習, 有興趣能夠Star.
最後附上 LIVE DEMOvue
應用建立時須要使用的構造函數對象, data
爲咱們的數據模型java
const vm = new Vue({ data: { foo: 'hello world' } });
被觀察對象,data屬性裏的值的變化,get
時檢查是否有新的觀察員須要加入觀察員集合, set
時通知觀察員集合裏的觀察員更新視圖,被觀察者有一個觀察員的集合對象。react
一個觀察員的收集器, depend()
負責將當前的 Dep.target
觀察員加入觀察員集合, data
中的每一項數據都會有對應的閉包dep
對象, 數據對象會有一個內置的dep
對象,用來通知嵌套的數據對象變化的狀況。git
由模板解析指令建立的觀察員, 負責模板中的更新視圖操做。保留舊的數據,以及設置鉤子函數 update()
, 等待被觀察者數據通知更新,對比新的value與舊數據, 從而更新視圖。github
咱們的關注點在與建立後的vm
, 其 options.data
, 被掛載至vm._data
, 同時被代理至 vm
上, 以致於 vm._data.foo
等價於 vm.foo
, 代理函數代碼以下:緩存
const noop = function () {} const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) } // initState 時執行 initData function initData () { const keys = Object.keys(data) let i = keys.length while (i--) { const key = keys[i] // key不以 $ 或 _開頭 if (!isReserved(key)) { proxy(vm, `_data`, key) } } // do something }
/** * Define a reactive property on an Object. */ function defineReactive(obj, key, val) { // 觀察員集合 const dep = new Dep(); // data 內屬性描述 const property = Object.getOwnPropertyDescriptor(obj, key); // 屬性不可再次修改 if (property && property.configurable === false) { return; } // 屬性預約義 getter/setters const getter = property && property.get; const setter = property && property.set; // 若是val爲對象, 獲取val的 被觀察數據對象 __ob__ let childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { // 被觀察數據被使用時, 獲取被觀察員最新的數據 const value = getter ? getter.call(obj) : val // 觀察員在new時或使用 get()時, 注入給被觀察員對象集合 if (Dep.target) { // 將當前的 watcher 傳遞給 觀察員 dep.depend(); if (childOb) { // 將當前的 watcher 傳遞給子對象的 觀察員 childOb.dep.depend(); } } return val; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (newVal === value || (newVal !== newVal && value !== value)) { return; } if (setter) { setter.call(obj, newVal); } else { val = newVal; } // 新value設置被觀察者對象 __ob__ childOb = observe(newVal); // 通知數據對象依賴的觀察員, 更新 update() dep.notify(); } }); }
計算屬性的定義和使用閉包
var vm = new Vue({ data: { firstname: 'li', lastname: 'yanlong' }, computed: { fullname () { return this.firstname + this.lastname; } } }); console.log(vm.fullname);
const computedWatcherOptions = {lazy: true}; function initComputed (vm, computedOptions) { // 建立計算屬性對應的觀察員對象 // 獲取計算屬性時收集 內置數據對象的 dep const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); if (!(key in vm)) { defineComputed(vm, key, userDef) } } } function defineComputed (target, key, userDef) { // 若是不爲服務端渲染,則使用緩存value const shouldCache = !isServerRendering() // sharedPropertyDefinition 共享屬性配置 if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } // 給 vm對象定義計算屬性 Object.defineProperty(target, key, sharedPropertyDefinition) } // 建立 function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 計算屬性的 watcher 有數據更新過, 從新計算 if (watcher.dirty) { watcher.evaluate() } // 視圖指令 使用了計算屬性 // 將計算屬性的watcher依賴傳遞給視圖指令的 watcher if (Dep.target) { // 源碼地址 // https://github.com/vuejs/vue/blob/master/src/core/observer/watcher.js#L210 watcher.depend() } return watcher.value } } }
1. 計算屬性的 watcher
對象
計算屬性函數在讀取它自己的value
時, 使用一個watcher
觀察員進行代理. 經過對原始數據的劫持, 將watcher
觀察員添加到原始數據的dep
依賴集合中. mvvm
2. deps
的數據對象發生更新
舉例,若是firstname
或者 lastname
任意一個更新,那麼就會設置計算屬性的watcher.dirty = true
, 而當其它指令或者函數使用,計算屬性會從新計算值,若是是視圖指令,還會從新該指令的watcher
的數據對象依賴。函數
目前瞭解狀況來看, 主要分三類
watcher
watcher
watcher