vue中是怎样监听数组变化的


Posted in Javascript onOctober 24, 2020

我们知道通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,这就会造成使用上述方法改变数组后,页面上并不能及时体现这些变化,也就是数组数据变化不是响应式的(对上述不了解的可以参考这篇文章)。但实际用vue开发时,对于响应式数组,使用push、splice、pop等方法改变数组时,页面会及时体现这种变化,那么vue中是如何实现的呢?

通过vue源码可以看出,vue重写了数组的push、splice、pop等方法。

// src/core/observer/array.js

// 获取数组的原型Array.prototype,上面有我们常用的数组方法
const arrayProto = Array.prototype
// 创建一个空对象arrayMethods,并将arrayMethods的原型指向Array.prototype
export const arrayMethods = Object.create(arrayProto)

// 列出需要重写的数组方法名
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
]
// 遍历上述数组方法名,依次将上述重写后的数组方法添加到arrayMethods对象上
methodsToPatch.forEach(function (method) {
 // 保存一份当前的方法名对应的数组原始方法
 const original = arrayProto[method]
 // 将重写后的方法定义到arrayMethods对象上,function mutator() {}就是重写后的方法
 def(arrayMethods, method, function mutator (...args) {
  // 调用数组原始方法,并传入参数args,并将执行结果赋给result
  const result = original.apply(this, args)
  // 当数组调用重写后的方法时,this指向该数组,当该数组为响应式时,就可以获取到其__ob__属性
  const ob = this.__ob__
  let inserted
  switch (method) {
   case 'push':
   case 'unshift':
    inserted = args
    break
   case 'splice':
    inserted = args.slice(2)
    break
  }
  if (inserted) ob.observeArray(inserted)
  // 将当前数组的变更通知给其订阅者
  ob.dep.notify()
  // 最后返回执行结果result
  return result
 })
})

从上面可以看出array.js中重写了数组的push、pop、shift、unshift、splice、sort、reverse七种方法,重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中。

重写完数组的上述7种方法外,我们还需要将这些重写的方法应用到数组上,因此在Observer构造函数中,可以看到在监听数据时会判断数据类型是否为数组。当为数组时,如果浏览器支持__proto__,则直接将当前数据的原型__proto__指向重写后的数组方法对象arrayMethods,如果浏览器不支持__proto__,则直接将arrayMethods上重写的方法直接定义到当前数据对象上;当数据类型为非数组时,继续递归执行数据的监听。

// src/core/observer/index.js
export class Observer {
 ...
 constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  if (Array.isArray(value)) {
   if (hasProto) {
    protoAugment(value, arrayMethods)
   } else {
    copyAugment(value, arrayMethods, arrayKeys)
   }
   this.observeArray(value)
  } else {
   this.walk(value)
  }
 }
 ...
}
function protoAugment (target, src: Object) {
 /* eslint-disable no-proto */
 target.__proto__ = src
 /* eslint-enable no-proto */
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
 for (let i = 0, l = keys.length; i < l; i++) {
  const key = keys[i]
  def(target, key, src[key])
 }
}

经过上述处理后,对于数组,当我们调用其方法处理数组时会按照如下原型链来获取数组方法:

vue中是怎样监听数组变化的

对于响应式数组,当浏览器支持__proto__属性时,使用push等方法时先从其原型arrayMethods上寻找push方法,也就是重写后的方法,处理之后数组的变化会通知到其订阅者,更新页面,当在arrayMethods上查询不到时会向上在Array.prototype上查询;当浏览器不支持__proto__属性时,使用push等方法时会先从数组自身上查询,如果查询不到会向上再Array.prototype上查询。

对于非响应式数组,当使用push等方法时会直接从Array.prototype上查询。

值得一提的是源码中通过判断浏览器是否支持__proto__来分别使用protoAugment和copyAugment 方法将重写后的数组方法应用到数组中,这是因为对于IE10及以下的IE浏览器是不支持__proto__属性的:

上述截图参考于Vue源码解析五——数据响应系统

结论:

在将数组处理成响应式数据后,如果使用数组原始方法改变数组时,数组值会发生变化,但是并不会触发数组的setter来通知所有依赖该数组的地方进行更新,为此,vue通过重写数组的某些方法来监听数组变化,重写后的方法中会手动触发通知该数组的所有依赖进行更新。

