济南asp网站制作公司浙江振升建设有限公司网站
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:48
当前位置: 首页 > news >正文
济南asp网站制作公司,浙江振升建设有限公司网站,网站建设ps模板下载,智慧城市目录
- 前言
- 在哪里收集依赖
- 使Array型数据可观测 3.1 思路分析 3.2 数组方法拦截器 3.3 使用拦截器
- 再谈依赖收集 4.1 把依赖收集到哪里 4.2 如何收集依赖 4.3 如何通知依赖
- 深度侦测
- 数组新增元素的侦测
- 不足之处
- 总结 1. 前言 上一篇文…目录
- 前言
- 在哪里收集依赖
- 使Array型数据可观测 3.1 思路分析 3.2 数组方法拦截器 3.3 使用拦截器
- 再谈依赖收集 4.1 把依赖收集到哪里 4.2 如何收集依赖 4.3 如何通知依赖
- 深度侦测
- 数组新增元素的侦测
- 不足之处
- 总结 1. 前言 上一篇文章中我们介绍了Object数据的变化侦测方式本篇文章我们来看一下对Array型数据的变化Vue是如何进行侦测的。 为什么Object数据和Array型数据会有两种不同的变化侦测方式 这是因为对于Object数据我们使用的是JS提供的对象原型上的方法Object.defineProperty而这个方法是对象原型上的所以Array无法使用这个方法所以我们需要对Array型数据设计一套另外的变化侦测机制。 万变不离其宗虽然对Array型数据设计了新的变化侦测机制但是其根本思路还是不变的。那就是还是在获取数据时收集依赖数据变化时通知依赖更新。 下面我们就通过源码来看看Vue对Array型数据到底是如何进行变化侦测的。
- 在哪里收集依赖 首先还是老规矩我们得先把用到Array型数据的地方作为依赖收集起来那么第一问题就是该在哪里收集呢 其实Array型数据的依赖收集方式和Object数据的依赖收集方式相同都是在getter中收集。那么问题就来了不是说Array无法使用Object.defineProperty方法吗无法使用怎么还在getter中收集依赖呢 其实不然我们回想一下平常在开发的时候在组件的data中是不是都这么写的 data(){return {arr:[1,2,3]} }想想看arr这个数据始终都存在于一个object数据对象中而且我们也说了谁用到了数据谁就是依赖那么要用到arr这个数据是不是得先从object数据对象中获取一下arr数据而从object数据对象中获取arr数据自然就会触发arr的getter所以我们就可以在getter中收集依赖。 总结一句话就是Array型数据还是在getter中收集依赖。
- 使Array型数据可观测 上一章节中我们知道了Array型数据还是在getter中收集依赖换句话说就是我们已经知道了Array型数据何时被读取了。 回想上一篇文章中介绍Object数据变化侦测的时候我们先让Object数据变的可观测即我们能够知道数据什么时候被读取了、什么时候发生变化了。同理对于Array型数据我们也得让它变的可观测目前我们已经完成了一半可观测即我们只知道了Array型数据何时被读取了而何时发生变化我们无法知道那么接下来我们就来解决这一问题当Array型数据发生变化时我们如何得知 3.1 思路分析 Object的变化时通过setter来追踪的只有某个数据发生了变化就一定会触发这个数据上的setter。但是Array型数据没有setter怎么办 我们试想一下要想让Array型数据发生变化那必然是操作了Array而JS中提供的操作数组的方法就那么几种我们可以把这些方法都重写一遍在不改变原有功能的前提下我们为其新增一些其他功能例如下面这个例子 let arr [1,2,3] arr.push(4) Array.prototype.newPush function(val){console.log(arr被修改了)this.push(val) } arr.newPush(4)在上面这个例子中我们针对数组的原生push方法定义个一个新的newPush方法这个newPush方法内部调用了原生push方法这样就保证了新的newPush方法跟原生push方法具有相同的功能而且我们还可以在新的newPush方法内部干一些别的事情比如通知变化。 是不是很巧妙Vue内部就是这么干的。 3.2 数组方法拦截器 基于上一小节的思想在Vue中创建了一个数组方法拦截器它拦截在数组实例与Array.prototype之间在拦截器内重写了操作数组的一些方法当数组实例使用操作数组方法时其实使用的是拦截器中重写的方法而不再使用Array.prototype上的原生方法。如下图所示 经过整理Array原型中可以改变数组自身内容的方法有7个分别是push,pop,shift,unshift,splice,sort,reverse。那么源码中的拦截器代码如下 // 源码位置/src/core/observer/array.jsconst arrayProto Array.prototype // 创建一个对象作为拦截器 export const arrayMethods Object.create(arrayProto)// 改变数组自身内容的7个方法 const methodsToPatch [push,pop,shift,unshift,splice,sort,reverse ]/*** Intercept mutating methods and emit events/ methodsToPatch.forEach(function (method) {const original arrayProto[method] // 缓存原生方法Object.defineProperty(arrayMethods, method, {enumerable: false,configurable: true,writable: true,value:function mutator(…args){const result original.apply(this, args)return result}}) }) 在上面的代码中首先创建了继承自Array原型的空对象arrayMethods接着在arrayMethods上使用object.defineProperty方法将那些可以改变数组自身的7个方法遍历逐个进行封装。最后当我们使用push方法的时候其实用的是arrayMethods.push而arrayMethods.push就是封装的新函数mutator也就后说实标上执行的是函数mutator而mutator函数内部执行了original函数这个original函数就是Array.prototype上对应的原生方法。 那么接下来我们就可以在mutator函数中做一些其他的事比如说发送变化通知。 3.3 使用拦截器 在上一小节的图中我们把拦截器做好还不够还要把它挂载到数组实例与Array.prototype之间这样拦截器才能够生效。 其实挂载不难我们只需把数据的proto属性设置为拦截器arrayMethods即可源码实现如下 // 源码位置/src/core/observer/index.js export class Observer {constructor (value) {this.value valueif (Array.isArray(value)) {const augment hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)} else {this.walk(value)}} } // 能力检测判断proto是否可用因为有的浏览器不支持该属性 export const hasProto proto in {}const arrayKeys Object.getOwnPropertyNames(arrayMethods)/** Augment an target Object or Array by intercepting* the prototype chain using proto/ function protoAugment (target, src: Object, keys: any) {target.proto src }/** Augment an target Object or Array by defining* hidden properties./ / istanbul ignore next */ function copyAugment (target: Object, src: Object, keys: Arraystring) {for (let i 0, l keys.length; i l; i) {const key keys[i]def(target, key, src[key])} } 上面代码中首先判断了浏览器是否支持proto如果支持则调用protoAugment函数把value.proto arrayMethods如果不支持则调用copyAugment函数把拦截器中重写的7个方法循环加入到value上。 拦截器生效以后当数组数据再发生变化时我们就可以在拦截器中通知变化了也就是说现在我们就可以知道数组数据何时发生变化了OK以上我们就完成了对Array型数据的可观测。
- 再谈依赖收集 4.1 把依赖收集到哪里 在第二章中我们说了数组数据的依赖也在getter中收集而给数组数据添加getter/setter都是在Observer类中完成的所以我们也应该在Observer类中收集依赖源码如下 // 源码位置/src/core/observer/index.js export class Observer {constructor (value) {this.value valuethis.dep new Dep() // 实例化一个依赖管理器用来收集数组依赖if (Array.isArray(value)) {const augment hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)} else {this.walk(value)}} }上面代码中在Observer类中实例化了一个依赖管理器用来收集数组依赖。 4.2 如何收集依赖 在第二章中我们说了数组的依赖也在getter中收集那么在getter中到底该如何收集呢这里有一个需要注意的点那就是依赖管理器定义在Observer类中而我们需要在getter中收集依赖也就是说我们必须在getter中能够访问到Observer类中的依赖管理器才能把依赖存进去。源码是这么做的 function defineReactive (obj,key,val) {let childOb observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get(){if (childOb) {childOb.dep.depend()}return val;},set(newVal){if(val newVal){return}val newVal;dep.notify() // 在setter中通知依赖更新}}) }/*** Attempt to create an observer instance for a value,* returns the new observer if successfully observed,* or the existing observer if the value already has one.* 尝试为value创建一个0bserver实例如果创建成功直接返回新创建的Observer实例。* 如果 Value 已经存在一个Observer实例则直接返回它/ export function observe (value, asRootData){if (!isObject(value) || value instanceof VNode) {return}let obif (hasOwn(value, ob) value.ob instanceof Observer) {ob value.ob} else {ob new Observer(value)}return ob }在上面代码中我们首先通过observe函数为被获取的数据arr尝试创建一个Observer实例在observe函数内部先判断当前传入的数据上是否有ob属性因为在上篇文章中说了如果数据有ob属性表示它已经被转化成响应式的了如果没有则表示该数据还不是响应式的那么就调用new Observer(value)将其转化成响应式的并把数据对应的Observer实例返回。 而在defineReactive函数中首先获取数据对应的Observer实例childOb然后在getter中调用Observer实例上依赖管理器从而将依赖收集起来。 4.3 如何通知依赖 到现在为止依赖已经收集好了并且也已经存放好了那么我们该如何通知依赖呢 其实不难在前文说过我们应该在拦截器里通知依赖要想通知依赖首先要能访问到依赖。要访问到依赖也不难因为我们只要能访问到被转化成响应式的数据value即可因为vaule上的ob就是其对应的Observer类实例有了Observer类实例我们就能访问到它上面的依赖管理器然后只需调用依赖管理器的dep.notify()方法让它去通知依赖更新即可。源码如下 /** Intercept mutating methods and emit events*/ methodsToPatch.forEach(function (method) {const original arrayProto[method]def(arrayMethods, method, function mutator (…args) {const result original.apply(this, args)const ob this.ob// notify changeob.dep.notify()return result}) })上面代码中由于我们的拦截器是挂载到数组数据的原型上的所以拦截器中的this就是数据value拿到value上的Observer类实例从而你就可以调用Observer类实例上面依赖管理器的dep.notify()方法以达到通知依赖的目的。 OK以上就基本完成了Array数据的变化侦测。
- 深度侦测 在前文所有讲的Array型数据的变化侦测都仅仅说的是数组自身变化的侦测比如给数组新增一个元素或删除数组中一个元素而在Vue中不论是Object型数据还是Array型数据所实现的数据变化侦测都是深度侦测所谓深度侦测就是不但要侦测数据自身的变化还要侦测数据中所有子数据的变化。举个例子 let arr [{name:NLRXage:18} ]数组中包含了一个对象如果该对象的某个属性发生了变化也应该被侦测到这就是深度侦测。 这个实现起来比较简单源码如下 export class Observer {value: any;dep: Dep;constructor (value: any) {this.value valuethis.dep new Dep()def(value, ob, this)if (Array.isArray(value)) {const augment hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)this.observeArray(value) // 将数组中的所有元素都转化为可被侦测的响应式} else {this.walk(value)}}/*** Observe a list of Array items.*/observeArray (items: Arrayany) {for (let i 0, l items.length; i l; i) {observe(items[i])}} }export function observe (value, asRootData){if (!isObject(value) || value instanceof VNode) {return}let obif (hasOwn(value, ob) value.ob instanceof Observer) {ob value.ob} else {ob new Observer(value)}return ob }在上面代码中对于Array型数据调用了observeArray()方法该方法内部会遍历数组中的每一个元素然后通过调用observe函数将每一个元素都转化成可侦测的响应式数据。 而对应object数据在上一篇文章中我们已经在defineReactive函数中进行了递归操作。
- 数组新增元素的侦测 对于数组中已有的元素我们已经可以将其全部转化成可侦测的响应式数据了但是如果向数组里新增一个元素的话我们也需要将新增的这个元素转化成可侦测的响应式数据。 这个实现起来也很容易我们只需拿到新增的这个元素然后调用observe函数将其转化即可。我们知道可以向数组内新增元素的方法有3个分别是push、unshift、splice。我们只需对这3中方法分别处理拿到新增的元素再将其转化即可。源码如下 /*** 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.oblet insertedswitch (method) {case push:case unshift:inserted args // 如果是push或unshift方法那么传入参数就是新增的元素breakcase splice:inserted args.slice(2) // 如果是splice方法那么传入参数列表中下标为2的就是新增的元素break}if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式// notify changeob.dep.notify()return result}) })在上面拦截器定义代码中如果是push或unshift方法那么传入参数就是新增的元素;如果是splice方法那么传入参数列表中下标为2的就是新增的元素拿到新增的元素后就可以调用observe函数将新增的元素转化成响应式的了。
- 不足之处 前文中我们说过对于数组变化侦测是通过拦截器实现的也就是说只要是通过数组原型上的方法对数组进行操作就都可以侦测到但是别忘了我们在日常开发中还可以通过数组的下标来操作数据如下 let arr [1,2,3] arr[0] 5; // 通过数组下标修改数组中的数据 arr.length 0 // 通过修改数组长度清空数组而使用上述例子中的操作方式来修改数组是无法侦测到的。 同样Vue也注意到了这个问题 为了解决这一问题Vue增加了两个全局API:Vue.set和Vue.delete这两个API的实现原理将会在后面学习全局API的时候说到。
- 总结 在本篇文章中首先我们分析了对于Array型数据也在getter中进行依赖收集其次我们发现当数组数据被访问时我们轻而易举可以知道但是被修改时我们却很难知道为了解决这一问题我们创建了数组方法拦截器从而成功的将数组数据变的可观测。接着我们对数组的依赖收集及数据变化如何通知依赖进行了深入分析最后我们发现Vue不但对数组自身进行了变化侦测还对数组中的每一个元素以及新增的元素都进行了变化侦测我们也分析了其实现原理。 以上就是对Array型数据的变化侦测分析。
- 上一篇: 济南 网站建设优质国外网站
- 下一篇: 济南mip网站建设系统开发需求文档
相关文章
-
济南 网站建设优质国外网站
济南 网站建设优质国外网站
- 技术栈
- 2026年03月21日
-
济南 规划 网站seo长尾关键词优化
济南 规划 网站seo长尾关键词优化
- 技术栈
- 2026年03月21日
-
技术支持 上海做网站去设计公司还是去企业
技术支持 上海做网站去设计公司还是去企业
- 技术栈
- 2026年03月21日
-
济南mip网站建设系统开发需求文档
济南mip网站建设系统开发需求文档
- 技术栈
- 2026年03月21日
-
济南seo网站排名关键词优化网络设计专业工资
济南seo网站排名关键词优化网络设计专业工资
- 技术栈
- 2026年03月21日
-
济南城市建设集团有限公司网站成都公司注册多少钱
济南城市建设集团有限公司网站成都公司注册多少钱
- 技术栈
- 2026年03月21日
