再说vue响应式数据

请说一下你对响应式数据的理解

如何实现响应式数据据

数组和对象类型当值变化时如何劫持到。

对象

对象内部通过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)来触发响应式更新。

这些变异方法,如pushpopshiftunshiftsplicesortreverse,在执行时会被重写,以便在修改数组时同时触发响应式更新。

以下几个数组方法会被重写以实现响应式:

  1. push():向数组末尾添加一个或多个元素。
  2. pop():删除并返回数组的最后一个元素。
  3. shift():删除并返回数组的第一个元素。
  4. unshift():向数组的开头添加一个或多个元素。
  5. splice():从指定位置插入、删除或替换元素。
  6. sort():对数组进行排序。
  7. reverse():颠倒数组中元素的顺序。

当调用这些方法时,Vue2会拦截它们的调用,并执行以下操作:

  1. 在Vue初始化阶段: Vue2会对data选项中的数组进行遍历,并重写数组的变异方法。
  2. 更新依赖:Vue2会更新依赖于该数组的视图或计算属性。
  3. 触发响应:Vue2会通知相关组件进行重新渲染。

通过重写数组变异方法,Vue2能够在数组被修改时,及时地通知相关组件进行更新,从而实现数组的响应式。

需要注意的是,这种方式只能拦截变异方法的调用,而无法拦截直接通过索引修改数组元素的方式。如果需要修改数组中的某个元素,并触发响应式更新,需要使用Vue提供的特定方法,比如$setVue.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函数接受一个普通对象作为参数,并返回一个经过代理的响应式对象。这个代理对象利用Proxygetset方法来拦截属性的读取和修改操作,并触发相应的依赖收集和更新。

需要注意的是,上述代码中的tracktrigger函数是为了配合依赖收集和更新使用的,它们在实际应用中需要根据具体场景进行实现。

使用上述的reactive函数,我们可以将一个普通对象转换成响应式实例。例如:

const user = reactive({
  name: 'Alice',
  age: 25
});

console.log(user.name); // 输出:'Alice'

user.age = 26; // 触发依赖更新

通过reactive函数创建的user对象就是一个响应式实例了。当访问user对象的属性时,会自动进行依赖收集;当更新属性的值时,会触发相应的依赖更新。这样就实现了Vue 3中的响应式机制。

由于Proxy是ES6的新特性,不支持的浏览器可能无法正常运行上述代码。

在实际开发中,可以使用Babel等工具进行转换,以兼容不同的浏览器环境。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布道人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值