随着MVVM框架的广泛使用响应式数据已经变得耳熟能详,现在谈响应式数据好像有点炒冷饭的意思,对!没错!不过这次炒的是蛋炒饭,而且还是加火腿肠的那种。之前看过几个框架的响应式数据实现,貌似都对数组(Array)做了特殊处理,咋的啦,有这么特别吗?看来是时候反思和总结一波了。

如何实现响应式数据

首先来简单实现一个响应式对象。easy! ES5在Object对象中新增了defineProperties和defineProperty两个方法用来对对象的属性进行描述,其中的setter和getter这一对CP就是实现响应式对象的关键,先来看一段简单的代码

const person = {name:"wyy",age:"18"
}
Object.defineProperty(person,name,{get(){console.log(`没错,我就是人见人爱,车见爆胎的wyy`);return "wyy"},set(newVal){console.log('俺老王,行不更名,坐不改姓')return}
})

没错就是这么简单,这是目前普遍的实现方式。当然了,这段代码只是响应式的一部分,在get中进行依赖搜集,在set中通知收集来的依赖进行相应的处理,然后再搭配上高大上的观察者模式,这样响应式系统才基本成型,大部分响应式数据的实现都是这个路数,比如看过Vue2.x的响应式数据源码会发现几个关键字Observe,Watcher,Dep,notify,从字面意思来看大概也就是这么个路数,这里就不展开说了,毕竟掘金上个个都是人才,上面有不下50篇文章分析vue的响应式数据,篇篇精彩。

好了分析结束。wait…wait…wait…标题好像是谈数组的响应式,跑题了

实现一个响应式数组

下面来说所数组,没记错的话数组也是继承自Object,它算一种特殊的对象,那么数组能用Object.defineProperty来实现响应式吗?不说了,打开VSCode就是干

const arr = ["2019","云","栖","音","乐","节"];
arr.forEach((val,index)=>{Object.defineProperty(arr,index,{set(newVal){console.log("来了老弟");},get(){console.log("小老弟,来呀,快活呀");return val;}})
})
let index = arr[1];
arr[0] = "2050";

没毛病,和上面的效果一样儿一样儿的

上面这段代码,有没有人没看懂,我假装你们都不懂,贴张图


不,不,不是这张,是这张


通过观察数组的结构发现其实数组也是一个key-value键值对集合,只是key是数字罢了,那自然也可以通过属性描述的方式来实现访问和赋值的拦截了。一切貌似都是那么合理,那么Vue是这么干的吗?翻开源码一顿看后发现

import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]
methodsToPatch.forEach(function (method) {const original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)ob.dep.notify()return result})
})

实在不想贴源码了,太占篇幅了。你没有看错,Vue的响应式系统中并没有通过属性描述的方式处理数组,而对数组对象上的几常用方法进行了代理,只有当我们调用数组的这7个方法的时候才会触发视图的更新,这也解释了为什么我们在Vue项目中通过改变数组索引的方式改变数值是不会触发视图更新的。那么问题来了,为什么不使用属性描述的方式呢?

下面接着上面实现的响应式数组看个问题,直接贴图


前面的数组长度是6,这里我们直接给索引为7的位置赋值,会发现属性访问和赋值并没有被拦截。这是因为,当我们在数组中新增一个元素的时候,新增的元素是设置set和get属性的,所以索引位置的访问和赋值就不能被拦截了,这个好理解,即便是一个普通的key-value对象新增一个key后也需要对该key重新进行属性描述(defineProperty),所以貌似重新通过Object.definePropert一下就好了,这就完了吗?再来看张图


当我们通过上面这种方式改变数组长度的时候,发现之前设置的访问和赋值拦截都不起作用了,纳尼!其实也没什么,就算一个普通的key-value对象,你把对应的key删除掉,结果也一样。

再来看一张图:

瞬间数组的长度就变成16,中间的位置使用空元素填充。这个时候我们改变这些新增位置值的时候,也是不会被拦截的。

