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() }
掘金地址
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/109735.html
摘要:总结最后我们依照下图参考深入浅出,再来回顾下整个过程在后,会调用函数进行初始化,也就是过程,在这个过程通过转换成了的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行函数,而在当被赋值的时候会执行函数。 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解...
摘要:此模块包含的设计思路即为预以匹配降级方案。没有默认编译该模块,以及利用该模块判断后提供平台相关逻辑的主要原因在于其设计原则的代码完成核心的功能。此处,也引出了代码实现的另一个基本原则面向功能标准,先功能覆盖再优雅降级。 在进入 Zepto Core 模块代码之前,本节简略列举 Zepto 及其他开源库中一些 Polyfill 的设计思路与实现技巧。 涉及模块:IE/IOS 3/Dete...
阅读 2721·2021-11-22 13:54
阅读 1062·2021-10-14 09:48
阅读 2291·2021-09-08 09:35
阅读 1549·2019-08-30 15:53
阅读 1166·2019-08-30 13:14
阅读 605·2019-08-30 13:09
阅读 2520·2019-08-30 10:57
阅读 3333·2019-08-29 13:18