如果我的内容能对你有所帮助,我就很开心啦!

以上就是vue中是怎样监听数组变化的的详细内容,更多关于vue 监听数组变化的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
根据分辨率不同,调用不同的css文件
Aug 25 Javascript
超级兔子让浮动层消失的前因后果
Mar 09 Javascript
jquery 上下滚动广告
Jun 17 Javascript
JavaScript Math.ceil 方法(对数值向上取整)
Jan 09 Javascript
纯js实现仿QQ邮箱弹出确认框
Apr 29 Javascript
在JavaScript中使用JSON数据
Feb 15 Javascript
JS闭包、作用域链、垃圾回收、内存泄露相关知识小结
May 16 Javascript
jQuery遍历节点树方法分析
Sep 08 Javascript
关于js二维数组和多维数组的定义声明(详解)
Oct 02 Javascript
jQuery实现的模拟弹出窗口功能示例
Nov 24 Javascript
mongodb初始化并使用node.js实现mongodb操作封装方法
Apr 02 Javascript
vue移动端城市三级联动组件使用详解
Jul 26 Javascript
JSON stringify方法原理及实例解析
Oct 23 #Javascript
vue中使用腾讯云Im的示例
Oct 23 #Javascript
vue中使用router全局守卫实现页面拦截的示例
Oct 23 #Javascript
vue使用video插件vue-video-player详解
Oct 23 #Javascript
vue-video-player视频播放器使用配置详解
Oct 23 #Javascript
Javascript实现贪吃蛇小游戏(含详细注释)
Oct 23 #Javascript
Vue toFixed保留两位小数的3种方式
Oct 23 #Javascript
You might like
本地计算机无法启动Apache故障处理
2014/08/08 PHP
Js+XML 操作
2006/09/20 Javascript
jQuery 自动增长的文本输入框实现代码
2010/04/02 Javascript
初识SmartJS - AOP三剑客
2014/06/08 Javascript
深入浅析react native es6语法
2015/12/09 Javascript
详解Bootstrap四种图片样式
2016/01/04 Javascript
js获取指定字符前/后的字符串简单实例
2016/10/27 Javascript
详解微信小程序开发之下拉刷新 上拉加载
2016/11/24 Javascript
简单的渐变轮播插件
2017/01/12 Javascript
详解使用fetch发送post请求时的参数处理
2017/04/05 Javascript
JavaScript中Require调用js的实例分享
2017/10/27 Javascript
node.js基于fs模块对系统文件及目录进行读写操作的方法详解
2017/11/10 Javascript
vue.js项目 el-input 组件 监听回车键实现搜索功能示例
2018/08/25 Javascript
mpvue中使用flyjs全局拦截的实现代码
2018/09/13 Javascript
其实你可以少写点if else与switch(推荐)
2019/01/10 Javascript
JS实现根据数组对象的某一属性排序操作示例
2019/01/14 Javascript
简单两步使用node发送qq邮件的方法
2019/03/01 Javascript
JS三级联动代码格式实例详解
2019/12/30 Javascript
JS apply用法总结和使用场景实例分析
2020/03/14 Javascript
小程序使用分包的示例代码
2020/03/23 Javascript
小程序实现图片移动缩放效果
2020/05/26 Javascript
探索Python3.4中新引入的asyncio模块
2015/04/08 Python
python中偏函数partial用法实例分析
2015/07/08 Python
学生信息管理系统python版
2018/10/17 Python
Python read函数按字节(字符)读取文件的实现
2019/07/03 Python
Python 实现文件读写、坐标寻址、查找替换功能
2019/09/11 Python
亚马逊墨西哥站:Amazon.com.mx
2018/08/26 全球购物
Vans(范斯)新西兰官方网站:美国原创极限运动品牌
2020/09/19 全球购物
.NET初级开发工程师面试题(包括Javascript)
2012/08/22 面试题
村干部培训班主持词
2014/03/28 职场文书
党政领导班子民主生活会整改措施
2014/09/18 职场文书
学校国庆节活动总结
2015/03/23 职场文书
党员干部廉洁自律承诺书
2015/04/28 职场文书
2016元旦主持人经典开场白台词
2015/12/03 职场文书
Python批量将csv文件转化成xml文件的实例
2021/05/10 Python
MySQL系列之二 多实例配置
2021/07/02 MySQL