从的上面的操作总结一下,通过Object.defineProperty描述数组为什么不合适

  1. 数组和普通对象的区别在于,js中的数组太"多变"了,就像…。比如:arr.length=0,可以瞬间清空一个数组;arr[100]=1有可以瞬间将一个数组的长度变为100,等等骚操作。对于一个普通对象,我们一般只会改变key对应的value值,而不会连key都改变了,当key改变时我们只需要在拦截器中将重新赋值的value使用Object.defineProperty给它加上拦截器就行了。但是数组就不一样了,因为他的多变,我们需要重新将整个数组对象的所有key递归的加上拦截器并且我们还要穷举每一种数组变化的可能,这样势必就会带来性能开销问题,有的人会觉得这点性能开销算个x呀,但是性能问题都是由小变大的,如果数组对象中存的数据量大而且操作频繁的时候,这就是一个大问题。React16.x在就因为在优化textNode的时候,移除了一些无意义的标签,性能据说都提升了多少个percent。
  2. 数组在应用中经常会被操作,但是通常'push','pop','shift','unshift','splice','sort','reverse'这7中方法,基本上也能达到我们的目的。因此如果为了使数组具备通过value的改变就能触发视图更新的能力,而使用Object.defineProperty就显得代价太大了。

这大概这也是Vue设计之初考虑到的。那么数组的响应式实现只能是这种阉割版吗?接着往下看。

Proxy和Reflect实现响应式数组

有一天在React项目中玩弄mobx的时候,突然发现在mobx中通过改变数组索引也能触发视图的改变,该死的好奇心让我辗转反侧,难于入眠,于是乎装模作样的去看了一下它的实现,不看不知道一看又发现一对CPProxy和Reflet,真香。讲真,要是不是mobx我还真想不起他两的使用场景了。

Proxy和Reflect是ES2015新增的类,字面意思已经很明白了代理,反射,目的就是用他们来生成一个代理对象。这里不展开讲这两个类本身的使用了,扔一个链接,没映象或概念模糊的可以去看看ES的黑科技Proxy和Reflect,简单来说这两家伙就是Object.defineProperty的加强版。下面用Proxy和Reflect来将前面的代码重构一下。
下面用Proxy和Reflect来重写一下上面的代码

const arr = ["2019","云","栖","音","乐","节"];
let ProxyArray = new Proxy(arr,{get:function(target, name, value, receiver) {console.log("来了老弟")return Reflect.get(target,name);},set: function(target, name, value, receiver) {console.log("来啊,快活呀")Reflect.set(target,name, value, receiver);;}})const index = ProxyArray[0];ProxyArray[0]="2050"

效果一样一样的,而且我们不用去遍历每个key给它加上属性描述器了,还有个大大的好处就是通过代理对象无论我们怎么改变数组,数组的变化和访问我们都能观察到,大家可以去试试看。

当然好的东西也是有缺点的,就是兼容性问题,因为这是ES6的新特性,而且是没办hack成ES5,所以使用的时候你懂的…。

下一代响应式数据的实现

从上的文章中有没有闻出点味道,没错是他就是她,Proxy和Reflect,这对CP会作为猪脚闪亮登场了,微软都放弃IE拥抱chromiun了,还考虑那么多兼容问题干嘛。
所以在以后响应式数据的实现可能就变成下面这样了

