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 相关文章推荐
JS 自动完成 AutoComplete(Ajax 查询)
Jul 07 Javascript
简单的代码实现jquery定时器
Jan 03 Javascript
JQuery实现鼠标移动到图片上显示边框效果
Jan 09 Javascript
js时钟翻牌效果实现代码分享
Jul 31 Javascript
浅谈JS继承_寄生式继承 &amp; 寄生组合式继承
Aug 16 Javascript
AngularJS+Bootstrap实现多文件上传与管理
Nov 08 Javascript
jQuery插件zTree实现更新根节点中第i个节点名称的方法示例
Mar 08 Javascript
尝试自己动手用react来写一个分页组件(小结)
Feb 09 Javascript
Javascript实现运算符重载详解
Apr 07 Javascript
小程序实现多选框功能
Oct 30 Javascript
微信小程序云开发详细教程
May 16 Javascript
Vue项目打包、合并及压缩优化网页响应速度
Jul 07 Vue.js
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
推荐文章系统(一)
2006/10/09 PHP
php设置编码格式的方法
2013/03/05 PHP
PHP中使用数组指针函数操作数组示例
2014/11/19 PHP
PHP实现微信模拟登陆并给用户发送消息的方法【文字,图片,图文】
2017/06/29 PHP
Laravel 框架控制器 Controller原理与用法实例分析
2020/04/14 PHP
超酷的网页音乐播放器DewPlayer使用方法
2010/12/18 Javascript
使用JS取得焦点(focus)元素代码
2014/03/22 Javascript
IE中的File域无法清空使用jQuery重设File域
2014/04/24 Javascript
jQuery实现的一个tab切换效果内部还嵌有切换
2014/08/10 Javascript
浅谈Javascript如何实现匀速运动
2014/12/19 Javascript
JS弹出窗口的运用与技巧大全
2016/11/01 Javascript
微信小程序顶部可滚动导航效果
2017/10/31 Javascript
javaScript产生随机数的用法小结
2018/04/21 Javascript
JS中‘hello’与new String(‘hello’)引出的问题详解
2018/08/14 Javascript
swiper在vue项目中loop循环轮播失效的解决方法
2018/09/15 Javascript
vue addRoutes路由动态加载操作
2020/08/04 Javascript
原生JavaScript实现五子棋游戏
2020/11/09 Javascript
vite2.0+vue3移动端项目实战详解
2021/03/03 Vue.js
python操作sqlite的CRUD实例分析
2015/05/08 Python
Python 中pandas.read_excel详细介绍
2017/06/23 Python
Python的CGIHTTPServer交互实现详解
2018/02/08 Python
完美解决Pycharm无法导入包的问题 Unresolved reference
2018/05/18 Python
Python实现简单的文本相似度分析操作详解
2018/06/16 Python
Python 使用类写装饰器的小技巧
2018/09/30 Python
python 输入一个数n,求n个数求乘或求和的实例
2018/11/13 Python
Python3中的bytes和str类型详解
2019/05/02 Python
pygame实现非图片按钮效果
2019/10/29 Python
Python ellipsis 的用法详解
2020/11/20 Python
CSS3实现王者荣耀匹配人员加载页面的方法
2019/04/16 HTML / CSS
德国著名廉价网上药店:Shop-Apotheke
2017/07/23 全球购物
智乐游戏测试笔试题
2014/05/21 面试题
出纳年终工作总结2014
2014/12/05 职场文书
告诉你一个秘密:富人致富的五大优点
2019/07/11 职场文书
Python词云的正确实现方法实例
2021/05/08 Python
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
2021/07/07 SQL Server
html+css实现滚动到元素位置显示加载动画效果
2021/08/02 HTML / CSS