Vue 源码剖析 —— 变化侦测相关 API 实现原理
栏目: JavaScript · 发布时间: 5年前
内容简介:vm.$watch(expOrFn, callback, [options])由于前面还提到,
vm.$watch(expOrFn, callback, [options])
- 返回值: unwatch { Function }
- 用法:用于观察一个表达式或 computed 函数在 Vue.js 实例上的变化,同时给回调函数传入新数据和旧数据作为参数。
- options参数: {deep, immediate} ,其中 deep 指定是否观察对象内部值的变化, immediate 指定是否立即执行回调函数
实现原理
Vue.prototype.$watch = function(expOrFn, cb, options) { const vm = this options = options || {} const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { cb.call(vm, watcher.value) } return function unwatch() { watcher.teardown() } } 复制代码
由于 $watch 函数的第一个参数 expOrFn 可以是函数也可以是字符串表达式,所以这里需要改写一下 Watcher 类:
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm // 新增 if (typeof expOrFn === 'function') { // 为函数时,不止可以返回动态数据,其中所有访问到的数据也都会被 Watcher 观察 this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } ... } 复制代码
前面还提到, vm.$watch 函数最后返回的是一个 unwatch 函数,顾名思义,它的作用是取消观察函数。执行 unwatch 函数其实就是执行当前 Watcher 实例的 teardown 函数。目前我们并没有在 Watcher 内部记录订阅的 Dep ,所以又要进行一次改写:
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.deps = [] // 新增 this.depIds = new Set() // 新增 if (typeof expOrFn === 'function') { // 为函数时,不止可以返回动态数据,其中所有访问到的数据也都会被 Watcher 观察 this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } ... addDep (dep) { const id = dep.id if (!this.depIds.has(id)) { this.depIds.add(id) this.deps.push(dep) dep.addSub(this) } } } // 给 Dep 加上 id let uid = 0 class Dep { constructor () { this.subs = [] } addSub (sub) { // 新增 if(somethingToWatch) { somethingToWatch.addDep(this) } //this.subs.push(sub) } ... } 复制代码
到现在,不仅是 Dep 会记录数据发生变化时,需要通知哪些 Watcher ,而 Watcher 中也同样记录了自己会被那些 Dep 通知,也就是说,这是一个多对多的关系。添加了 deps 属性后, teardown 函数的实现就很简单了,这里就省略不写。
最后,可以看下 deep 参数的实现。当 deep 为 true 时,需要监听当前对象的所有子值。实现原理和 Watcher 监听某个值的过程类似,通过访问这个数据,触发当前数据收集依赖的逻辑,把自己收集进去,故可以通过递归遍历当前要监听对象的所有子值来实现。
vm.$set
API 用法
vm.$set(target, key, value)
- 用法: 在 target 中添加属性 key , 如果 target 是响应式的,那么新增的 key 值也是响应式的。
对于数组的处理
数组的处理比较直观,可以直接利用包裹好的 splice 函数实现:
function set(target, key, val) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } } 复制代码
对于对象的处理
function set(target, key, val) { ... // 新增 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = target.__ob__ // _isVue 标记是否是 Vue 实例, ob.vmCount 标记是否是根数据对象 if (target._isVue || (ob && ob.vmCount)) { // 报错 ... return val } if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val } 复制代码
vm.$delete
API 用法
vm.$delete(target, key)
由于在 Vue.js 中,删除某个属性无法自动向依赖发送通知,所以包装了一个 vm.$delete 避免出现下面这种比较丑陋的代码 =. =:
delete this.obj.name this.obj.__ob__.dep.notify() 复制代码
下面是 vm.$delete 的实现:
function delete(target, key) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = target.__ob__ if (!hasOwn(target, key)) return delete ob[key] if (!ob) return ob.dep.notify() } 复制代码
本系列文章均是深入浅出 Vue.js的学习笔记,有兴趣的小伙伴可以去看书哈。
以上所述就是小编给大家介绍的《Vue 源码剖析 —— 变化侦测相关 API 实现原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Vue 源码剖析 —— 对象变化侦测
- 读vue的变化侦测
- Vue侦测相关api
- 海康威视网络摄像头-预览出现绿色移动侦测规则框
- Sophos Intercept X EDR:智能、简单的端点侦测与响应
- 【Java集合源码剖析】ArrayList源码剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
MacTalk 人生元编程
池建强 / 人民邮电出版社 / 2014-2-1 / 45
《MacTalk·人生元编程》是一本随笔文集,主要内容来自作者的微信公众平台“MacTalk By 池建强”。本书撰写于2013年,书中时间线却不止于此。作者以一个70 后程序员的笔触,立于Mac 之上,讲述技术与人文的故事,有历史,有明天,有技术,有人生。70 多篇文章划分为六大主题:Mac、程序员与编程、科技与人文、人物、工具、职场。篇篇独立成文,可拆可合,随时阅读。 此外,作者还对原来......一起来看看 《MacTalk 人生元编程》 这本书的介绍吧!