响应式入门,你了解vue3.0响应式数据怎么实现吗( 三 )


现在我们在来试试下面的代码:const data = https://www.xysc168.com/guoxue/{userIds: ['01','02','03','04','05']}defineReactiveData(data);data.userIds // getting userIds ["01", "02", "03", "04", "05"]// get 过程是没有问题的 , 现在我们尝试给数组中push一个数据data.userIds.push('06') // getting userIds
what ? setting没有被触发 , 反而因为取了一次userIds所以触发了一次getting~ , 
不仅如此 , 很多数组的方法都不会触发setting , 比如:push,pop,shift,unshift,splice,sort,reverse这些方法都会改变数组 , 但是不会触发set , 所以Vue为了解决这个问题 , 重新包装了这些函数 , 同时当这些方法被调用的时候 , 手动去触发notify();看下源码:// 获得数组原型const arrayProto = Array.prototypeexport 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 inserted// 调用以下几个函数时 , 监听新数据switch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted)// 手动派发更新ob.dep.notify()return result})})
上面是官方的源码 , 我们可以实现一下push的伪代码 , 为了省事 , 直接在prototype上下手了~const push = Array.prototype.push;Array.prototype.push = function(...args){console.log('push is happenning');return push.apply(this, args);}data.userIds.push('123') // push is happenning
通过这种方式 , 我们可以监听到这些的变化 , 但是vue官方文档中有这么一个注意事项
由于 JavaScript 的限制 , Vue 不能检测以下变动的数组:当你利用索引直接设置一个项时 , 例如:vm.items[indexOfItem] = newValue当你修改数组的长度时 , 例如:vm.items.length = newLength
这个最根本的原因是因为这2种情况下 , 受制于js本身无法实现监听 , 所以官方建议用他们自己提供的内置api来实现 , 我们也可以理解到这里既不是defineProperty可以处理的 , 也不是包一层函数就能解决的 , 这就是2.x版本现在的一个问 。回到这篇文章的主题 , vue官方会在3.x的版本中使用proxy来代替defineProperty处理响应式数据的过程 , 我们先来模拟一下实现 , 看看能否解决当前遇到的这些问题;
3.x版本
我们先来通过proxy实现对data对象的get和set的劫持 , 并返回一个代理的对象 , 注意 , 我们只关注proxy本身 , 所有的实现都是伪代码 , 有兴趣的同学可以自行完善const defineReactiveProxyData = https://www.xysc168.com/guoxue/data => new Proxy(data,{get: function(data, key){console.log(`getting ${key}`)return Reflect.get(data, key);},set: function(data, key, newVal){console.log(`setting ${key}`);if(typeof newVal === 'object'){ // 如果是object , 递归设置代理return Reflect.set(data, key, defineReactiveProxyData(newVal));}return Reflect.set(data, key, newVal);}})const data = https://www.xysc168.com/guoxue/{name:'nanjing',age: 19};const vm = defineReactiveProxyData(data);vm.name // getting name nanjingvm.age = 20; // setting age 20
看起来我们的代理已经起作用啦 , 之后只要在setting的时候加上notify()去通知模板进行编译就可以了 , 然后我们来尝试设置一个数组看看;vm.userIds = [1,2,3] // setting userIdsvm.userIds.push(1);// getting userIds 因为我们会先访问一次userids// getting push 调用了push方法 , 所以会访问一次push属性// getting length 数组push的时候 length会变 , 所以需要先访问原来的length// setting 3 通过下标设置的 , 所以set当前的index是3// setting length 改变了数组的长度 , 所以会set length// 4 返回新的数组的长度