失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 数据双向绑定_手写 Vue3 数据双向绑定 理解Proxy

数据双向绑定_手写 Vue3 数据双向绑定 理解Proxy

时间:2019-01-10 12:46:44

相关推荐

数据双向绑定_手写 Vue3  数据双向绑定 理解Proxy

前言

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

letdata={

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}

就像这样,已经打印成功了

handler.get

刚刚我们已经将 set 理解的已经差不多了,get还会难么?我们来看看

letdata={

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)

//24

数据响应式 ↓

图片来源公众号 自律神仙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的双向绑定

<divid="Foo">

<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>

我们现在对双向绑定有了一个基本的认知。

简单实现数据渲染

等会儿我们实现双向绑定,在此之前我们做一个数据渲染过程,也简单的了解一下其原理

因为内容有点多,所以讲解呢全部在注释里面

html>

<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>

index.js

classReactive{

//接收参数

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)

}

})

}

}

一个简单并且潦草一点的的渲染数据功能已经完成了

Proxy实现双向绑定

html>

<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>

index.js

//EventTarget[6]

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)

}

})

}

}

这样我们就实现了一个双向绑定的小 demo ,当然代码还不够严谨,比如v-model的元素筛选都还不够完善,只是带大家简单的了解一下实现逻辑

回顾 Vue2 双向绑定实现

vue2 大部分同学刷题也经常会碰到 ,我们接下来看看vue2如何实现的呢!

一个超级简陋的双向绑定hhh,简单回顾一下就行了

html>

<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>

Proxy解决了vue2的哪些痛点

Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象;Object.defineProperty对新增属性需要手动进行Observevue2.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》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。