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 相关文章推荐
jquery 获取dom固定元素 添加样式的简单实例
Feb 04 Javascript
jQuery 鼠标经过(hover)事件的延时处理示例
Apr 14 Javascript
实现JavaScript的组成----BOM和DOM详解
May 18 Javascript
Node.js读写文件之批量替换图片的实现方法
Sep 07 Javascript
BootStrap中Table分页插件使用详解
Oct 09 Javascript
jQuery设置和获取select、checkbox、radio的选中值方法
Jan 01 Javascript
vue项目中使用百度地图的方法
Jun 08 Javascript
vue 项目打包通过命令修改 vue-router 模式 修改 API 接口前缀
Jun 13 Javascript
JS前端知识点offset,scroll,client,冒泡,事件对象的应用整理总结
Jun 27 Javascript
JS+CSS+HTML实现“代码雨”类似黑客帝国文字下落效果
Mar 17 Javascript
vue中实现图片压缩 file文件的方法
May 28 Javascript
关于JavaScript回调函数的深入理解
Jun 27 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 magic_quotes_gpc的一点认识与分析
2008/08/18 PHP
解析PHP中如何将数组变量写入文件
2013/06/06 PHP
如何用C语言编写PHP扩展的详解
2013/06/13 PHP
PHP+apc+ajax实现的ajax_upload上传进度条代码
2016/01/25 PHP
Yii框架布局文件的动态切换操作示例
2019/11/11 PHP
在网页里看flash的trace数据的js类
2009/01/10 Javascript
javascript 动态table添加colspan\rowspan 参数的方法
2009/07/25 Javascript
使用JavaScript检测Firefox浏览器是否启用了Firebug的代码
2010/12/28 Javascript
基于JQuery的模拟苹果桌面Dock效果(稳定版)
2012/10/15 Javascript
Textbox控件注册回车事件及触发按钮提交事件具体实现
2013/03/04 Javascript
js内存泄露的几种情况详细探讨
2013/05/31 Javascript
JavaScript初学者建议:不要去管浏览器兼容
2014/02/04 Javascript
javascript中slice(),splice(),split(),substring(),substr()使用方法
2015/03/13 Javascript
js提交form表单,并传递参数的实现方法
2016/05/25 Javascript
JavaScript直播评论发弹幕切图功能点集合效果代码
2016/06/26 Javascript
node.js平台下的mysql数据库配置及连接
2017/03/31 Javascript
Angular.js中$resource高大上的数据交互详解
2017/07/30 Javascript
解读ES6中class关键字
2017/11/20 Javascript
微信小程序仿朋友圈发布动态功能
2018/07/15 Javascript
详解Node.js读写中文内容文件操作
2018/10/10 Javascript
浅谈JS中this在各个场景下的指向
2019/08/14 Javascript
vue实现数据控制视图的原理解析
2020/01/07 Javascript
python提示No module named images的解决方法
2014/09/29 Python
django使用html模板减少代码代码解析
2017/12/12 Python
Pycharm设置界面全黑的方法
2018/05/23 Python
解决Django的request.POST获取不到内容的问题
2018/05/28 Python
Python数据分析matplotlib设置多个子图的间距方法
2018/08/03 Python
解决python运行启动报错问题
2020/06/01 Python
python统计mysql数据量变化并调用接口告警的示例代码
2020/09/21 Python
三星新西兰官网:Samsung新西兰
2019/03/05 全球购物
会计自荐书
2013/12/02 职场文书
2014小学教师个人工作总结
2014/11/10 职场文书
二年级数学教学反思
2016/02/16 职场文书
如何计划开一家便利店?
2019/07/31 职场文书
超级实用!五步法则,教你写好年终工作总结
2019/12/05 职场文书
Spring Cloud 中@FeignClient注解中的contextId属性详解
2021/09/25 Java/Android