watch Watcher会执行上面部分,parsePath源码可自行查看,他会将obj.a这种写法兼容, 最终是返回需要监听的属性的getter函数
if (this.computed) { this.value = undefined; this.dep = new Dep(); } else { // 执行get方法 this.value = this.get(); }拿到getter后,会执行this.get方法:
Watcher.prototype.get = function get () { // 加入Dep.target pushTarget(this); var value; var vm = this.vm; try { value = this.getter.call(vm, vm); // 执行 } catch (e) { } finally { popTarget(); this.cleanupDeps(); } return value };以上为get方法,内容简单,但是做的事情举足轻重,他不仅做了值的获取,还做了依赖收集。
pushTarget会将当前watchWatcher赋值到Dep.target中,然后执行this.getter函数,要监听的属性如count会触发他的get钩子,与此同时会进行收集依赖,收集到的依赖就是前面Dep.target也就是当前的watchWatcher
正因为有上面的依赖收集,使count属性有了此watchWatcher的依赖,当this.count改变时,会触发set钩子,进行事件分发,从而执行回调函数
Watcher.prototype.getAndInvoke = function getAndInvoke (cb) { var value = this.get(); if ( value !== this.value || isObject(value) || this.deep ) { // set new value var oldValue = this.value; this.value = value; this.dirty = false; if (this.user) { try { cb.call(this.vm, value, oldValue); } catch (e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { cb.call(this.vm, value, oldValue); } } };上面就是this.count改变时,最终调用的方法,在这里会执行this.cb,也就是定义的watch的回调函数,会把value/oldValue传递过去
立即执行的watch前面说到,watch只会在监听的属性改变值后才会触发回调,在初始化时不会执行回调,如果想要一开始初始化就执行回调,需要传参对象,并immediate为true,实现原理已经在贴出来了
if (options.immediate) { cb.call(vm, watcher.value); }创建watcher时,如果immediate为真值,会直接执行回调函数
与computed比较computed是计算属性,watch是监听属性变化,有些场景计算属性做的事情,watch也可以做,当然要尽量用computed去做,为什么?
new Vue({ data: { num: 1, sum: 2 }, watch: { num(val) { this.sum = val + 1; } } })watch实现需要声明2个data属性num 和 sum,2个都会加入数据驱动,当num改变后,num和sum都触发了set钩子。
而computed不会,computed只会触发num的set钩子,因为sum根本没有声明,num改变后是动态计算出来的。