写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】数组
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧bash
今天继续探索 Watch 源码,废话很少说了学习
带着个人几个疑问开始ui
一、何时初始化
二、怎么肯定监听哪些值
三、深度监听怎么回事
四、怎么触发个人函数
复制代码
这些问题的答案会掺杂在源码的解析中,我发现这几篇的写做套路都差很少.....this
也能够看查一下个人白话版spa
首先,从这个问题开始咱们今天的探索之旅,请看源码prototype
function Vue(){
... 其余处理
initState(this)
...解析模板,生成DOM 插入页面
}
function initState(vm) {
...处理 data,props,computed 等数据
if (opts.watch) {
initWatch(this, vm.$options.watch);
}
}
复制代码
没错,当你调用 Vue 建立实例过程当中,会去处理各类选项,其中包括处理 watch3d
处理 watch的方法是 initWatch,下面就呈上 源码code
function initWatch(vm, watch) {
for (var key in watch) {
var watchOpt = watch[key];
createWatcher(vm, key, handler);
}
}
复制代码
而后这段源码并无作什么惊天地泣鬼神的事情,只是简简单单的一个遍历,而后每一个watch 都使用一个叫什么 createWatcher 的东西去处理
这个函数究竟是干吗的?暗藏着什么秘密,欢迎来到咱们今晚的 《走近科学》《源码解析》
createWatcher 来看看他的真身
function createWatcher(
// expOrFn 是 key,handler 多是对象
vm, expOrFn, handler,opts
) {
// 监听属性的值是一个对象,包含handler,deep,immediate
if (typeof handler ==="object") {
opts= handler
handler = handler.handler
}
// 回调函数是一个字符串,从 vm 获取
if (typeof handler === 'string') {
handler = vm[handler]
}
// expOrFn 是 key,options 是watch 的所有选项
vm.$watch(expOrFn, handler, opts)
}
复制代码
大概就这样吧
一、获取到监听回调
二、调用 vm.$watch
首先,你传入的 watch 配置多是这三种(还有更多,差很少,不解释,累死我)
若是配置是个对象,就取handler 字段
若是配置是函数,那么直接就是 监听回调
若是配置是字符串,从实例上获取函数
看着这个方法,简直 mmp,还有完没完,我从一个函数进入一个函数,又从这个函数进入到另外一个函数,迷宫啊这是.....
显然你不用急,下面还有更多......
好吧,仍是先看源码
Vue.prototype.$watch = function(
// expOrFn 是 监听的 key,cb 是监听回调,opts 是全部选项
expOrFn, cb, opts
){
// expOrFn 是 监听的 key,cb 是监听的回调,opts 是 监听的全部选项
var watcher = new Watcher(this, expOrFn, cb, opts);
// 设定了当即执行,因此立刻执行回调
if (opts.immediate) {
cb.call(this, watcher.value);
}
};
复制代码
看完了吧?这么短,大家确定看得懂的啦,就两件事
若是你设置了 immediate 的话,表示不用等我数据变化,初始化时立刻执行一遍,执行的代码就是直接调用 回调,绑定上下文,传入监听值
代码从这里开始变得沉重,各位观众,喝口水,恰口饭,屏息观看操做
看看 watcher 的源码
“watcher 的源码以前的文章也讲过不少,可是对于每种选项的涉及的细节是不同的,因此每次都放上来,可是只放跟本内容相关的部分代码,其余的省去以便咱们快速理解”
var Watcher = function (vm, key, cb, opt) {
this.vm = vm;
this.deep = opt.deep;
this.cb = cb;
// 这里省略处理 xx.xx.xx 这种较复杂的key
this.getter = function(obj) {
return obj[key]
};
// this.get 做用就是执行 this.getter函数
this.value = this.get();
};
复制代码
再看看,新建 watcher 的时候 ,传入了什么
一、监听的 key
二、监听回调 (Watch 中的cb)
三、监听配置的options
这里会涉及到三个问题,如今来解释
咱们要先对 Watch 中的 this.getter 的函数进行理解,他的本质是为了获取对象的key值
而后 getter 是在 watcher.get 中执行的,看下 get 源码
// 对本问题进行了独家简单化的源码
Watcher.prototype.get = function() {
var value = this.getter(this.vm);
return value
};
复制代码
你能看到,Watch 在结尾会当即执行一次 watcher.get,其中便会执行 getter,便会根据你监听的key,去实例上读取并返回,存放在 watcher.value 上
看到了吗,从实例上读取属性,这句话。
首先,watch 初始化以前,data 应该初始化完毕了,每一个 data 数据都已是响应式的
使用例子来讲明一下
当 watch.getter 执行,而读取了 vm.name 的时候,name的依赖收集器就会收集到 watch-watcher
因而 name 变化的时候,会能够通知到 watch,监听就成功了
首先,深度监听,是你设置了 deep 的时候,以下
而后,观察上面的 Watch 源码,deep 会保存在watcher 中,以便后用
话锋一转
上一问题说过,在 新建 watcher 的时候,会立刻执行一个 get,上个问题的 get 源码简化不少,把 处理深度监听的部分去掉了,这里露出来了
Watcher.prototype.get = function() {
Dep.target= this
var value = this.getter(this.vm)
if (this.deep) traverse(value)
Dep.target= null
return value
};
复制代码
没错,处理深度监听只有一条语句!
if (this.deep) traverse(value)
复制代码
value 是 getter 从实例上读取监听key 获得的值,没有疑问
可是 traverse 是何方神圣?come on 让咱们深刻....
function traverse(val) {
var i, keys;
// 数组逐个遍历
if (Array.isArray(val)) {
i = val.length;
// val[i] 就是读取值了,而后值的对象就能收集到 watch-watcher
while (i--) {
traverse(val[i])
}
}
else {
keys = Object.keys(val);
i = keys.length;
// val[keys[i]] 就是读取值了,而后值的对象就能收集到 watch-watcher
while (i--) {
traverse(val[keys[i]])
}
}
}
复制代码
你看它这段代码长,实际上是个纸老虎,作的就是一个事情,不断递归深刻读取对象
他的想法是这样的
由于读取,就可让这个属性收集到 watch-watcher 的原则
就算是深层级的对象,其中的每一个属性也都是响应式的,每一个属性都有本身的依赖收集器
经过不断深刻的读取每一个属性,这样每一个属性就均可以收集到 watch-watcher 了
这样无论对象内多深的属性变化,都会通知到 watch-watcher
因而这样就完成了深度监听
经过上面的问题,咱们已经了解了大部分了
监听的数据变化的时候,就能通知 watch-watcher 更新,所谓通知更新,就是手动调用 watch.update
速度看下 watcher.update 源码
Watcher.prototype.update= function() {
var value = this.get();
if (this.deep) {
var oldValue = this.value;
this.value = value;
// cb 是监听回调
this.cb.call(this.vm, value, oldValue);
}
};
复制代码
很简单嘛,就是读取一遍值,而后保存新值,接着 调用 监听回调,并传入新值和 旧值
ok,就这样