菲洛嘉青春动能素135HA FILLMED® NCTF 135HA LED指示灯的常见故障分析 智微智能 Elkhartlake K075终端,零售产业新选择 天空蓝拓客管理系统详细介绍版 muso公链项目 天使计划 是什么?[秘] 独家揭秘最前沿的家装“黑科技”——掌赋 天博体育欧洲杯特辑,东道主法兰西的失意2016 亚马逊的送货侦察员 学习听起来像挡泥板 Google Comics Factory使ML变得容易 笑着说-男性或女性 Amazon Rekognition中更好的人脸检测 关于Spaun的真相-大脑模拟 两个聊天机器人彼此聊天-有趣又怪异 GANPaint:将AI用于艺术 WCF和WF给予社区 从耳朵到脸 所有神经网络的深层缺陷 蠕虫在尾巴上平衡杆子 Kickstarter上的OpenCV AI套件 TensorFlow-Google的开源AI和计算引擎 众包取代新闻工作者 Google的DeepMind学会玩街机游戏 哑机器人V智能机器人 .NET与.NET 5融为一体 Google的深度学习-语音识别 LInQer将.NET LINQ移植到Javascript 机器人TED演讲-新的图灵测试? GAN的发明者加入苹果 您的智能手机会监视您键入的内容 人工智能帮助改善国际象棋 Zalando Flair NLP库已更新 TensorFlow 1.5包含移动版本 AlphaGo输了一场比赛-比分3-1 虚拟机器学习峰会 Microsoft开源AI调试工具 SharePoint走向移动 F#4.0发出文化变革的信号 克里斯蒂拍卖AI艺术品 人工智能如何区分 Facebook在蒙特利尔的新AI实验室 Mozilla想要您的声音 微软使用极深的神经网络赢得ImageNet 建立AI合作伙伴关系 .NET Core 3-Microsoft几乎回到了起点 神经网络-更好的销售商? Google使用AI查找您的住所 虹膜-适用于Android的Siri证明苹果没有优势 TensorFlow 2提供更快的模型训练 深度学习研究人员将为Google工作
您的位置:首页 >大数据 >

vue源码(十三) 数组下标改变值的响应式误区以及实现

前言

相信大家都知道,在vue2.0x中,使用数组下标改变值时,是不会触发响应式的

以下来自:Vue官方文档

Vue 不能检测以下数组的变动:

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:vm.items.length = newLength

但是其实还是有特殊情况的,让我们来分析分析

正常情况

让我们看看,使用数组下标直接改变数组元素的值,是否会响应式变化(按照官方文档是不可行的)

<div id="app"><ul><li v-for="item in list">{{item}}</li></ul></div>
const app = new Vue({el: '#app',data: { list: ['4', '5', '6']},mounted () {this.list[1] = '6'console.log(this.list)}})

这里通过this.list[1],也就是数组下标改变数组值时,让我们看看是否会响应式变化

下面为页面中的打印结果

在这里插入图片描述 在这里插入图片描述 通过打印结果,可以看出,this.list[1]是成功改变了data中list数组下标为1的值的,因为打印出的list的索引为1的值为"6"了

但是在页面中,并没有将这个6渲染出来,所以可以得出这一个赋值不是响应式的,并不会让界面跟着渲染

所以可以得出结论

通过数组下标改变元素值时,是不会响应式变化的

误区

而这个时候,会有些人走入一个误区

这个误区,可以看看代码

<div id="app"><ul><li v-for="item in objectList">{{item}}</li></ul></div>
const app = new Vue({el: '#app',data: {objectList: [{ value: 1, id: 1 },{ value: 2, id: 2 },{ value: 3, id: 3 },]},mounted () {this.objectList[1].value = 3}})

打印结果如下

在这里插入图片描述 这里就是大多数人的误区:你看!我用的也是数组下标,为什么这样改变的就会是响应式的呢?

其实这就是被官方文档绕进去了,看到数组下标就以为不是响应式,其实这里的数组下标只是获取到那个对象而已,而这个对象却是响应式的,所以你改变这个对象的值,当然也就响应式的变化了

