vm.$watch
用法: vm.$watch( expOrFn, callback, [options] ) ,返回值为 unwatch 是一个函数用来取消观察;下面主要理解 options 中的两个参数 deep 和 immediate 以及 unwatch
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 unwatchFn() { watcher.teardown() } }
immediate
从上面代码中可以看出当 immediate 为 true 时,就会直接进行执行回调函数
unwatch
实现方式是:
- 将被访问到的数据 dep 收集到 watchs 实例对象上,通过 this.deps 存起来
- 将被访问到的数据 dep.id 收集到 watchs 实例对象上,通过 this.depIds 存起来
- 最后通过 watchs 实例对象的 teardown 进行删除
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } .... addDep (dep) { const id = dep.id //参数dep是Dep实例对象 if(!this.depIds.has(id)) { //判断是否存在避免重复添加 this.depIds.add(id) this.deps.push(dep) dep.addSub(this) //this 是依赖 } } teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } } } let uid = 0 class Dep { constructor () { this.id = uid++ ... } ... depend () { if(window.target) { window.target.addDep(this) //将this即当前dep对象加入到watcher对象上 } } removeSub (sub) { const index = this.subs.indexOf(sub) if(index > -1) { return this.subs.splice(index, 1) } } }
分析
当执行 teardown() 时需要循环;因为例如 expOrFn = function () { return this.name + this.age } ,这时会有两个 dep 分别是 name 与 age 分别都加入了 watcher 依赖( this ),都会加入到 this.deps 中,所以需要循环将含有依赖的 dep 都删除其依赖
deep
需要明白的是
- deep 干啥用的,例如 data = {arr: [1, 2, {b: 6]} ,当我们只是监听 data.arr 时,在 [1, 2, {b: 66}] 这个数值内部发生变化时,也需要触发,即 b = 888
怎么做呢?
class Watcher { constructor (vm, expOrFn, cb, options) { this.vm = vm this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function') { this.getter = expOrFn }else { this.getter = parsePath(expOrFn) } if(options) { //取值 this.deep = !!options.deep }else { this.deep = false } this.cb = cb this.value = this.get() } get () { window.target = this let value = this.getter.call(vm, vm) if(this.deep) { traverse(value) } window.target = undefined return value } ... } const seenObjects = new Set() function traverse (val) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse(val, seen) { let i, keys const isA = Array.isArray(val) if((!isA && isObject(val)) || Object.isFrozen(val)) { //判断val是否是对象或者数组以及是否被冻结 return } if(val._ob_) { const depId = val._ob_.dep.id //可以看前面一篇我们对Observer类添加了this.dep = new Dep(),所以能访问其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 while (i--) _traverse(val[i], seen) } }
分析
- window.target = this ,寄存依赖
- let value = this.getter.call(vm, vm) 访问当前val,并执行 get
的 dep.depend() ,如果发现 val 为数组,则将依赖加入到 observer 的 dep 中,也就实现了对当前数组的拦截
- traverse(value) 也就是执行 _traverse(val, seenObjects) ;核心就是对被 Observer 的 val 通过 val[i] 通过这种操作,间接触发 get ,将依赖添加到当前数值的 dep 中,这样也就实现了,当内部数据发生变化,也会循环 subs 执行依赖的 update ,从而触发回调;当是数组时,只需进行遍历,看内部是否有 Object 对象即可,因为在第二步的时候,会对 val 进行判断是否是数组,变改变七个方法的value,在遍历;所以这边只要是内部数组都会进行拦截操作,添加依赖,即对象 {} 这种没没添加依赖。
- seenObjects.clear() 当内部所以类型数据都添加好其依赖后,就清空。
- window.target = undefined 消除依赖
vm.$set
用法: vm.$set(target, key, value)
作用
- 对于数组,进行 set 则是添加新元素,并需要触发依赖更新
- 对于对象,如果 key 值存在,则是修改 value ;不存在,则是添加新元素,需新元素要进行响应式处理,以及触发更新
- 对于对象本身不是响应式,则直接添加 key-value ,无需处理
Vue.prototype.$set = function (target, key, val) { if(Array.isArray(target) && isValidArrayIndex(key)) { //是数组并且key有效 target.length = Math.max(target.length, key) //处理key > target.length target.splice(key, 1, val) //添加新元素,并输出依赖更新同时新元素也会进行`Obsever`处理 return val } if(key in targert && !(key in Object.prototype) { //能遍历并且是自身key target[key] = val //触发set,执行依赖更新 return val } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象) //触发警告 return } if(!ob) { //只添加 target[key] = val return val } defineReactive(ob.value, key, val) //进行响应式处理 ob.dep.notify() //触发依赖更新 returnv val }
vm.$delete
用法: vm.$delete( target, key)
作用
- 对于数组,进行 delete 则是删除新元素,并需要触发依赖更新
- 对于对象,如果 key 值不存在,直接 return ,存在,删除元素,
- 对于对象本身不是响应式,则只删除 key-value ,无需其他处理
Vue.prototype.$delete = function (target, key) { if(Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = target._ob_ if(target.isVue || (ob && ob.vm.Count) { //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象) //触发警告 return } if(!hasOwn(target, key)) { return } delete target[key] if(!ob) { return } ob.dep.notify() }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
Vue侦测相关api
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。