前言
vue3的 Proxy 最近貌似各大网红公众号都有发,我也来蹭蹭热度写一篇吧!我们也可以结合vue2来看看vue3到底发生了些什么变化,又解决了Vue2.x的哪些痛点。接下来我们一起看看~
目录结构
Proxy是什么?简单用法尝试案例proxy - target 参数Proxy - handler 参数get()set()handler什么叫做数据双向绑定?简单实现数据渲染Proxy实现双向绑定回顾 Vue2 双向绑定实现Proxy 解决了Vue2的哪些痛点Proxy 的缺陷延伸阅读Proxy是什么?
Proxy
翻译过来就是代理的意思,何为代理呢?就是 用new
创建一个目标对象(traget
)的虚拟化对象,然后代理之后就可以拦截JavaScript
引擎内部的底层对象操作;这些底层操作被拦截后会触发响应特定操作的陷阱函数。
简单用法
constp=newProxy(target,handler)
target
要使用Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为。
尝试案例
讲得再多,看得再多,不如写写再说
Proxy - target 参数
letdata={}; //创建一个Proxy,将data作为目标对象 letproxy=newProxy(data,{}); //修改Proxy代理对象的name属性 proxy.name='严老湿'; console.log(proxy);//{name:'严老湿'} console.log(data);//{name:'严老湿'}//定义一个空对象
看了上面的案例,现在的你应该已经大概知道这个Proxy
的目标对象(target
)是怎么使用的了
Proxy - handler 参数
handler
单独抽离出来作为一个大标题是因为里面的内容有点多
handler
handler
对象是一个容纳一批特定属性的占位符对象。它包含有Proxy
的各个捕获器(trap)。它里面的参数有太多了,我们就拿会用到几个讲讲吧!有像深究的同学可以去看看文档Proxy handler
[1]
handler.set
handler.set()
方法用于拦截设置属性值的操作。
文档上面呢基本上就是这样写的
letdata={ name:"严老湿", age:'24' }; //handler抽离出来 lethandler={ set(target,prop,value){ console.log(target,prop,value) } } letp=newProxy(data,handler); p.age=18;//定义一个对象
个人习惯直接这样写
letdata={ name:"严老湿", age:'24' }; //创建一个Proxy,将data作为目标对象 letp=newProxy(data,{ set(target,prop,value){ //target=目标对象 //prop=设置的属性 //value=修改后的值 console.log(target,prop,value) //{name:'严老湿',age:'24'}'age'18 } }); //直接修改p就可以了 p.age=18; console.log(data) //{name:'严老湿',age:'24'}//定义一个对象
在我们设置值的时候会触发里面的set
方法;
我们已经捕捉到修改后的 属性 以及 他的值
但是打印data 并没有发生任何变化,那这还有啥用呢?
请看官方示例handler.set()
[2]
在示例中出现了一个Reflect.set()
[3]
returnReflect.set(...arguments);
Reflect
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API [4]
我们需要在handler.set()
中return
一个Reflect.set(...arguments)
来进行赋值给目标对象。
Reflect.set
Reflect.set
方法设置target
对象的name
属性等于value
。如果name
属性设置了赋值函数,则赋值函数的this
绑定receiver
。
Reflect.get
Reflect.get
方法查找并返回target
对象的name
属性,如果没有该属性,则返回undefined
。
name:"严老湿", age:'24' }; letp=newProxy(data,{ set(target,prop,newV){ //target=目标对象 //prop=设置的属性 //newV=修改后的值 returnReflect.set(...arguments) } }); p.age=18; console.log(data) //{name:'严老湿',age:18}letdata={
就像这样,已经打印成功了
handler.get
刚刚我们已经将 set 理解的已经差不多了,get还会难么?我们来看看
name:"严老湿", age:'24' }; letp=newProxy(data,{ get(target,prop){ //target=目标对象 //prop=获取的属性 console.log(target,prop) //{name:'严老湿',age:'24'}'age' returnReflect.get(...arguments) //这里的Reflect.get我们在上面已经讲到了 } }); //获取 console.log(p.age) //24letdata={
数据响应式 ↓
图片来源公众号 自律神仙ScarSu
什么叫数据双向绑定?
当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。
上栗子:
html
<h2class="app">h2>
js
letapp=document.querySelector('.app'); //定义data letdata={ name:"严老湿", age:24 }; //替换成data.age此时我们页面上应该是有个24 app.innerHTML=data.age; //我们在这里修改age data.age=21; console.log(data); //{name:"严老湿",age:21}//获取元素
这样看确实没啥毛病
但是呢在vue
中,我们在下面异步修改data中的值,页面上的值不应该是跟着一起变化的么?虽然data
对象已经发生变化,但是它并不能触发一些其他操作;我们来看看vue的双向绑定
<h2>hello{{msg}}h2> <inputtype="text"v-model="msg"> div> <scriptsrc="/npm/vue@2.6.12">script> <script>letvm=newVue({el:'#Foo',data:{msg:"严家辉" } });script><divid="Foo">
我们现在对双向绑定有了一个基本的认知。
简单实现数据渲染
等会儿我们实现双向绑定,在此之前我们做一个数据渲染过程,也简单的了解一下其原理
因为内容有点多,所以讲解呢全部在注释里面
<htmllang="en"> <head> <metacharset="UTF-8"> <title>Documenttitle> <scriptsrc="./src/index.js">script> head> <body> <divid="app">{{name}} <h2>{{age}}h2> div> <script>letvm=newReactive({//挂载元素 el:"#app",data:{name:"严老湿",age:24 } });script> body> html>html>
index.js
//接收参数 constructor(options){ this.options=options; //data赋值 this.$data=this.options.data; //挂载元素 this.el=document.querySelector(this.options.el) //调用compile函数 pile(this.el) } //渲染数据 compile(el){ //获取el的子元素 letchild=el.childNodes; //遍历判断是否存在文本 [...child].forEach(node=>{ //如果node的类型是TEXT_NODE if(node.nodeType===3){ //拿到文本内容 lettxt=node.textContent; //正则匹配{{}}空格 letreg=/\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(txt)){ let$1=RegExp.$1; this.$data[$1]&&(node.textContent=txt.replace(reg,this.$data[$1])) } //如果node的类型是ELEMENT_NODE }elseif(node.nodeType===1){ //递归执行 pile(node) } }) } }classReactive{
一个简单并且潦草一点的的渲染数据功能已经完成了
Proxy实现双向绑定
<htmllang="en"> <head> <metacharset="UTF-8"> <title>Documenttitle> <scriptsrc="./src/index.js">script> head> <body> <divid="app">{{name}} <h2>{{age}}h2> <inputtype="text"v-model="name"> {{name}} div> <script>letvm=newReactive({//挂载元素 el:"#app",data:{name:"严老湿",age:24, } });script> body> html>html>
index.js
classReactiveextendsEventTarget{ //接收参数 constructor(options){ super(); this.options=options; //data赋值 this.$data=this.options.data; //挂载元素 this.el=document.querySelector(this.options.el); //调用compile函数 pile(this.el); //调用双向绑定 this.observe(this.$data); } //双向绑定 observe(data){ //备份this let_this=this; //接收目标对象进行代理 this.$data=newProxy(data,{ set(target,prop,value){ //创建一个自定义事件CustomEvent[5] //事件名称使用的是prop letevent=newCustomEvent(prop,{ //传入新的值 detail:value }) //派发event事件 _this.dispatchEvent(event); returnReflect.set(...arguments); } }) } //渲染数据 compile(el){ //获取el的子元素 letchild=el.childNodes; //遍历判断是否存在文本 [...child].forEach(node=>{ //如果node的类型是TEXT_NODE if(node.nodeType===3){ //拿到文本内容 lettxt=node.textContent; //正则匹配 letreg=/\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(txt)){ let$1=RegExp.$1; this.$data[$1]&&(node.textContent=txt.replace(reg,this.$data[$1])) //绑定自定义事件 this.addEventListener($1,e=>{ //替换成传进来的detail node.textContent=txt.replace(reg,e.detail) }) } //如果node的类型是ELEMENT_NODE }elseif(node.nodeType===1){ //获取attr letattr=node.attributes; //判断是否存在v-model属性 if(attr.hasOwnProperty('v-model')){ //获取v-model中绑定的值 letkeyName=attr['v-model'].nodeValue; //赋值给元素的value node.value=this.$data[keyName] //绑定事件 node.addEventListener('input',e=>{ //当事件触发的时候我们进行赋值 this.$data[keyName]=node.value }) } //递归执行 pile(node) } }) } }//EventTarget[6]
这样我们就实现了一个双向绑定的小 demo ,当然代码还不够严谨,比如v-model
的元素筛选都还不够完善,只是带大家简单的了解一下实现逻辑
回顾 Vue2 双向绑定实现
vue2 大部分同学刷题也经常会碰到 ,我们接下来看看vue2如何实现的呢!
一个超级简陋的双向绑定hhh,简单回顾一下就行了
<htmllang="en"> <head> <metacharset="UTF-8"> <title>title> head> <body> <h2id="txt">h2> <inputtype="text"id="el"> <script>letobj={};//获取节点letel=document.querySelector('#el');lettxt=document.querySelector('#txt');Object.defineProperty(obj,'foo',{set(newValue){//修改后的值进行赋值 txt.innerHTML=newValue; } });//绑定事件 el.addEventListener('input',e=>{//赋值给obj数据 obj.foo=e.target.value; });script> body> html>html>
Proxy解决了vue2的哪些痛点
Object.defineProperty
只能劫持对象的属性,而Proxy是直接代理对象;Object.defineProperty
对新增属性需要手动进行Observe
;vue2.x
无法监控到数组下标的变化,因为vue2
放弃了这个特性;Proxy支持13种拦截操作,这是Objectd.defineProperty
所不具有的;Proxy的缺陷
其他的不想多说,就一个 IE 兼容真的挺难受的,硬伤了
延伸阅读
[1] /zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler
[2] /zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/set
[3] /zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
[4] /zczhangcui/p/6486582.html
[5] /zh-CN/docs/Web/API/CustomEvent
[6] /zh-CN/docs/Web/API/EventTarget
如果觉得《数据双向绑定_手写 Vue3 数据双向绑定 理解Proxy》对你有帮助,请点赞、收藏,并留下你的观点哦!