- 请说一下你对响应式数据的理解
- 如何实现响应式数据据
- 对象
- vue2 响应式核心代码
- 数组
- 对象
- vue2 处理缺陷
- Vue3则采用 proxy
- vue3 响应式核心代码
- 如何实现响应式数据据
请说一下你对响应式数据的理解
如何实现响应式数据据
数组和对象类型当值变化时如何劫持到。
对象
对象内部通过defineReactive方法,使用object.defineProperty将属性进行劫持(只会劫持已经存在的属性)
多层对象是通过递归来实现劫持。
vue2 响应式核心代码
let obj = {
name:'jw' ,
age:30,
n: {
num: 000
}
};
// 定义相应式函数
function defineReactive(target,key,value){
// 判断检测对象中是否有嵌套对象
observer(value)
object.defineProperty(target, key,{
get(){
return value
},
set(newValue){
if(value !== newValue){
value = newValue;
// 判断心智是否有是对象
observer(newValue)
}
}
})
}
// 定义观察函数
function observer(data){
// 如果不是对象或者数据为空,直接返回
if(typeof data !== "object" || typeof data == null){
return data
}
// 循环执行第一层数据
for(let key in data){
defineReactive(data, key , data[key])
}
}
observer(obj)
数组
数组则是通过重写数组方法来实现。
Vue2中数组的响应式更新没有使用Object.defineProperty
,而是采用了一种特殊的技术。
使用Object.defineProperty
可以定义对象属性的获取(get)和设置(set)方法,从而实现对属性的拦截和控制。然而,对于数组来说,它只能拦截并控制数组对象本身的变化,而不能直接拦截数组元素的变化。
因此,如果使用Object.defineProperty
来实现数组的响应式,需要对数组的每一个索引进行拦截,监听其变化并触发更新,这样会带来很大的性能开销和复杂度。
为了解决这个问题,Vue2采用了数组的变异方法(mutation method)来触发响应式更新。
这些变异方法,如push
、pop
、shift
、unshift
、splice
、sort
和reverse
,在执行时会被重写,以便在修改数组时同时触发响应式更新。
以下几个数组方法会被重写以实现响应式:
- push():向数组末尾添加一个或多个元素。
- pop():删除并返回数组的最后一个元素。
- shift():删除并返回数组的第一个元素。
- unshift():向数组的开头添加一个或多个元素。
- splice():从指定位置插入、删除或替换元素。
- sort():对数组进行排序。
- reverse():颠倒数组中元素的顺序。
当调用这些方法时,Vue2会拦截它们的调用,并执行以下操作:
- 在Vue初始化阶段: Vue2会对data选项中的数组进行遍历,并重写数组的变异方法。
- 更新依赖:Vue2会更新依赖于该数组的视图或计算属性。
- 触发响应:Vue2会通知相关组件进行重新渲染。
通过重写数组变异方法,Vue2能够在数组被修改时,及时地通知相关组件进行更新,从而实现数组的响应式。
需要注意的是,这种方式只能拦截变异方法的调用,而无法拦截直接通过索引修改数组元素的方式。如果需要修改数组中的某个元素,并触发响应式更新,需要使用Vue提供的特定方法,比如$set
或Vue.set
方法。
var vm = new Vue({
data: {
list: ['apple', 'banana', 'orange']
}
})
// 修改数组,触发响应式更新
vm.list.push('grape');
// 视图会自动更新,list中的元素会显示为['apple', 'banana', 'orange', 'grape']
// 直接通过索引修改数组元素,不会触发响应式更新
vm.list[0] = 'watermelon';
// 视图不会自动更新,list中的元素仍然显示为['apple', 'banana', 'orange', 'grape']
// 使用Vue.set方法修改数组元素,触发响应式更新
Vue.set(vm.list, 0, 'watermelon');
// 视图会自动更新,list中的元素会显示为['watermelon', 'banana', 'orange', 'grape']
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
vue2 处理缺陷
- 在Vue2 的时候使用 defineProperty 来进行数据的劫持,需要对属性进行重写添加getter及setter 性能差。
- 当新增属性和删除属性时无法监控变化。需要通过$set、 $delete实现
- 数组不采用 defineproperty 来进行劫持 (浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理。
- 对于 ES6 中新产生的 Map、Set 这些数据结构不支持
Vue3则采用 proxy
Vue2 不采用 proxy,因为浏览器兼容
vue3 响应式核心代码
let obj = {
name:'jw' ,
age:30,
n: {
num: 000
}
};
let handler = {
get(target, key) {
// 在访问属性时进行依赖收集
let temp = target[key]
// 如果值是对象,则递归监听
if(typeof temp=== "object"){
return new Proxy(temp, handler)
}
// 否则直接返回
return temp
},
set(target, key, value) {
// 在更新属性时触发依赖更新
target[key] = value
},
}
function reactive(target) {
return new Proxy(target, handler);
}
const proxy = reactive(obj)
reactive函数接受一个普通对象作为参数,并返回一个经过代理的响应式对象。
这个代理对象利用Proxy的get和set方法来拦截属性的读取和修改操作,并触发相应的依赖收集和更新。
复杂实例
在Vue 3中,可以使用Proxy
对象来实现响应式实例。
Proxy
是ES6引入的新特性,它可以拦截并自定义对象的操作。
下面是一个使用Proxy
实现响应式实例的示例:
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 在访问属性时进行依赖收集
track(target, key);
return Reflect.get(target, key);
},
set(target, key, value) {
// 在更新属性时触发依赖更新
const oldValue = target[key];
const result = Reflect.set(target, key, value);
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
// 在删除属性时触发依赖更新
const hasKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hasKey) {
trigger(target, key);
}
return result;
}
});
}
在上述代码中,reactive
函数接受一个普通对象作为参数,并返回一个经过代理的响应式对象。这个代理对象利用Proxy
的get
和set
方法来拦截属性的读取和修改操作,并触发相应的依赖收集和更新。
需要注意的是,上述代码中的track
和trigger
函数是为了配合依赖收集和更新使用的,它们在实际应用中需要根据具体场景进行实现。
使用上述的reactive
函数,我们可以将一个普通对象转换成响应式实例。例如:
const user = reactive({
name: 'Alice',
age: 25
});
console.log(user.name); // 输出:'Alice'
user.age = 26; // 触发依赖更新
通过reactive
函数创建的user
对象就是一个响应式实例了。当访问user
对象的属性时,会自动进行依赖收集;当更新属性的值时,会触发相应的依赖更新。这样就实现了Vue 3中的响应式机制。
由于Proxy
是ES6的新特性,不支持的浏览器可能无法正常运行上述代码。
在实际开发中,可以使用Babel等工具进行转换,以兼容不同的浏览器环境。