vue源码分析(二)>>:watch
今天来分析一下watch源码实现方式,watch就是实现某个属性发生变化立即得到通知。
step1:watch用法:
基本用法如下(写的比较粗糙,看不明白的看这篇基础点的:https://www.cnblogs.com/rainbowLover/p/13036284.html):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>vue-watch</title> </head> <body> <div id="app"> <p>info.name-{{info.name}}</p> <p>info.age-{{info.age}}</p> <p>hoppy-{{hobby}}</p> <p>tip-{{tip}}</p> <button @click="handleClick">click</button> </div> <script src="../../dist/vue.min.js"></script> <script> let vm = new Vue({ el:'#app', data:{ info:{ name:'张三', age:22 }, hobby:'love', tip:'哈哈' }, mounted() { // 用Vue的$watch添加,参数一:监听的属性名,参数二:回调,参数三:配置 this.$watch('tip',this.tipChanged,{immediate:true}) }, methods: { handleClick(){ this.info.name = '李四' this.info.age = 66 this.hobby = 'make' this.tip = '呵呵' }, tipChanged(){ console.log("tip watch >>:", arguments); } }, watch: { hobby(){// 方法名就是监听的属性名 console.log("hobby watch >>:", arguments); }, 'info.name':function(){// 监听info的属性name console.log("info.name watch >>:", arguments); }, info:{// 监听info immediate:true,// 立马执行 页面加载时候会执行一次回调方法 deep:true,// 开启深度监听的话 info的属性变化也会触发回调, handler:function(){// 回调 console.log("info watch >>:", arguments); } } } }) </script> </body> </html>
step2:watch源码实现:
vue在实例化时候走_init()方法,在beforeCreate和created之间回调用initState方法,继续追踪这个方法:这个方法是初始化数据相关的对象,在这个方法里边看到先后执行了:initProps、initMethods、initData、initComputed,最后执行initWatch方法。看看这个方法:
function initWatch (vm, watch) { for (var key in watch) { var handler = watch[key]; if (Array.isArray(handler)) { for (var i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } } }
断点看到watch是:就是界面上定义的watch里边的东西
这里看到watch[key]还可以是数组?然后我赶紧去试一下:改成这样
发现hobby变化时候这两个方法都执行了!然后继续往下看:createWatcher
// 查找发现 这个方法只被初始化watch过程调用
// 参数一:vue实例对象
// 参数二:key
// 参数三:定义的方法或者深度监听等配置
// 参数四:深度监听等配置 只有this.$watch()方法定义watch时候使用,
function createWatcher (
vm,
expOrFn,
handler,
options
) {
// 如果是对象 就是深度监听那种定义方法 info的那个定义
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
// 如果是字符串 就去看看方法里边定义的该方法
if (typeof handler === 'string') {
handler = vm[handler];
}
// 调用$watch
return vm.$watch(expOrFn, handler, options)
}
通过这个方法,把不同方式定义的watch规范一下,找到回调方法,
看到handler还能是字符串,我试一下:可以触发
然后继续看$watch方法:
// watch操作 Vue.prototype.$watch = function ( expOrFn, cb, options ) { debugger var vm = this; // 如果handler还不是方法 还是对象 递归调用createWatcher if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true;// 标注这些watch都是用户整的 var watcher = new Watcher(vm, expOrFn, cb, options);// 创建一个Watcher实例 if (options.immediate) {// 如果有immediate属性 就立即执行一下子 try { cb.call(vm, watcher.value); } catch (error) { handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\"")); } } return function unwatchFn () {// 设置完 返回一个注销监听的方法 watcher.teardown(); } };
在这里可以看到传immediate有撒用了,继续看实例化Watcher都有哪些操作:
/** * 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) { 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$2; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop; 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(); }; /** * Parse simple path. */ var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); function parsePath (path) { if (bailRE.test(path)) { return } var segments = path.split('.'); return function (obj) { for (var i = 0; i < segments.length; i++) { if (!obj) { return } obj = obj[segments[i]]; } return obj } }
然后看get方法:
/** * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get () { debugger 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(); // 对应 pushTarget(this); this.cleanupDeps(); } return value };
看看如果是深度监听 , 他会怎么操作递归给每个子属性都收集依赖:
var seenObjects = new _Set(); /** * 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; if (seen.has(depId)) { return } seen.add(depId); } if (isA) { i = val.length; while (i--) { _traverse(val[i], seen); } } else { keys = Object.keys(val); i = keys.length; // 递归获取子值,触发getter,收集依赖,此时Watcher不为空 while (i--) { _traverse(val[keys[i]], seen); } } }
step3:watch触发:
当我们修改属性值时候,就会触发该属性的set方法,就会执行watcher的update方法,然后会触发Watcher的run方法:然后回调咱们写的回调:
/** * Subscriber interface. * Will be called when a dependency changes. */ Watcher.prototype.update = function update () { console.log("Watcher update"); /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); } }; /** * Scheduler job interface. * Will be called by the scheduler. */ Watcher.prototype.run = function run () { console.log("Watcher run"); if (this.active) { var value = this.get(); if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value var oldValue = this.value; this.value = value; if (this.user) { try { this.cb.call(this.vm, value, oldValue);// 回调执行 } catch (e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { this.cb.call(this.vm, value, oldValue); } } } };
总结:收获什么了? 什么也没收获?什么也没收获吗?
over!