那么进入误区的人,现在又进入了一个难以理解的点,为什么数组的对象元素就是响应式的呢?

这时候,可以从源码中来看

数组中对象元素的响应式

说到响应式,之前也分析过,从initData中执行observe函数,那么从observe开始看

export function observe (value: any, asRootData: ?boolean): Observer | void {// 判断是否为对象 判断是否为VNodeif (!isObject(value) || value instanceof VNode) {// 如果不是对象 或者 是实例化的Vnode 也就是vdomreturn}// 观察者 创建一个oblet ob: Observer | void// 检测是否有缓存obif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {// 直接将缓存的ob拿到ob = value.__ob__} else if (// 如果没有缓存的obshouldObserve && // 当前状态是否能添加观察者!isServerRendering() && // 不是ssr(Array.isArray(value) || isPlainObject(value)) && // 是对象或数组Object.isExtensible(value) && // 是否可以在它上面添加新的属性!value._isVue// 是否是Vue实例) {// new 一个Observer实例 复制给ob// 也是把value进行响应化,并返回一个ob实例,还添加了__ob__属性ob = new Observer(value)}// 如果作为根data 并且当前ob已有值if (asRootData && ob) {// ++ob.vmCount++}// 最后返回ob,也就是一个Obesrver实例 有这个实例就有__ob__,然后其对象和数组都进行了响应化return ob}

刚开始初始化时,内部肯定是没有ob这个属性的,所以会执行new Observer

因此继续看class Observer的构造函数

constructor (value: any) {this.value = value// 这里会new一个Dep实例this.dep = new Dep()this.vmCount = 0// def添加__ob__属性,value必须是对象def(value, '__ob__', this)// 判断当前value是不是数组if (Array.isArray(value)) {// 如果是数组// 检测当前浏览器中有没有Array.prototype// 当能使用__proto__时// 这里完成了数组的响应式,不使用这7个方法都不会触发响应式if (hasProto) {// 有原型时将arrayMethods覆盖value.__proto__,也就是把增加了副作用的7个数组方法放了进来protoAugment(value, arrayMethods)} else {// 复制增加了副作用的7个数组方法copyAugment(value, arrayMethods, arrayKeys)}// 遍历将数组所有元素进行observethis.observeArray(value)} else {// 不是数组是对象,执行这里// walk就是给对象的所有key进行响应化this.walk(value)}}

分析如图

在这里插入图片描述 可以看到,如果是数组,会首先对7个数组方法进行添加副作用,然后执行observeArray函数

所以继续看observeArray这个函数

// 遍历将数组所有元素进行observeobserveArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}

这段代码大家应该都能一下就知道在干啥,遍历数组,对所有数组元素执行observe函数

那么继续看observe函数

export function observe (value: any, asRootData: ?boolean): Observer | void {// 判断是否为对象 判断是否为VNodeif (!isObject(value) || value instanceof VNode) {// 如果不是对象 或者 是实例化的Vnode 也就是vdomreturn}// 观察者 创建一个oblet ob: Observer | void// 检测是否有缓存obif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {// 直接将缓存的ob拿到ob = value.__ob__} else if (// 如果没有缓存的obshouldObserve && // 当前状态是否能添加观察者!isServerRendering() && // 不是ssr(Array.isArray(value) || isPlainObject(value)) && // 是对象或数组Object.isExtensible(value) && // 是否可以在它上面添加新的属性!value._isVue// 是否是Vue实例) {// new 一个Observer实例 复制给ob// 也是把value进行响应化,并返回一个ob实例,还添加了__ob__属性ob = new Observer(value)}// 如果作为根data 并且当前ob已有值if (asRootData && ob) {// ++ob.vmCount++}// 最后返回ob,也就是一个Obesrver实例 有这个实例就有__ob__,然后其对象和数组都进行了响应化return ob}

分析如图

在这里插入图片描述 可以看到,observe进行了一次处理,不是对象的他就不会执行observe去添加ob属性

如果是对象的话,就会执行new Observer,这时候又回到之前的那个构造函数,只不过这次执行的下面的else分支,这次不是数组了,所以会执行this.walk

如图

在这里插入图片描述

其实walk就是给对象所有的key进行defineReactive添加数据劫持(这里不细说,反正这样已经完成响应式了,不懂得可以看这篇)

所以数组中对象元素都是响应式的,因此下次再碰到这种情况,就不要再进入误区了,这里并没有通过数组下标去改变值,而是获取相应的对象,而这个对象是响应式的!

又一个误区

还有一个误区,也让很多人认为通过数组下标改变值会是响应式,直接看代码

<div id="app"><ul><li v-for="item in objectList">{{item.value}}</li></ul><ul><li v-for="item in list">{{item}}</li></ul></div>
const app = new Vue({el: '#app',data: {objectList: [{ value: 1, id: 1 },{ value: 2, id: 2 },{ value: 3, id: 3 },],list: ['4', '5', '6']},mounted () {this.list[1] = '6'this.objectList[1].value = 3}})

这里一个通过数组下标改变了一个值,又通过数组下标获取一个对象并改变对象的值

由之前得出的结论,第一个值的改变是不会响应式变化的,第二个值的改变会响应式变化

所以页面上应该显示1,3,3, 4, 5,6

但是打印结果如下

在这里插入图片描述 阿这,跟我们预想的不一样啊,为什么通过数组下标的this.list[1] = '6’响应式变化了呢?

这就是又一个误区

其实这里我们可以在this.list[1]='6’后,打印一下this.list

mounted () {this.list[1] = '6'console.log(this.list)this.objectList[1].value = 3}

在这里插入图片描述 可以看到,这时的list值是成功改变的了,只是没有响应式的渲染在页面上

所以继续执行后面的this.objectList[1].value = 3时,这是一个响应化的操作,因此当值改变时,会触发setter中的dep.notify,去通知视图更新,经过一系列vdom,patch后,vue会发现data中有个list数组中一个元素值也改变了,因此也会将当前改变的值和list数组中改变的那个值都给重新渲染了

因此这里的数组下标改变值的响应化其实是后一句执行 this.objectList[1].value = 3,这一句通知视图更新时,会检测到前一个list数组中值有变化,但是视图中没更新,因此才会一起渲染

实现数组下标改变值的响应式

其实,vue2.0x是可以实现数组下标改变值的响应式的,且非常简单

前面我们也知道,通过数组下标改变值,能成功改变data中的值,但是因为没有监听,因此不会触发响应式更新

那么我们可以通过添加属性监听完成这一操作,众所周知,数组索引也是数组的一个属性,因此让我们重写一下对数组的操作

constructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this)if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}// this.observeArray(value)this.walkArray(value)} else {this.walk(value)}}walkArray (obj: Object) {obj.forEach((item, index) => {defineReactive(obj, index)})}

我把原本的observeArray注释了,换成了自己写的walkArray,而walkArray就是实现了对索引的监听。

这时候就已经实现成功了,看看效果

<div id="app"><ul><li v-for="item in list">{{item}}</li></ul></div>
const app = new Vue({el: "#app",data: {list: ['1', '2', '3']},mounted (){this.list[1] = 4}})

来看看这次,通过数组下标改变值会不会响应式变化

打印如下图

在这里插入图片描述 好的,成功实现了!

疑问

这时肯定都会有个疑问,如果这么简单就能实现这个数组下标的响应式的话,为什么尤大不写进去呢?

这里尤大有回答过:为什么vue没有提供对数组属性的监听?

在这里插入图片描述 在这里插入图片描述 既然尤大都说了性能问题,那不写入数组下标响应式肯定是他们经过考量之后的决定了

所以现在只要知道怎么实现以及避免这两个误区即可

总结

这也是在群里有人问了之后,我也踩进了误区,然后经过看源码后,算是对其有了清晰的认识了

所以作为一篇随笔写在这分享给大家,也算是避雷吧哈哈哈

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。