作者:我的名字是so。
正向链接:https://mp.weixin.qq.com/s/ck4e_YqvAx-MDadSodBKcQ
导 读
在vue3.0中,响应数据部分放弃了Object.defineProperty,转而使用Proxy。本文将主要通过以下几个方面来分析vue为什么选择放弃Object.defineProperty。
1.是真的吗?Object.defineProperty检测不到数组下标的变化?
2.分析vue2.x中数组Observe的源代码
3.用代理比较Object.defineProperty属性
一 无法监控到数组下标的变化?
我在一些技术博客上看到一个说法,Object.defineProperty有一个缺陷,就是不能监控数组变化:
无法监控数组下标的变化,导致直接通过数组下标设置值,无法实时响应。这就是为什么vue设置了七种变异数组(push,pop,shift,unshift,splice,sort,reverse)的hack方法来解决问题。
Object.defineProperty的第一个缺陷是它不能监视数组变化。但是Vue的文档中提到Vue可以检测数组变化,但是只有八个方法,vm.items[indexOfItem] = newValue,无法检测。
这个说法有问题。其实Object.defineProperty本身就可以监控数组下标的变化,但是在Vue的实现中,从性能/体验性价比的角度放弃了这个特性。
让我们通过一个示例来清除Object.defineProperty的名称:
function defineReactive(data,key,value){ object . define property(data,key,{ enumerate:true,configurable: true,get:function define get(){ console . log(` get key:$ { key } value:$ { value } `)返回值},set:function define set(new val){ console . log(` set key:$ { key } value:$ { new val } `)value = new val } })function observe(data){ object . keys(数据)。foreach(function(key){ define reactive(data,key,data [key])} let arr = [1,2,3] observe (arr)上面的代码通过Object.defineProperty劫持数组arr的每一个属性,我们对数组arr进行操作,看看哪些行为会触发数组的getter和setter方法。
1.获取一个元素并通过下标修改元素的值。
可以看到,通过下标获取元素会触发getter方法,设置值会触发setter方法。
接下来我们再试试数组的一些操作方法,看看会不会触发。
2.数组的推送方法
Push不触发setter和getter方法,数组的下标可以看作是对象中的键。这里的push后,相当于添加了一个下标较低的元素,下标为3,但是新的下标不是observe,所以不会被触发。
3.阵列的非移位方法
刚刚发生了什么?
Unshift操作会导致原始索引为0、1、2、3的值发生变化,所以需要把原始索引为0、1、2、3的值取出来,然后重新赋值,所以获取值的过程触发getter,赋值触发setter。
让我们试着通过索引得到相应的元素:
只有索引为0,1,2的属性才会触发getter。
在这里,我们可以比较对象。arr数组的初始值是[1,2,3],也就是说observe方法只对索引为0,1,2的元素进行,所以不管后面数组的长度如何变化,只有索引为0,1,2的元素在变化时才会被触发。其他新的索引相当于对象中新增加的属性,需要再次手动观察。
4.数组的弹出方法
当移除的元素是引用为2的元素时,将触发getter。
删除索引为2的元素后,在修改或获取其值时,setter和getter将不会被再次触发。
这与对象的处理是一样的。删除数组的索引后,相当于删除了对象的属性,不会再触发观察。
这里可以简单总结一下结论。
Object.defineProperty在数组中的表现和在对象中的表现是一致的,数组的索引可以看作是对象中的键。
1.当通过索引访问或设置相应元素的值时,可以触发getter和setter方法。
2.通过推或者不推,索引会增加,新增加的属性需要手动初始化才能观察到。
3.通过pop或shift删除元素将删除和更新索引,并触发setter和getter方法。
所以Object.defineProperty有监控数组下标变化的能力,但是vue2.x放弃了这个特性。
二 vue对数组的observe做了哪些处理?
在core/observer/index.js中定义了vue的Observer类。
可以看到,vue的观察者已经单独处理了数组。
HasProto是判断一个数组的实例是否有__proto__属性,如果有__proto__属性,就会执行protoAugment方法,在原型上重写arrayMethods。HasProto的定义如下。
ArrayMethods是对数组方法的重写,定义在core/observer/array.js中,下面是对这部分源代码的分析。
/* *不对该文件进行类型检查,因为流不& # 39;*动态访问数组原型上的方法*/import { def } from & # 39;../util/index & # 39;//复制数组构造函数的原型。Array.prototype也是一个数组。const arrayProto = Array.prototype//创建一个对象,对象的__proto__指向array proto,所以arrayMethods的__proto__包含了数组的所有方法。export const array methods = object . create(array proto)//下面的数组是要重写的方法const methods to patch =[& # 39;推& # 39;, '波普& # 39;, 'shift & # 39, 'unshift & # 39, '拼接& # 39;, '排序& # 39;, '反转& # 39;]/* * *拦截变异方法并发出事件*//遍历methodsToPatch数组,重写methodsToPatch . foreach(Function(Method){//缓存原方法const original = Array proto[Method]//def方法在lang.js文件中定义,通过object.defineProperty重定义属性//即在arrayMethods中找到我们要重写的方法,重定义它def(数组方法,方法,函数Mutator(…args) {constresult = original。Apply (this,Args) const ob = this。_ _ ob _ let插入开关(方法){//上面已经分析过了。对于推送,unsupported会添加一个索引,所以需要手动观察case & # 39推& # 39;:案例& # 39;unshift & # 39:inserted = args break // splice方法,如果传入第三个参数,也会有一个新的索引,所以也需要手动观察case & # 39拼接& # 39;:inserted = argsslice (2) break}//Push,unshift,splice被触发,然后在这里手动观察,其他方法的变化会更新到当前索引中。所以不需要执行ob . observariarrayf(inserted)ob . observariarray(inserted)//notify change ob . dep . notify()return result })。三Object.defineProperty VS Proxy已经知道Object.defineProperty在数组和对象上的性能是一致的,那么它和Proxy相比有什么优缺点呢?
1.Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。
由于Object.defineProperty只能劫持属性,所以它需要遍历对象的每个属性。如果属性值也是对象,则需要深度遍历。Proxy直接代理对象,不需要遍历操作。
2.Object.defineProperty需要手动观察新属性。
因为Object.defineProperty劫持了对象的属性,所以在添加属性时,需要再次遍历对象,并用object.defineproperty劫持新添加的属性..
正是因为这个原因,在使用vue给数据中的数组或对象添加属性时,需要使用vm。$set以确保添加的属性也是对应的。
我们来看看vue的set方法是如何实现的。set方法在core/observer/index.js中定义,下面是核心代码。
/** *设置对象的属性。添加新属性,如果该属性不符合&# 39;t *已经存在。*/导出函数集(target:Array & lt;任何& gt| object,key: any,val:any):any {//如果target是数组,key是有效的数组索引,将调用数组的splice方法。//上面我们说了,数组的拼接方法会被重写,重写的方法会被手动观察//所以vue的set方法,对于数组,就是直接调用重写拼接方法if(array . Is ray(target)& & isvalidiarrayindex(key)){ target . length = math . max(target . length,key) target.splice (key,1,Val) return val} //对于一个对象,如果key是对象中的属性,直接修改值就可以触发if (key in target &&!(键入对象。prototype)){ target[key]= val return val }/vue的响应式对象会加上__ob__属性,这样就可以判断它是否是响应式对象constob = (target: any)。_ _ ob _//如果它不是响应对象。ob){ target[key]= val return val }//调用defineReactive为数据添加getter和setter,//这样vue的set方法就会调用defineReactive为响应对象重新定义响应对象。定义反应函数定义反应函数。值,关键字,值)ob。离开notify () Return val}在set方法中,目标是数组,对象是分开处理的。当目标是数组时,将调用重写的拼接方法进行手动观察。
对于对象,如果键是对象的属性,则直接修改值触发更新,否则调用defineReactive方法重新定义响应对象。
如果是代理实现的,代理可以通过set (target,propkey,value,receiver)拦截对象属性的设置,可以拦截对象的新属性。
不仅如此,Proxy to array的方法也是可以检测的,不需要像上面vue2.x的源代码中那样进行黑客攻击。
完美!!!
3.代理支持13种拦截操作,这是defineProperty没有的。
get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
4.新标准绩效奖金
代理是一种新标准。长期来看,JS engine会继续优化Proxy,但getter和setter基本不会优化。
5.代理兼容性差。
如你所见,代理对于IE浏览器来说是一场灾难。
目前还没有一个完整的Polyfill方案支持代理的所有拦截方式。Google写的一个proxy-https://github . com/Google chrome/proxy-poly fill只支持get、set、apply、construct四种拦截,可以支持IE9+和Safari 6+。
四 总结
1.Object.defineProperty对于数组和对象的性能是一样的,所以监控数组下标的变化也不是不可能。vue2.x中无法通过数组索引自动更新响应数据是vue本身的设计造成的,而不是defineProperty的锅。
2.object.defineProperty和Proxy的本质区别在于,defineProperty只能劫持属性,所以存在需要递归遍历和人工观察才能添加属性的问题。
3.代理作为一种新标准,势必会被浏览器厂商不断优化,但其兼容性也是一个问题,目前还没有完整的polyfill方案。
推荐Vue学习资料文章:
“干货”学会这些Vue小技巧,你就可以早点下班,和女神约会了”
探索Vue-多选
《精细30张脑图带你从零开始学习Vue》
Vue后台项目中的技术难点及解决方案
手把手教你电子+Vue实用课程(五)
手把手教你电子+Vue实践课程(四)
手把手教你电子+Vue实用教程(三)
手把手教你电子+Vue实践课程(二)
手把手教你电子+Vue实用课程(1)
收集了22个开源的Vue模板和主题框架“干货”
如何写出一个优秀的后台管理系统?拿11个经典模板不感谢“干货”
手把手教你实现一个Vue自定义指令懒人加载
基于Vue和高德地图的地图组件“练习”的实现
——由Vue作者尤雨溪开发的web开发工具vite。
是什么让我爱上了Vue.js
11000字深度精品Vue3.0源码响应系统笔记“第一部分”
11000字深度精品Vue3.0源码响应系统笔记(下)
7个“实践”Vue数据更新案例总结和扩展解决方案总结
“翻译犹大关于Vue3诞生之路的论述”
打包速度提升10倍的工具Snowpack 2.0已经正式发布,再也不需要打包器了。
大厂的代码评审总结了Vue开发规范“值得学习”的经验
Vue3“尝鲜版”插件开发详解值得收藏”
带你五步学会Vue SSR
“记得一次Vue3.0干货分享会”
“Vue 3.x如何入门”进阶章节“快速且严密”
“干货微信支付前后流程安排(Vue+Node)”
“带你了解vue-next(Vue 3.0)的完整做法”
“干货”Vue+高德地图实现页面点击绘制多边形和多边形切割拆分”
「干货」Vue+元素前端导入导出Excel
“全分析”练习“Deno字节模块”
《精pdf.js练习解决水印和电子签名问题》Vue章节
基于vue+元素的后台管理系统解决方案
” Vue仿蘑菇街商城项目(vue+koa+mongodb)”
基于电子琴的音乐播放器的实践
编辑器插件Vue-quill-editor是“练习”Vue项目中的标准配置。
基于Vue技术栈的微前端方案实践。
消息队列帮助你成为高薪的Node.js工程师
Node.js中流模块详解
Deno TCP Echo服务器如何运行?》
《干货》大德诺实战课程
《通俗易懂的德诺干货入门课程》
“Deno正式发布,彻底了解与node的区别”
“练习”基于Apify+node+react/vue搭建有趣的爬虫平台
《实践》深度对比Vue 3.0 Composition API和React Hooks。
前端网络名人框架插件机制全面梳理(axios,koa,redux,vuex)。
深入到Vue,必须学习高阶分量HOC的进阶章节。
深入了解Vue的数据、计算和观察,以实现最简化和响应最快的系统。
10个小练习示例,快速入门并熟练掌握Vue3 core的新功能(I)
10个小练习示例、快速入门熟练度、Vue3 core的新功能(II)
教你部署和构建Vue-cli4+Webpack的移动框架“实践”
2020年前端就业的Vue框架“实践”
“详细解释一下Vue3中路由器带来了哪些变化?》
Vue项目“实践”的部署和性能优化指南
Vue高性能渲染大数据树组件“实践”
“优达小拼建立技术网站和个人博客的实践”
十项Vue开发技能“实践”
是什么原因导致你选择放弃Webpack?” vite原理分析” “
“带你了解vue-next(Vue 3.0)的小测试【练习】”
带你到vue-next(Vue 3.0)的开头【练习】。
练习JSX(TSX)风格组件开发的Vue 3.0。
一篇文章教你并排比较React.js和Vue.js的语法【练习】。
“携手带你开启Vue3的世界【修行】”
“简单来说,通过vue-cli3构建SSR应用程序[实践]”
如何加速你的Vue.js单页应用
“说说昨晚尤雨溪Vue3.0 Beta的新特性和知识点总结”
“【新消息】Vue 3.0 Beta版发布,还能学吗?》
“Vue真的很棒。一万多字的Vue知识点超级详细!》
从零开始为H5页面创建可视化编辑器-夸克-H5
简单来说,Vue3遵循了尤雨溪的Ref[练习]打字稿。
手把手教你如何把vue-cli3升级到vue-cli4。
“Vue 3.0 Beta和React开发者分别在吧上”
教你如何使用vue拖动图表实现一个可以拖动/缩放的图表组件。
Vue3早期采用者
总结Vue组件的通信
“Vue开源项目45强”
“2020年Vue的人气会超过React吗?》
尤雨溪:Vue 3.0的设计原则。
使用vue从HTML页面生成图片
“实现全栈收银系统(节点+Vue)(一)”
“实现全栈收银系统(节点+Vue)(下)”
Vue介绍高德的本土地图
“Vue合理配置WebSocket,实现群聊”
vue项目多年实战经验总结
用vue将echart封装成组件
基于Vue的双层顶板抽吸和坑踩综述
Vue插件总结【前端开发必备】。
“Vue开发必须知道的36个技能【近1W字】”
建设大型Vue.js项目的十点建议
深入理解vue中的插槽和插槽范围
手把手教你通过Vue分析pdf(base64)到图片【练习】。
利用vue+node构建前端异常监控系统
推荐8个漂亮的vue.js进度条组件。
基于Vue的拖拽升级(九宫格拖拽)。
摸摸你的手,用vue Series II带你去后台(登录权限)。
摸摸你的手,用vue系列三(实战)带你去后台。
Vue还是react做前端框架?明确对比两者的区别。”
您使用多少种方式在Vue组件之间进行通信?“实践”
React/Vue跨端渲染原理及实现分析
帮助成为更好的工程师的10个Vue开发技能
Vue [props,$ref,$emit]的父子组件之间的沟通实践的实际操作说明
“1W字长文+多图,带你了解vue双向数据绑定的源代码实现”
Vue3的回应和之前有什么不同?“实践”
“干货满满!如何优雅简洁地实现时钟翻牌(支持JS/Vue/React)
基于Vue/VueRouter/Vuex/Axios的登录路由和接口级拦截的原理与实现。
教你D3.js实现数据可视化,快速上手Vue应用。
吃透Vue项目开发实践| 16个方面深度前端工程开发技巧(一)。
了解Vue项目开发实践| 16个方面深度前端工程开发技巧【中】。
吃透Vue项目开发实践| 16个方面深度前端工程开发技巧【二】。
Vue3.0中权限管理的实现过程【实践】。
后台管理系统,前端Vue根据角色动态设置菜单栏和路由。
作者:我的名字是so。
正向链接:https://mp.weixin.qq.com/s/ck4e_YqvAx-MDadSodBKcQ
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。