其餘章節請看:javascript
vue 快速入門 系列html
前面(偵測數據的變化 - [基本實現])咱們已經介紹了新增屬性沒法被偵測到,以及經過 delete 刪除數據也不會通知外界,所以 vue 提供了 vm.$set() 和 vm.$delete() 來解決這個問題。vue
vm.$watch() 方法賦予咱們監聽實例上數據變化的能力。java
下面依次對這三個方法的使用以及原理進行介紹。react
Tip: 如下代碼出自 vue.esm.js,版本爲 v2.5.20。無關代碼有一些刪減。中文註釋都是筆者添加。express
這是全局 Vue.set 的別名。向響應式對象中添加一個 property,並確保這個新 property 一樣是響應式的,且觸發視圖更新。api
語法:數組
參數:ide
如下是相關源碼:函數
Vue.prototype.$set = set; /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ function set (target, key, val) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); } // 若是 target 是數組,而且 key 是一個有效的數組索引 if (Array.isArray(target) && isValidArrayIndex(key)) { // 若是傳遞的索引比數組長度的值大,則將其設置爲 length target.length = Math.max(target.length, key); // 觸發攔截器的行爲,會自動將新增的 val 轉爲響應式 target.splice(key, 1, val); return val } // 若是 key 已經存在,說明這個 key 已經被偵測了,直接修改便可 if (key in target && !(key in Object.prototype)) { target[key] = val; return val } // 取得數據的 Observer 實例 var ob = (target).__ob__; // 處理文檔中說的 」注意對象不能是 Vue 實例,或者 Vue 實例的根數據對象「 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } // 若是數據沒有 __ob__,說明不是響應式的,也就不須要作任何特殊處理 if (!ob) { target[key] = val; return val } // 經過 defineReactive$$1() 方法在響應式數據上新增一個屬性,該方法會將新增屬性 // 轉成 getter/setter defineReactive$$1(ob.value, key, val); ob.dep.notify(); return val } /** * Check if val is a valid array index. * 檢查 val 是不是一個有效的數組索引 */ function isValidArrayIndex (val) { var n = parseFloat(String(val)); return n >= 0 && Math.floor(n) === n && isFinite(val) }
這是全局 Vue.delete 的別名。刪除對象的 property。若是對象是響應式的,確保刪除能觸發更新視圖。你應該不多會使用它。
語法:
參數:
實現思路與 vm.$set 相似。請看:
Vue.prototype.$delete = del; /** * Delete a property and trigger change if necessary. * 刪除屬性,並在必要時觸發更改。 */ function del (target, key) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); } // 若是 target 是數組,而且 key 是一個有效的數組索引 if (Array.isArray(target) && isValidArrayIndex(key)) { // 觸發攔截器的行爲 target.splice(key, 1); return } // 取得數據的 Observer 實例 var ob = (target).__ob__; // 處理文檔中說的 」注意對象不能是 Vue 實例,或者 Vue 實例的根數據對象「 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ); return } // key 不是 target 自身屬性,直接返回 if (!hasOwn(target, key)) { return } delete target[key]; // 不是響應式數據,終止程序 if (!ob) { return } // 通知依賴 ob.dep.notify(); }
觀察 Vue 實例上的一個表達式或者一個函數計算結果的變化。回調函數獲得的參數爲新值和舊值。表達式只接受簡單的鍵路徑。對於更復雜的表達式,用一個函數取代。
語法:
參數:
返回值:
例如:
// 鍵路徑 vm.$watch('a.b.c', function (newVal, oldVal) { // 作點什麼 }) // 函數 vm.$watch( function () { return this.a + this.b }, function (newVal, oldVal) { // 作點什麼 } )
相關源碼請看:
Vue.prototype.$watch = function ( expOrFn, cb, options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true; // 經過 Watcher() 來實現 vm.$watch 的基本功能 var watcher = new Watcher(vm, expOrFn, cb, options); // 在選項參數中指定 immediate: true 將當即以表達式的當前值觸發回調 if (options.immediate) { try { cb.call(vm, watcher.value); } catch (error) { handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\"")); } } // 返回一個函數,做用是取消觀察 return function unwatchFn () { watcher.teardown(); } }; /** * Remove self from all dependencies' subscriber list. * 取消觀察。也就是從全部依賴(Dep)中把本身刪除 */ Watcher.prototype.teardown = function teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this); } // this.deps 中記錄這收集了本身(Wtacher)的依賴 var i = this.deps.length; while (i--) { // 依賴中刪除本身 this.deps[i].removeSub(this); } this.active = false; } }; /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) { this.vm = vm; if (isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if (options) { // deep 監聽對象內部值的變化 this.deep = !!options.deep; this.user = !!options.user; this.lazy = !!options.lazy; this.sync = !!options.sync; this.before = options.before; } else { this.deep = this.user = this.lazy = this.sync = false; } this.cb = cb; this.id = ++uid$1; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers // 存儲依賴(Dep)。Watcher 能夠經過 deps 得知本身被哪些 Dep 收集了。 // 可用於取消觀察 this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : ''; // parse expression for getter // expOrFn能夠是簡單的鍵路徑或函數。本質上都是讀取數據的時候收集依賴, // 因此函數能夠同時監聽多個數據的變化 // 函數: vm.$watch(() => {return this.a + this.b},...) if (typeof expOrFn === 'function') { this.getter = expOrFn; // 鍵路徑: vm.$watch('a.b.c',...) } else { // 返回一個讀取鍵路徑(a.b.c)的函數 this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop; process.env.NODE_ENV !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? undefined : this.get(); }; /** * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get () { // 把本身入棧,讀數據的時候就能夠收集到本身 pushTarget(this); var value; var vm = this.vm; try { // 收集依賴 value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching // 對象內部的值發生變化,也須要通知依賴。 if (this.deep) { // 把當前值的子值都觸發一遍收集依賴的邏輯便可 traverse(value); } popTarget(); this.cleanupDeps(); } return value }; /** * Recursively traverse an object to evoke all converted * getters, so that every nested property inside the object * is collected as a "deep" dependency. */ function traverse (val) { _traverse(val, seenObjects); seenObjects.clear(); } function _traverse (val, seen) { var i, keys; var isA = Array.isArray(val); // 不是數組和對象、已經被凍結,或者虛擬節點,直接返回 if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } if (val.__ob__) { var depId = val.__ob__.dep.id; // 拿到 val 的 dep.id,防止重複收集依賴 if (seen.has(depId)) { return } seen.add(depId); } // 若是是數組,循環數組,將數組中的每一項遞歸調用 _traverse if (isA) { i = val.length; while (i--) { _traverse(val[i], seen); } } else { keys = Object.keys(val); i = keys.length; // 重點來了:讀取數據(val[keys[i]])觸發收集依賴的邏輯 while (i--) { _traverse(val[keys[i]], seen); } } }
其餘章節請看: