其余章节请看: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); } } }
其余章节请看: