在vue2当中是如何做到更改数据从而页面进行刷新数据的呢?先看底层原理
原理:在vue2中利用的是原生js下边的Object.defineProperty()进行数据劫持,在通过里面的getter和setter方法,进行查看和数据的修改,通过发布、订阅者模式进行数据与视图的响应式。
在进行Vue2的开发过程当中很容易会发现会存在几个问题,那么就是新增、删除对象的属性,以及直接通过对数组的索引值进行修改是不会触发页面刷新的,我们可以通过以下的例子来看一下。
<template>
<div>
<h2>姓名:{{ person.name }}</h2>
<h2>性别:{{ person.sex }}</h2>
<h2>爱好:{{ hobby }}</h2>
<button @click="addSex">加性别</button>
<button @click="deleteName">删姓名</button>
<button @click="update">修改数组</button>
</div>
</template>
<script>
import vue from 'vue'
export default {
data() {
return {
person: {
name: '张三',
},
hobby: ['爬山', '爬虫', '爬路']
}
}, methods: {
deleteName() {
// delete this.person.name
// this.$delete(this.person, 'name')
vue.delete(this.person, 'name')
},
addSex() {
// this.person.sex = '男'
// this.$set(this.person, 'sex', '男')
vue.set(this.person, 'sex', '男')
},
update() {
// this.hobby[0] = '爪巴'
// this.$set(this.hobby, 0, '爪巴')
vue.set(this.hobby, 0, '爪巴')
}
}
}
</script>
这个问题大多数的Vue2开发者都知道了,这里简单说明一下,针对于直接新增、删除对象属性以及根据数组的索引修改值,都需要通过this.$set
或者 this.$delete
进行操作,或者直接再引入一遍vue,通过vue.set
或者 vue.delete
进行操作。对应Vue2的文档说明:深入响应式原理
在vue官方文档当中有这么一段话进行说明:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
⚡️ For example 接下来研究一下为什么监测不到属性变更,
let person = {
name: '张三',
}
let p = {}
Object.defineProperty(p, 'name', {
configurable: true,
get() {
return person.name
},
set(value) {
console.log('name被修改,将更新页面')
person.name = value
}
})
这里简单实现对name的响应式,直接操作p,可以发现,当对p进行新增属性和删除属性的时候,虽然p的值已经被改变了,但是其没有触发到set方法,也就是说这个时候页面不会进行响应更新,基于这一点Vue2当中就提供了 $set
和 $delete
用来做数据响应
答案是很显然的,在vue3当中已经对这个问题进行了处理,我们可以看这个官方文档是如何描述的:Vue 中的响应性是如何工作的
在 JavaScript 中有两种劫持 property 访问的方式:getter / setters 和 Proxies。Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref。
总结: vue2之所以会出现那个问题,是因为Vue2做响应式还是采用的 Object.defineProperty
,这是为了兼容ES6之前的浏览器及版本,但是在ES6之后,ES6新增了一个Proxy代理对象,而对于Vue3来说,Vue3.0在2019年就发布了,自然会舍弃 Object.defineProperty
而采用 Proxy 代理对象来实现响应式
⚡️ For example 接下来研究一下Vue3实现的底层Proxy代理是如何做的
let person = {
name: '张三',
age: 18
}
let proxy = new Proxy(person, {
get(target, propName) {
console.log('属性读取 ====', propName)
// return target[propName]
return Reflect.get(target, propName)
},
set(target, propName, value) {
console.log('属性修改 ====', propName, ' 修改后值 ', value)
// return target[propName] = value
return Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
console.log('删除属性', propName)
// return delete target[propName]
return Reflect.deleteProperty(target, propName)
}
})
✏️ 对于Vue3实现数据响应式还是要归功于ES6新提供的Proxy代理对象,这是因为在代理对象当中可以对整个对象的增删改查的操作都进行拦截,一旦被拦截到了之后Vue3就可以对拦截之后进行页面更新完成数据响应。
在代码当中可以发现,在Proxy代理对象当中对get、set方法就可以获取到需要修改的对象的对象名称、属性、值这些,只需要直接 对象.属性=值
即可,为什么还要使用一个Reflect这不是麻烦吗?
这是因为使用反射对象用来代替Object,⚡️ For example 当使用 Object.defineProperty
来进行控制更改时,一旦错误的对同一属性进行操作,代码直接报错,这对于底层框架来说是很不好的 ,而相同的代码将 Object 换成 Reflect 是不会出现错误的,而使用Reflect会返回一个是否操作成功的标识,后续只需要对该标识进行判断即可,大程度上避免了报错以及报错后的try catch 处理
Object.defineProperty(person, 'c', {
get() {
return 4
}
})
Object.defineProperty(person, 'c', {
get() {
return 5
}
})
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
MDN Proxy对象说明:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
语法
const p = new Proxy(target, handler)
参数
target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
MDN Reflect对象说明:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect