Vue 2.6.13 源码解析(五) 感知变化以发起通知
admin
2024-01-20 04:20:41
0

文章目录

  • 前言
  • 一、Object.defineProperty()
  • 二、观察者Observer
  • 三、对象类型的响应式处理
    • 1.收集依赖
    • 2.通知依赖
  • 四、数组类型的响应式处理
    • 1.依赖收集
    • 2.通知依赖
  • 总结


前言

上次捋清了通知行为的执行, 但也引出一个问题, 何时需要通知, 何时需要更新.

所以从更早的地方向通知操作捋.


一、Object.defineProperty()

开始学习Vue时, 就已经听说过, Vue似乎通过通过Object.defineProperty为每一个属性设置具备自动反馈机制的读写方法来精确感应属性的变动.

Object.defineProperty -MDN: Object.defineProperty -MDN

经过这个方法处理的属性会在受读取时调用get(), 受写入时调用set()并可取到写入值.
基于这个特性为每个数据执行Object.defineProperty并添加写入反馈机制及时通知所有该数据的依赖(该数据的依赖指的是该数据的使用者)做出更新.
做这种响应式处理, 以使该数据成为响应式数据.


二、观察者Observer

数据经过响应式处理之后会在受写入时执行set(), 如果在set()时顺带做出可以被观察监听的行为, 就可以将自己的变化告知外界.
外界也需要一个观察者, 实时监控这种反馈并发起操作.
因为数组类型不具备set, 所以针对数组和对象两种类型会在此处被交付不同的方法处理, 但是除了数组使用一种特殊方法来发起写入反馈, 他者的最终目的依然是为每个属性执行响应式处理.

这一步还为每个数据创建一个依赖对象Dep,(这里的依赖依旧指依赖该数据者)像一个功能不全的用户数据库, 依赖该数据者将被存储于该依赖对象的subs数组, 并且Dep内提供了系列方法, 只要调用就可以完成对依赖的增删改.

export class Observer {value: any;dep: Dep;vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this)if (Array.isArray(value)) {if (hasProto) { // 判定浏览器是否支持protoprotoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)} else {this.walk(value)}}walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}observeArray (items: Array) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}
}

三、对象类型的响应式处理

1.收集依赖

收集所有对该数据依赖有依赖者, 纳入该数据的Dep进行管理, 在依赖者和被依赖者间建立联系.
依赖者一旦访问其依赖的数据必然会触发该数据的get(), 那么在数据的get()里理所当然也可以获取到依赖者, 此时将该依赖者纳入Dep管制比较合适:
src/core/observer/index.js>defineReactive

访问者并不会被直接加入依赖, 而是将访问者的Watcher加入依赖:

notify () {const subs = this.subs.slice()if (process.env.NODE_ENV !== 'production' && !config.async) {subs.sort((a, b) => a.id - b.id)}for (let i = 0, l = subs.length; i < l; i++) {subs[i].update() // 调的是Watcher方法, 所以subs[i]应当为一个Watcher}
}

通知的时候通知Dep的所有依赖即所有订阅Dep的所有Watcher, Watcher能接收通知并做出更新.

get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) { // Watcher执行时会执行get(), 此时调用pushTarget将Dep.target设为该Watcher// 以前这步是用window.target完成// 将访问者加入依赖dep.depend()}return value
}
depend () {if (Dep.target) {Dep.target.addDep(this)}
}

2.通知依赖

发生变化后set触发, 调用

dep.notify()

向dep中的所有Watcher发起更新通知.


四、数组类型的响应式处理

1.依赖收集

observerArray线:
对每个元素执行observe
src/core/observer/index.js/>Observer

observeArray (items: Array) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}
}

为数组元素设立观察者Observer并返回Observer实例

export function observe (value: any, asRootData: ?boolean): Observer | void {let ob: Observer | voidif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {ob = new Observer(value)}return ob
}

如果子元素不再是数组, 就走defineReactive, 然后就到了defineProperty, 收集依赖依然需要在get()时候.
defineReactive中尝试创建childOb, 如果defineReactive传入了数组, 此时通过为数组执行observe拿到该数组的观察者对象, 之后数组观察者对象中的dep调用depend收集该数组的依赖.


2.通知依赖

需要解决数组类型不能set不能感应写入的问题.
为此在数组原型上的方法的基础上进行了改动, 为所有能对原数组产生影响的方法增加通知机制.

