其实这个问题在很多文章里都有写过,也是面试高频话题。这里只是记录下我的理解。Proxy和Object.defineproperty的区别Object.defineProperty只能劫持对象的属性,嵌套对象需要深度遍历;而Proxy直接代理整个对象。Object.defineProperty对于新增的属性需要手动Observe(使用$set);Proxy可以拦截对象的新增属性,数组的push、shift、splice也可以拦截Proxy,有13个拦截操作。这是defineProperty没有的Proxy。兼容性差。IE浏览器不支持多种Proxy。方法没有完整的polyfill解决方案defineProperty;functiondefineReactive(data,key,value){Object.defineProperty(data,key,{enumerable:true,configurable:true,get:functiondefineGet(){console.log(`getkey:${key}value:${value}`)返回值},set:functiondefineSet(newVal){console.log(`setkey:${key}value:${newVal}`)value=newVal}})}functionobserve(data){Object.keys(data).forEach(function(key){//递归gettersetterdefineReactive(data,key,data[key])})}Proxy:letproxyObj=newProxy(data,{get(key){返回数据[key]},set(key,value){data[key]=value}})当然还有其他属性,这里说最简单的。这两种方式的区别让我想起了事件代理111222333444li>如果不使用事件代理,它会为ul下的每一个li绑定事件。这样写的问题是新加入的li没有事件,事件没有加在一起。如果使用事件代理,那么新添加的子节点也会有事件响应,因为这很类似于通过触发代理节点(父节点冒泡)来触发事件。这里要说明的是:defineProperty本身就是对对象属性做getter/setter,而Proxy返回的是一个代理对象。只有当代理对象被修改时,响应式才会发生。如果修改了原始对象属性,则不会生成响应式更新。Object.defineProperty处理数组查看vue官方文档,我们可以看到:Vue无法检测到以下数组变化:1.当你使用索引直接设置数组项时,例如:vm.items[indexOfItem]=新值2。当你修改数组的长度时,例如:vm.items.length=newLength对于第一点:有些文章直接写Object.defineProperty,有一个缺陷就是无法监听到数组的变化,导致直接通过数组下标设置数组的值,不能实时响应。说法是错误的。其实Object.defineProperty可以监听数组下标的变化,但是在Vue的实现中,考虑到性能/体验性价比,放弃了这个特性。对于数组下的索引,你可以使用getter/setter是的,但是为什么vue没有这样做呢?如果监控index值,通过push或者unshift添加的元素的index没有被劫持,没有响应。需要手动观察,通过pop或者shift删除元素。索引会被删除和更新,响应也会被触发,但是经常遍历数组,触发很多索引的getter性能不是很好。对于第二点:MDN:Redefinitionofthelengthpropertyofarraysispossible,butsubjecttogeneralredefinitionrestrictions.(length属性最初是不可配置的,不可枚举可写的。对于一个内容不变的数组,可以改变它的length属性的值或者使其不可写。但是改变它的可枚举性和可写性是可配置的或试图在不可写或可写时更改其值,这两种情况都是不允许的。)但是,并非所有浏览器都允许重新定义Array.length。因此,对于数组的长度,无法获取和设置其存取器属性,也就无法进行响应式更新。这里注意有两个概念:索引和下标数组是有下标的,但是对应的下标可能没有索引值!arr=[1,2]arr.length=5arr[4]//empty的下标为4,值为空,索引值不存在。for..in不会遍历索引值不存在的元素,手动给length赋一个更大的值。这时候会更新length,但是不会赋值对应的index,也就是对象没有属性,defineProperty无法处理。未知属性的监控,例如:对于length=5的数组,index不一定有4,如果index(property)不存在,就没办法设置。数组的索引其实和对象的key是一致的。Vue单独处理数组,劫持重写,看到一个数组劫持的demo:constarrayProto=Array.prototype//anemptyobjectbasedonarrayProtoconstarrayMethods=Object.create(arrayProto)constmethodToPatch=['push','splice']methodToPatch.forEach(function(method){constoriginal=arrayProto[method]def(arrayMethods,method,functionmutator(...args){constresult=original.apply(this,args)console.log('hijackhh')returnresult})})functiondef(obj,key,val,enumerable){Object.defineProperty(obj,key,{value:val,enumerable:!!enumerable,configurable:true,writable:true})}letarr=[1,2,3]arr.__proto__=arrayMethodsarr.push(4)//output//hijackhh//4us根据数组创建一个空对象arrayMethods,并定义要劫持的数组在上面。我们只是简单地打印了一句话。改变arr的原型点(assign__proto__),arr操作push和splice时会使用劫持的方法。Vue的数组劫持其实就是在劫持方法中加入了响应式逻辑。functionmutator(...args){//缓存原始方法constoriginal=arrayProto[method]//objkey,val,enumerabledef(arrayMethods,method,functionmutator(...args){constresult=original.apply(this,args)constob=this.__ob__letinsertedswitch(method){case'push':case'unshift'://eg:push(a)inserted=[a]//为推送添加Oberserve响应监控valueinserted=argsbreakcase'splice'://eg:splice(start,deleteCount,...items)inserted=[items]//添加Oberserve对新增值的响应监听inserted=args.slice(2)break}if(inserted)ob.observeArray(inserted)//通知更改ob.dep.notify()返回结果}/***观察数组项列表。*/observeArray(items:Array){for(leti=0,l=items.length;i|Object,key:any,val:any):any{//ifset如果函数的第一个参数是undefined或null或原始类型值,那么在非生产环境中将打印警告消息//此API最初用于对象和数组if(process.env.NODE_ENV!=='production'&&(isUndef(target)||isPrimitive(target))){warn(`无法在未定义、null或原始值上设置响应式属性:${(target:any)}`)}if(Array.isArray(target)&&isValidArrayIndex(key)){//类似$vm.set(vm.$data.arr,0,3)//修改数组的长度,避免索引导致的splcie()执行错误>arraylengthtarget.length=Math.max(target.length,key)//使用数组的拼接变异方式来触发响应,前面有提到target.splice(key,1,val)returnval}//target是一个对象,key在target或者target.prototype上//同时,一定不能在Object.prototype上。//可以直接修改。有兴趣的可以看issue:https://github.com/vuejs/vue/issues/6845if(keyintarget&&!(keyinObject.prototype)){target[key]=valreturnval}//以上都不成立,即开始为target创建一个全新的属性//获取Observer实例constob=(target:any).__ob__//Vue实例对象有_isVue属性,即不允许向Vue实例对象添加属性//并且Vue.set/$set函数不允许向根数据对象(vm.$data)添加属性if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=='production'&&warn('避免向Vue实例或其根$data添加反应属性'+'在运行时-在数据选项中预先声明它。')returnval}//target本身不是reactive数据,直接赋值if(!ob){target[key]=valreturnval}//---->Reactive处理defineReactive(ob.value,key,val)ob.dep.notify()returnval}参考:https://www.zhihu.com/questio...https://www.javascriptc.com/3...https://juejin.cn/post/684490...