计算机系统应用教程网站

网站首页 > 技术文章 正文

Vue2和Vue3数据双向绑定原理的区别及优缺点(下篇)

btikc 2024-10-12 13:25:14 技术文章 4 ℃ 0 评论

上篇我们讲到了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所替代。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表