网站首页 > 技术文章 正文
上篇我们讲到了Vue2的数据双向绑定原理,如果你没有阅读上篇,建议先阅读一下上篇中的内容:
Vue2和Vue3数据双向绑定原理的区别及优缺点(上篇)
在上篇中我们抛出了一个问题:是不是Vue3中的数据双向绑定原理就优于Vue2的?我们也给大家透露了,并不是这样,是各有利弊的,那么本篇我们就带着这个问题,来感受一下Vue3中的数据双向绑定原理是如何实现的。
一、Vue3数据双向绑定原理的实现
Vue3中最主要的改变就是将Object.defineProperty()替换成为Proxy对象,可以原生支持到数组的响应式,不需要重写数组的原型,还可以直接支持新增和删除属性,比Vue2的Object.defineProperty更加的清晰明了。
那么什么是Proxy,通过查阅MDN我们知道Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。什么意思呢?Proxy 就像一个拦截器一样,它可以在读取对象的属性,修改对象的属性,获取对象属性列表,通过for in循环等等操作的时候,去拦截对象上面的默认行为,然后自己自定义这些行为。以下为Proxy的语法格式:
let proxy = new Proxy(target, handler)
/**
* target: 要兼容的对象,可以是一个对象,数组,函数等等
* handler: 是一个对象,里面包含了可以监听这个对象的行为函数,
* 同时会返回一个新的对象proxy, 为了能够触发handler里面的函数,必须要使用返回值去进行其他操作,比如修改值
*/
那么Vue3是如何实现数据双向绑定原理的呢,其实核心代码非常的少,并且主要是3版本的数据劫持的方式由Object.defineProperty更改为Proxy代理。
具体代码实现过程如下:
定义构造函数
function Vue(option){
this.$el = document.querySelector(option.el); //获取挂载节点
this.$data = option.data;
this.$methods = option.methods;
this.deps = {}; //所有订阅者集合 目标格式(一对多的关系):{msg: [订阅者 1, 订阅者2, 订阅者3], info: [订阅者1, 订阅者2]}
this.observer(this.$data); //调用观察者
this.compile(this.$el); //调用指令解析器
}
定义指令解析器
Vue.prototype.compile = function (el) {
let nodes = el.children; //获取挂载节点的子节点
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.children.length) {
this.compile(node) //递归获取子节点
}
if (node.hasAttribute('l-model')) { //当子节点存在l-model指令
let attrVal = node.getAttribute('l-model'); //获取属性值
node.addEventListener('input', (() => {
this.deps[attrVal].push(new Watcher(node, "value", this, attrVal)); //添加一个订阅者
let thisNode = node;
return () => {
this.$data[attrVal] = thisNode.value //更新数据层的数据
}
})())
}
if (node.hasAttribute('l-html')) {
let attrVal = node.getAttribute('l-html'); //获取属性值
this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //添加一个订阅者
}
if (node.innerHTML.match(/{{([^\{|\}]+)}}/)) {
let attrVal = node.innerHTML.replace(/[{{|}}]/g, ''); //获取插值表达式内容
this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //添加一个订阅者
}
if (node.hasAttribute('l-on:click')) {
let attrVal = node.getAttribute('l-on:click');//获取事件触发的方法名
node.addEventListener('click', this.$methods[attrVal].bind(this.$data)); //将this指向this.$data
}
}
}
定义观察者
Gao.prototype.observer = function (data) {
const that = this;
for (var key in data) {
that.deps[key] = []; //初始化所有订阅者对象{msg: [订阅者], info: []}
}
let handler = {
get(target, property) {
return target[property];
},
set(target, key, value) {
let res = Reflect.set(target, key, value);
var watchers = that.deps[key];
watchers.map(item => {
item.update();
});
return res;
}
}
this.$data = new Proxy(data, handler);
}
定义订阅者
function Watcher(el, attr, vm, attrVal) {
this.el = el;
this.attr = attr;
this.vm = vm;
this.val = attrVal;
this.update(); //更新视图
}
更新视图
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.val]
}
二、两者的区别及优缺点
通过上面的分析,我们也应该了解,两者的区别主要是Vue3的数据劫持的方式由Object.defineProperty更改为Proxy代理。那么他们两者的优点和缺点分别是什么呢?
Object.defineProperty的优点
兼容性好,支持IE9,为什么这么说?只需要继续往下看,看proxy的劣势我们就能明白Object.defineProperty的优点。
Object.defineProperty的缺陷
1、无法检测到对象属性的新增或删除
Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。其只能监听对象已有数据是否被修改,但是不能监听新增属性和删除属性。
2、不能监听数组的变化
我们在Vue的官方文档中可以看到这么一段话:
Vue在实现数组的响应式时,把无法监听数组的情况通过重写数组的部分方法来实现响应式,但是只局限在以下7种方法:
push/pop/shift/unshift/splice/sort/reverse,其他数组方法及数组的使用则无法检测到。
那么怎么解决以上问题呢?答案就是Vue3的proxy。
Proxy
proxy属性是ES6中新增的一个属性,它也是一个构造函数,可以通过new关键字来创建函数。表示修改某些操作的默认行为,等同于在语言层面做出修改。
proxy可以理解为在目标对象之前架设一层拦截,外界对该对象的访问,都必须经过这层拦截,因此提出了一种机制,可以对外界的网文进行过滤和改写,proxy这个词是代理的意思,用来表示由他代理某些操作,可以译为代理器。
Proxy的优点
1、proxy直接代理的是整个对象而不是代理对象属性。
2、可以监听同级结构下的所有属性变化,包括新增的属性和删除的属性,也可以监听数组的变化。
Proxy代理目标对象,是通过操作下面表格中的13种trap来完成的,这与ES6提供的另一个api即Reflect的13种静态方法一一对应。二者一般是配合使用的,在修改proxy代理对象时,一般也需要同步到代理的目标对象上,这个同步就是用Reflect对应方法来完成的。
trap | 描述 |
handler.get | 获取对象的属性时拦截 |
handler.set | 设置对象的属性时拦截 |
handler.has | 拦截propName in proxy的操作,返回boolean |
handler.apply | 拦截proxy实例作为函数调用的操作,proxy(args)、proxy.call(...)、proxy.apply(..) |
handler.construct | 拦截proxy作为构造函数调用的操作 |
handler.ownKeys | 拦截获取proxy实例属性的操作,包括Object.getOwnPropertyNames、Object.getOwnPropertySymbols、Object.keys、for...in |
handler.deleteProperty | 拦截delete proxy[propName]操作 |
handler.defineProperty | 拦截Objecet.defineProperty |
handler.isExtensible | 拦截Object.isExtensible操作 |
handler.preventExtensions | 拦截Object.preventExtensions操作 |
handler.getPrototypeOf | 拦截Object.getPrototypeOf操作 |
handler.setPrototypeOf | 拦截Object.setPrototypeOf操作 |
handler.getOwnPropertyDescriptor | 拦截Object.getOwnPropertyDescriptor操作 |
Proxy的劣势
最主要的就是兼容性问题,虽然proxy相对于object.defineProperty有很有优势,但是并不是说proxy就是完全的没有劣势,主要表现在以下的两个方面:
1、proxy有兼容性问题,无完全的polyfill:
proxy为ES6新出的API,浏览器对其的支持情况可在w3c规范中查到,通过查找我们可以知道,虽然大部分浏览器支持proxy特性,但是一些浏览器或者低版本不支持proxy,因此proxy有兼容性问题,那能否像ES6其他特性有polyfill解决方案呢?,这时我们通过查询babel文档,发现在使用babel对代码进行降级处理的时候,并没有合适的polyfill。
2、第二个问题就是性能问题:
proxy的性能其实比promise还差,这就需要在性能和简单实用上进行权衡,例如Vue3使用proxy后,其对对象及数组的拦截很容易实现数据的响应式,尤其是数组。虽然proxy有性能和兼容性处理,但是proxy作为新标准将受到浏览器厂商重点持续的性能优化,性能这块相信会逐步得到改善。
总结
到这为止,咱们的Vue2与Vue3的数据双向绑定原理就总结完毕了,强烈建议没有阅读过上篇的同学先去看看上篇中所说内容,通过对比两篇文章咱们发现这两种方式还是各有优劣的,但是咱们发现Vue3的缺点其实是软性缺点,是可以通过后期优化去逐步完善的,所以咱们可以完全相信在不久的将来,Vue2的数据双向绑定原理的实现终将被Vue3所替代。
猜你喜欢
- 2024-10-12 学习Vue的数据绑定语法,实现动态的用户界面(四)
- 2024-10-12 Vue前端框架:绑定单个复选框。 vue复选框默认选中
- 2024-10-12 vue2响应式原理 vue2.0响应式原理
- 2024-10-12 vue数据绑定语法 vue样式绑定语法
- 2024-10-12 【Vue】第5章 Vue数据绑定和计算属性详解
- 2024-10-12 Vue数据及事件绑定、filter过滤器
- 2024-10-12 Vue开发:组件绑定自定义事件及其应用
- 2024-10-12 vue.js实战之表单绑定-单选按钮 vue表格单选
- 2024-10-12 跟神一起学前端-初识vue、数据绑定
- 2024-10-12 vue.js 动态绑定class的几种方式 vue动态绑定类
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)