当前位置: 首页 > Web前端 > vue.js

vue源码从什么到真香(二)-数组变化检测

时间:2023-03-31 18:14:35 vue.js

上一篇主要讲了Object类型的变化检测,为什么要讲Array类型呢?例如:this.array.push('ntyang'),请想一想,Object类型的变化检测是通过Object.defineProperty方法的getter/setter实现的,但是使用Array.prototype.push来更改数组不会触发getter/setter。那么Array类型的数据是如何实现变化检测的呢?接下来我跟大家说说1.你怎么知道发生了什么变化?上面的push方法是原型链上的一个方法。既然数组没有类似Object.defineProperty的方法,那我们换个角度想一想。可以用数组的原型链方法吗?作为切入点。用拦截器覆盖Array.prototype,然后我们每次使用数组原型方法时,实际上是执行拦截器中的方法,然后在拦截器中使用原来的Array.prototype方法。这样我们就可以在拦截器中监听Array的变化了。Array原型中有七个方法可以改变自己:push、pop、shift、unshift、splice、sort、reverse=['push','pop','shift','unshift','splice','sort','reverse']methodsToPatch.forEach(method=>{constoriginal=arrayProto[method]def(arrayMethods,方法,functionmutator(){letargs=[],len=arguments.length;while(len--)args[len]=arguments[len];returnoriginal.apply(this,args)})})functiondef(对象、键、值、可枚举e){Object.defineProperty(obj,key,{value:val,enumerable:!!enumerable,writable:true,configurable:true});}我们使用变量arrayPrototype来保存Array.prototype,它的所有功能都有。然后在arrayMethods中使用Object.defineProperty来封装这七个方法。如果我们使用show操作,使用arrayMethods覆盖Array.prototype,那么当我们使用push等方法时,实际上调用的是arrayMethods.push,也就是mutator函数。而mutator函数最终会执行数组的native方法。所以我们可以在mutator函数中做一些事情,比如发送通知。那么如何覆盖Array.prototype呢?2、覆盖Array.prototype使拦截器生效Array.prototype必须要覆盖。但是如果直接覆盖,会污染全局Array。我只想拦截那些响应数据。对了,大家有没有想到上一章的观察者呢?functionObserver(value){this.value=valueif(!Array.isArray(value)){this.walk(value)}else{value.__proto__=arrayMethods//New}}如果当前值为Array类型,直接赋值给它的隐式原型__proto__,但是鉴于目前浏览器对ES6的支持,我们需要处理无法使用__proto__的情况。Vue的处理也简单直接。既然你不能操作你的原型,你可以直接操作它。毕竟只要你有对应的方法,就不会去原型里再找,也就是把arrayMethods中的方法设置到数组中。修改Observe:consthasProto='__proto__'in{}constarrayKeys=Object.getOwnPropertyNames(arrayMethods);functionObserver(value){this.value=valueif(!Array.isArray(value)){this.walk(value)}else{//newif(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}}}functionprotoAugment(target,src){target.__proto__=src}functioncopyAugment(target,src,keys){for(leti=0,l=keys.length;i{constoriginal=arrayProto[method]def(arrayMethods,method,functionmutator(){letargs=[],len=arguments.length;while(len--)args[len]=arguments[len];constresult=original.apply(this,args)constob=this.__ob__//newob.dep.notify()//newreturnresult})})无论覆盖原型链还是直接添加方法值,就可以通过this.__ob__得到Observer实例,然后就可以愉快的ob.dep.notify()4.检测数组中每个元素的变化上面说的是检测数组本身的变化下一步是检测数组中的每个元素并转换Observer。当值为数组时,每个元素必须是递归的:functionObserver(value){this.value=valuethis.dep=newDep()def(value,'__ob__',this)if(!Array.isArray(value)){this.walk(value)}else{if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}this.observeArray(value)//New}}Observer.prototype.observeArray=function(items){for(leti=0,l=items.length;i{constoriginal=arrayProto[方法]def(arrayMethods,method,functionmutator(){letargs=[],len=arguments.length;while(len--)args[len]=arguments[len];constresult=original.apply(this,args)constob=this.__ob__letinstered//新开关(方法){case'push':case'unshift':instered=argsbreakcase'splice':instered=args.slice[2]break}if(instered)ob.observeArray(instered)//Addob.dep.notify()returnresult})})对方法进行switch判断,将方法中的参数保存到inserted中。如果inserted有值,使用ob.observeArray检测它。检测,那么就说明一定有一些操作是Vue无法拦截的,比如:this.myArray[0]=123this.myArray.length=0以上两个操作是Vue完全检测不到的,所以不能触发watch或者重新渲染操作,页面不会更新。请观者注意,除非你是故意要这样做的……