Vue2的响应式
核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持;
数据劫持 --> 给对象扩展属性 --> 属性设置
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
-
比如说在 vue2 中,data 数据中有一个数组, 当我们调用data 数组中的push方法,我们调用的是经过二次封装的push, 和 array原型中的push 方法不是一个事;
-
被封装的 push 方法中主要做了两件事:
-
帮们正常调用数据的push方法
-
帮我们更新界面 ;
-
-
-
-
存在问题:
-
新增属性、删除属性, 界面不会更新。
-
直接通过下标修改数组, 界面不会自动更新。
-
Vue2框架中数据响应式的优缺点
<template>
<div>
<h1>我是Vue2写的效果</h1>
<h2 v-show="person.name">姓名: {{fperson.name}}</h2>
<h2 v-show="person.sex">性别: {{person.sex}} </h2>
<h2>年龄: {{ person.age }} </h2>
<h2>爱好: {{ person.hobby}} </h2>
<button @click="addSex">添加一个sex属性</button>
<button @click="deleteName">删除name属性</button>
<button @click="updateHobby">修改第一个爱好的名字</button>
</div>
</template>
<script>
import Vue from 'vue'
export default {
name:'App'
data() {
return {
person:{
name:'张三',
age:18,
hobby: ["学习", "吃饭"]
}
}
}
methods: {
// 新增属性、删除属性, 界面不会更新
// 添加对象属性
addSex() {
// 数据改变页面没有监听到!
this.person.sex = '女'
// 解决方式: $set() 给对象追加一个响应式数据
this.$set(this.person, 'sex', '女')
Vue.set(this.person, 'sex', '女')
}
// 删除对象属性
deleteName(){
// delete this.person.name
// 移除一个响应式数据
this.$delete(this.person, name)
Vue.delete(this.person, 'name', '女')
}
// 直接通过下标修改数组, 界面不会自动更新。
updateHobby() {
// this.person.hobby[0] = '逛街'
this.$set(this.person.hobby, 0, '逛街')
// 调用 Array 中的变更方法可以实现修改
this.person.hobby.splice(0, 1, '逛街')
}
}
</script>
}
Vue2数据响应式原理
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E8%AF%AD%E6%B3%95
Object.defineProperty(obj, prop, descriptor)
obj
: 要定义属性的对象。prop:
要定义或修改的属性的名称或 Symbol 。descriptor
: 要定义或修改的属性描述符。- 返回值: 被传递给函数的对象。
function defineProperty () {
var _obj = {}
Object.defineProperty(_obj, 'a', {
value: 1
})
return _obj
}
var obj = defineProperty()
console.log(obj); //{a:1}
第三个参数里面还有6个配置控住属性
- writable:是否可重写
- value: 当前值
- get: 读取时内部调用的函数
- set: 写入时内部调用的函数
- enumerable:是否可以遍历
- configurable: 是否可再次修改配置项
<body>
<script type="text/javascript">
// 源数据
let person = {
name: '张三',
age: 18
}
// 模拟vue2中实现响应式
let p = {}
// p 对象身上追加响应式数据
Object.defineProperty(p, 'name', {
// 可配置的, 配置删除使用
configurable: true,
get() { // 有人读取name时调用
return person.name
},
set(value) { // 有人修改name时调用
console.log('有人修改了name属性, 我发现了, 我要去更新页面!');
person.name = value
}
})
Object.defineProperty(p, 'age', {
configurable: true,
value:"男", //设置属性值
enumerable:true, //控制属性是否可以枚举,默认值是false
writable:true, //控制属性是否可以被修改、可写,默认值是false
configurable:true //控制属性是否可以被删除、可配置,默认值是false
get() { // 有人读取age时调用
return person.age
},
set(value) { // 有人修改age时调用
console.log('有人修改了age属性, 我发现了, 我要去更新页面!');
person.age = value
}
})
// 数据修改
// p.age = 20 -- 有人修改了age属性, 我发现了, 我要去更新页面!
// 数据删除
// delete p.name -- true 主要需要 configurable: true
// 属性添加; 但是不是响应式的没有 set get 方法;
// p.sex = '男'
</script>
</body>
writable:true 控制属性是否可以被修改,控制台也看的当为TRUE的时候属性值可以被修改
configurable:true 控制属性是否可以被删除
enumerable:true 控制属性是否可以枚举,true的话简单的说就是可以遍历获取该值还有最重要的两个属性 set和get(即存取器描述:定义属性如何被存取),这两个属性是干嘛的?
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错滴)
get 是获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为undefined
set 是设置值的时候的方法,类型为 function ,设置值的时候会被调用,undefined
get或set不是必须成对出现,任写其一就可以
get:
当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
每一次获取属性值时,都会先走get方法,我们可以在返回该属性值之前,做一些事情;
set:
当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
每一次改变属性值时,都会先走set方法,相应的,我们也可以在这里做一些事情;
可以实现一个数据的联动效果
Vue3.0的响应式
-
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
-
Proxy:Proxy - JavaScript | MDN
-
Reflect:Reflect - JavaScript | MDN
-
-
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
Vue3响应式原理_Proxy
<body>
<script>
// 源数据
let person = {
name: '张三',
age: 18
}
//模拟 Vue3中实现数据响应式
// 定义一个代理对象p; 让p去映射对person的操作, 能检测到变化!
const p = new Proxy(person, {
// 完成数据的响应式, 捕获数据的修改
get(target, propName) {// 获取读取 target: 源数据; propName: 读取修改的属性
console.log(`有人读取了 p 身上的${ propName }属性!`);
return target[propName]
},
set(target, propName, value) { // 修改,追加调用
console.log(`有人修改了 p 身上的${ propName }属性, 我要去更新页面了!`);
target[propName] = value
},
deleteProperty(target, propName) { // 删除调用 target: 要删除的源数据; propName: 删除属性
console.log(`有人删除了 p 身上的${ propName }属性, 我要去更新页面了!`);
return delete target[propName]
}
})
// p.sex ="男" 添加属性
// delete p.age true 删除属性
// p.name = "ls" 有人修改了 p 身上的name属性, 我要求修改页面了!
// delete p.age 有人删除了 p 身上的age属性, 我要去更新页面了!
// p.sex ='男' 有人修改了 p 身上的sex属性, 我要去更新页面了!
</script>
</body>
最后总结:
1、defineProperty是对一个空对象进行设置属性,在读取和改变属性值的拦截一下,可以实现一些其他的逻辑处理。不支持函数拦截;
2、Proxy是对一个完整的对象进行代理,相当于拷贝了一份,当我们对原数据进行属性值操作时,这个代理对象会挡在前面对数据进行操作。支持对象代理,数组代理和函数代理。