if (Array.isArray(value)) {if (hasProto) { // 判定浏览器是否支持protoprotoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)
} else {this.walk(value)
}

改变这个数组类型数据在数组原型上的原生数组方法, 对该数组执行的数组方法实际上都是在给mutatormethod参数, 但是不论传什么, 最后都会发起通知:

const ob = this.__ob__ // __ob__即该数据的Observer对象
ob.dep.notify() // 发起通知

数组原型上的方法更改:
src/core/observer/array.js

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)const methodsToPatch = [ // 当前能对原数组造成影响的所有数组方法'push','pop','shift','unshift','splice','sort','reverse'
]/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__ // 此即数组的观察者对象let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)// notify changeob.dep.notify() // 数组观察者对象的dep调用notify发起通知.return result})
})

总结

上一篇: Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅

相关内容

热门资讯

14岁两个女生,一米深的河里手... 14岁两个女生,一米深的河里手绑手溺亡,疑点重重,怎么回事?这个是因为她们忍受不了家里面的重男轻女,...
帮解几道五年级数学题!急急急~... 帮解几道五年级数学题!急急急~快快快!(要解)52=2*2*13
问候幽默语 问候幽默语问候语幽默就应该问他开心快乐每一天,今天岁数是过的,又是非常的快乐的一天,非常充实。你现在...
想来其中自多情,你可知我意难平... 想来其中自多情,你可知我意难平何解!跟你说这句话的人想告诉你,他对你很用心用情,可是你却不在意,或许...
火影忍者所有op对应的集数 火影忍者所有op对应的集数一共9+20首。Naruto:“R★O★C★K★S”,第1至25集。“遥か...
求一本女主角很美好的名著 求一本女主角很美好的名著希望女主角内向些 大家认识的最善良 甜美的女主角 都推荐给我吧书最好是名著 ...
书名中带有 学生 的小说 书名中带有 学生 的小说富的生命。 “美”是生活的更新者,元气之恢复,健康之促进者,甚至可以说是生机...
开在上海乐高乐园里的比亚迪驾驶... ①首家:比亚迪与上海乐高乐园度假区达成的是战略合作伙伴关系,也是乐高乐园在全球合作的首个中国汽车品牌...
原创 林... 自从步入婚姻殿堂、迎来小宝贝后,林志玲的工作节奏悄然放慢,感觉她就像夏日的蜗牛,慢悠悠地享受生活。不...
武汉口碑最好的旅行社排名,靠谱... 在武汉这座充满魅力的城市,旅游市场蓬勃发展,旅行社众多,让人眼花缭乱。对于计划出游的朋友来说,选择一...
我是上海人,去了趟天津,有6点... 作为一个土生土长的上海人,我一直对北方城市充满好奇。上个月趁着年假,我独自踏上了天津之旅。本以为同为...
2025中国(青岛)-东盟经贸... 7月3日至5日,以“蓝色伙伴共赢未来”为主题的2025中国(青岛)-东盟经贸合作与人文交流活动在青岛...
开业首日600余人涌入“桃花源... “桃花源漂流”将速度与清凉完美融合。 连日高温,催热避暑经济,2025建德“17℃夏日漂流季”7月...
中盛旅投文化发展(北京)有限公... 中盛旅投文化发展(北京)有限公司:自然与现代交融 在这个世界上,自然与现代的交融总是能创造出令人惊叹...
在哈利波特与混血王子中,为什么... 在哈利波特与混血王子中,为什么罗恩在吃饭时自己头上会下雪才不是,才不是因为巴不得分手高兴所以挥舞魔杖...
八月第二周(8.6~8.12日... 八月第二周(8.6~8.12日)会得到月老牵线,桃花运降临的星座究竟是谁?八月第二周桃花运很好的星座...
足球1x2是什么意思出款多快? 足球1x2是什么意思出款多快?功能介绍: 游戏趣味变声,在游戏语音时,对您输入的声音进行处理,进行...
虚度半生事无为,轮回一世多悲忧... 虚度半生事无为,轮回一世多悲忧,不怨世人笑我痴,只恨自苦三钱命,啥意思虚度半生事无为,轮回一世多悲忧...
写人作文的方法和技巧 写人作文的方法和技巧写人作文的方法和技巧如下:1、肖像描写(外貌描写)通过对容貌、神情、姿态、服饰、...
保安人员培训记录有什么内容? 保安人员培训记录有什么内容?您好,玖玖泰丰验厂网可免费为您提供验厂咨询,十年验厂老品牌,百分百通过验...