const defineReactiveProxyData = data => new Proxy(data,{get: function(data, key){//....依赖搜集return Reflect.get(data, key);},set: function(data, key, newVal){//通知下发if(typeof newVal === 'object'){ // 如果是object,递归设置代理return Reflect.set(data, key, defineReactiveProxyData(newVal));}return Reflect.set(data, key, newVal);}})

通过Proxy和Reflect完美的解决了之前数组响应式实现遇到的问题,同时也不用再做那么多深层的遍历了,是不是爽歪歪。

最后再扔一个vue3.0的源码链接,有兴趣的可以去看看Vue3.0中响应式数据部分的实现。

总结

其实讲了这么多,就是想将大家的目光从Object.defineProperty吸引到Proxy和Rflect上来,毕竟ES2019都出来了,Proxy和Rflect也该拿出来用用了,不要某天某个时刻被问起ES6的时候只记得let,const,箭头函数之类的。

从数组的响应式看下一代响应式数据相关推荐

  1. 下一代响应式Web设计:组件驱动式Web设计

    自从著名设计师 Ethan Marcotte(@beep)在 A List Apart上发表了一篇名为< Responsive Web Design>的文章之后,响应式网页设计(RWD,即 ...

  2. php可以实现响应式吗,怎么实现Vue数据响应式

    这次给大家带来怎么实现Vue数据响应式,实现Vue数据响应式的注意事项有哪些,下面就是实战案例,一起来看一下. 前言 Vue的数据响应主要是依赖了Object.defineProperty(),那么整 ...

  3. (11)照虎画猫深入理解响应式流规范——响应式Spring的道法术器

    本系列其他文章见:<响应式Spring的道法术器>. 前情提要:响应式流 | Reactor3快速上手 2 响应式编程之法 上一章本着"快速上手"的原则,介绍了响应式流 ...

  4. 响应式网页教程_如何响应式思考:响应式网页设计教程

    响应式网页教程 For a long time, responsive web design was a trend. Now it's simply a reality. If we think o ...

  5. html响应式布局平移,响应式网页设计、响应式布局的实现原理

    概念 响应式网页设计最初是由 Ethan Marcotte 提出的一个概念:为什么一定要为每个用户群各自打造一套设计和开发方案?Web设计应该做到根据不同设备环境自动响应及调整.当然响应式Web设计不 ...

  6. html响应式布局效果图,响应式网页布局的实现方法原理

    昨天我在马海祥博客上跟大家详细的介绍过<什么是响应式网页设计?>,我觉的响应式网页设计不仅仅是一种趋势,还更是一个新的设计解决方案!它有助于解决不同的分辨率和设备(台式电脑,笔记本电脑,平 ...

  7. 面试官:什么是响应式设计?响应式设计的基本原理是什么?如何做?

    一.是什么 响应式网站设计(Responsive Web design)是一种网络页面设计布局,页面的设计与开发应当根据用户行为以及设备环境(系统平台.屏幕尺寸.屏幕定向等)进行相应的响应和调整 描述 ...

  8. css考核点整理(十一)-响应式开发经验,响应式页面的三种核心技术是什么

    响应式开发经验,响应式页面的三种核心技术是什么 转载于:https://www.cnblogs.com/yingwo/p/4120389.html

  9. html中响应式查询,css 响应式(媒介查询)

    1.CSS 来实现响应式 CSS实现响应式网站的布局要用到的就是CSS中的媒体查询接下来来简单介绍一下: @media 类型 and (条件1) and (条件二){css样式} 我们只需用到widt ...

最新文章

  1. 为什么你不应该自行更新 Drupal 网站?
  2. RHCE认证培训+考试七天实录(二)
  3. python3 set 集合 简介
  4. 1024节日快乐~~~~
  5. android开发switch自动关闭,更改Android Switch状态
  6. java foreach多线程_详解多线程入门案例与java8的并行流
  7. 2021-09-1311. 盛最多水的容器 数组 双指针
  8. CS231n-assignment1详解
  9. 微信小程序如何实现转发/分享功能
  10. 龙卷风路径_【龙卷风的防范措施】龙卷风的易发地点_龙卷风如何分级 - 妈妈网百科...
  11. 7-4 走迷宫II(Dijkstra 算法)
  12. Lazada商家售出产品多久能收款?收款方式及流程一篇详解!
  13. 微信公众号硬件开发杂谈(二)
  14. GD32定时器——单个定时器下多个通道PWM捕获
  15. Python:把一个数组按指定数组大小size分割为多个数组
  16. ICL8038信号发生器 正弦波 方波 三角波 低频信号发生 波形发生 原理图和PCB
  17. Thinkpad X1 Carbon/Yoga如何更换固态硬盘并无损转移系统(亲测有效)
  18. 番茄闹钟三(登录注册页面)
  19. Unity实现拨打电话
  20. 解决mac前端本地浏览器跨域的问题

热门文章

  1. python中prettytable模块_PrettyTable模块
  2. 深圳市智汇机器人科技有限公司环宇智行
  3. python orm框架
  4. onsubmit=return check();和onsubmit=check();的区别
  5. 使用frp+MobaXterm进行ssh远程连接报错及解决办法
  6. IM群聊头像九宫格实现方式
  7. OpenCV实战——多尺度FAST特征检测
  8. 如何从GitHub上下载一个项目中的单个文件或者子文件夹
  9. iOS https证书双向认证的实现机制
  10. js 获取数组最后一个元素