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 相关文章推荐
超级有用的13个基于jQuery的内容滚动插件和教程
Jul 31 Javascript
javascript组合使用构造函数模式和原型模式实例
Jun 04 Javascript
javascript删除数组重复元素的方法汇总
Jun 24 Javascript
javascript父子页面通讯实例详解
Jul 17 Javascript
JavaScript的jQuery库插件的简要开发指南
Aug 12 Javascript
jQuery实现响应鼠标背景变化的动态菜单效果代码
Aug 27 Javascript
vue组件表单数据回显验证及提交的实例代码
Aug 30 Javascript
详解Vue用cmd创建项目
Feb 12 Javascript
js使用文件流下载csv文件的实现方法
Jul 15 Javascript
layui form表单提交之后重新加载数据表格的方法
Sep 11 Javascript
微信小程序实现拨打电话功能的示例代码
Jun 28 Javascript
js 将多个对象合并成一个对象 assign方法的实现
Sep 24 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
PHP原生模板引擎 最简单的模板引擎
2012/04/25 PHP
php查询whois信息的方法
2015/06/08 PHP
php中使用websocket详解
2016/09/23 PHP
各浏览器中querySelector和querySelectorAll的实现差异分析
2012/05/23 Javascript
Jquery 模板数据绑定插件的使用方法详解
2013/07/08 Javascript
如何在指定的地方插入html内容和文本内容
2013/12/23 Javascript
checkbox全选所涉及到的知识点介绍
2013/12/31 Javascript
jquery队列queue与原生模仿其实现方法分享
2014/03/25 Javascript
详解Backbone.js框架中的模型Model与其集合collection
2016/05/05 Javascript
js HTML5多媒体影音播放
2016/10/17 Javascript
JavaScript中${pageContext.request.contextPath}取值问题及解决方案
2016/12/08 Javascript
原生js封装自定义滚动条
2017/03/24 Javascript
简单实现js鼠标跟随效果
2020/08/02 Javascript
jQuery Collapse1.1.0折叠插件简单使用
2017/08/28 jQuery
Bootstrap框架建立树形菜单(Tree)的实例代码
2017/10/30 Javascript
Vue 按键修饰符处理事件的方法
2018/05/04 Javascript
详解Vue底部导航栏组件
2019/05/02 Javascript
vue指令v-html使用过滤器filters功能实例
2019/10/25 Javascript
通过实例解析vuejs如何实现调试代码
2020/07/16 Javascript
Jquery如何使用animation动画效果改变背景色的代码
2020/07/20 jQuery
[01:10:30]DOTA2-DPC中国联赛正赛 Dragon vs Dynasty BO3 第一场 3月4日
2021/03/11 DOTA
Django1.7+python 2.78+pycharm配置mysql数据库
2016/10/09 Python
致Python初学者 Anaconda入门使用指南完整版
2018/04/05 Python
pyqt5的QWebEngineView 使用模板的方法
2018/08/18 Python
详解Python静态网页爬取获取高清壁纸
2019/04/23 Python
用Python将Excel数据导入到SQL Server的例子
2019/08/24 Python
Python自动采集微信联系人的实现示例
2020/02/28 Python
python 检测nginx服务邮件报警的脚本
2020/12/31 Python
苹果中国官方网站:Apple中国
2016/07/22 全球购物
碧欧泉美国官网:Biotherm美国
2016/08/31 全球购物
澳大利亚连衣裙和女装在线:Esther
2017/11/11 全球购物
软件毕业生个人鉴定
2014/03/03 职场文书
营销总监岗位职责
2014/09/16 职场文书
交通事故死亡赔偿协议书
2014/12/03 职场文书
Golang中interface{}转为数组的操作
2021/04/30 Golang
详解Mysql事务并发(脏读、不可重复读、幻读)
2022/04/29 MySQL