详解Vue响应式的部分实现

 更新时间:2022年12月08日 14:32:48   作者:小婉子啊  
响应式,简单来说当数据发生变化时,对数据有依赖的代码会重新执行。这篇文章主要为大家介绍了Vue中响应式的部分实现,感兴趣的可以了解一下

什么是响应式

简单来说当数据发生变化时,对数据有依赖的代码会重新执行

例如在Vue中,当我们的数据发生改变,界面上对该数据的引用组件会重新渲染

组件data的数据一旦变化,立即出发视图的更新;

computed属性在依赖发生变化时,自动重新计算新值;提供watch监听器,可以监听到数据的变化

Vue2与Vue3响应式之间的区别

  • Vue2使用ES5的defineProperty实现
  • Vue3使用的是ES6的propxy.(PS:这也就是为什么Vue2不支持IE7/8,而Vue3不支持IE11.)

使用Object.defineProperty监听对象

该方法允许精确地添加或修改对象的属性,并返回此对象。

Object.defineProperty()方法会在直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

备注:(应当直接在object构造器对象上调用此方法,而不是在任意一个object类型的实例上调用)

语法:Object.defineProperty(obj, prop, descriptor)

  • obj:要设置属性的对象;
  • prop:要设置的属性名,这个属性可以是已存在也可以是不存在的;
  • descriptor:要定义或修改的属性描述符。该参数接收一个对象,用来对属性进行描述。如value(值),writable(是否可重写),enumerable(是否可枚举)等

枚举时使用for...inObject.keys方法可以改变这些属性的值,默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符

  • 数据描述符:是一个具有值的属性,该值是可写的,也可以是不可写的
  • 存取描述符:由getter函数和setter函数所描述的属性

一个描述符只能是这两者其中之一,不能同时是两者

使用Object.defineProperty监听对象

利用 Object.defineProperty 重写 getset,将对象属性的赋值和获取变成函数,我们可以实现一个简单的双向绑定

  • get: 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值会被用作属性的值。默认为 undefined。
  • set: 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。默认为 undefined。
//实现一个简单的双向绑定
const data = {}
const name = 'xiaowanzi'
Object.defineProperty(data, 'name', {
  get: function () {
    console.log('get')
    return name
  },
  set: function (newVal) {
    console.log('set')
    name = newVal
  }
})
//测试
console.log(data.name)//get xiaowanzi
data.name = 'list'//set

如果我们想让对象的所有属性都具有响应式,就需要对全部属性进行遍历,实现getter和setter:

//实现Vue响应式原理
let obj = {
  name: 'aaa',
  age: 18
}
//获取obj对象的所有key
const keys = Object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
//遍历Key数组,对obj对象的每一个属性进行处理
keys.forEach(key => {
  //使用value变量保存key对应的属性值
  let value = obj[key]
  //使用Object.defineProperty
  Object.defineProperty(obj, key, {
    get() {//当获取属性时,回来到这里
      console.log(`${key}属性被获取`)
      return value
    },
    set(newValue) {//当修改属性时,会来到这里,并且设置的值会传给newValue
      console.log(`${key}属性被修改`)
      //这里不能写成obj[key]=newValue
      //如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了
      value = newValue
    }
  })
})
​
//现在我们已经可以实现监听obj对象的读取与修改了
console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。
obj.name = 'bbb'//打印name属性被修改,也就是说监听到了属性的改变
​
//实现Vue响应式原理
let obj={
  name:'aaa',
  age:18
}
//获取obj对象的所有key
const keys=object.keys(obj)//Object.keys()返回一个由一个给定对象的资深可枚举属性组成的数组,数组中的属性名的排列顺序和正常循环遍历该对象时返回的顺序一致
//遍历Key数组,对obj对象的每一个属性进行处理
keys.forEach(key=>{
  //使用value变量保存key对应的属性值
  let value = obj[key]
  //使用Object.defineProperty
  Object.defineProperty(obj,key,{
      get(){//当获取属性时,回来到这里
        console.log(`${key}属性被获取`)
        return value
      },
      set(newValue){//当修改属性时,会来到这里,并且设置的值会传给newValue
        console.log(`${key}属性被修改`)
        //这里不能写成obj[key]=newValue
        //如果这样写相当于又对该属性进行修改值,又会进入set,就死循环了
        value=newValue
      }
  })
})
​
//现在我们已经可以实现监听obj对象的读取与修改了
console.log(obj.name)//在打印'aaa'之前会先打印'name被获取',也就是说监听到属性的获取。
obj.name='bbb'//打印name属性被修改,也就是说监听到了属性的改变

缺点

可以实现监听对象的属性,但是它没有办法做到对对象新增的属性进行监听,同时也没有办法做到对数据进行监听

使用ES6的Proxy实现监听对象

该API就是用来实现监听对象的,而且该API对数组同样也是有效果的,在使用Proxy时,通常会搭配Reflect一起使用 Proxy

用于创建代理对象,从而实现基本操作的拦截和自定义(如属性的查找,赋值,枚举,函数调用等)

术语:

  • handler:包含捕捉器(trap)的占位符对象,可译为处理器对象。
  • traps:提供属性访问的方法。这类似于操作系统中捕获器的概念。
  • target:被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)

语法:

const p = new Proxy(target, handler)

1.第一个参数target:要包装的目标对象

2.第二个参数handle:接收一个对象,内部定义了操作目标对象时的方法;

参数:

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

方法

Reflect

是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect不是一个函数对象,因此它是不可构造的。

通过给对象设置代理,我们可以拦截对象属性的取值/赋值操作。

举个例子:

const student = {
  age: 23
}
const handler = {
  get(target, prop) {
    console.log("读值:", key, value);
    target[key] = value;
    return target[prop]
  },
  set(target, key, value) {
    console.log("设置值", key, value);
    target[key] = value;
    return true
  }
}
const proxy = new Proxy(studengt, handler)
console.log(proxy.age)//23
//proxy.age=32   //32

实现代码

//Proxy+Reflect
let obj = {
  name: 'aaa',
  age: 18
}
//第一个参数为要代理的对象,第二个参数位hander
const proxy = new Proxy(obj, {
  //当访问第一个属性的时候会得到getter
  //同时会传递三个参数
  //target要进行代理对象,这里就是obj
  //key被访问的属性
  //receiver用来绑定this
  get(target, key, receiver) {
    console.log(`${key}属性被访问`)
    return Reflect.get(target, key, receiver)
  },
  //当某一属性修改的时,回来到Setter
  // 同时会传递四个参数
  // target要进行代理的对象,这里就是obj
  // 可以被访问的属性
  // newValue新修改的值
  // receiver用来绑定this
  set(target, key, newValue, receiver) {
    console.log(`${key}属性修改`)
    return Reflect.set(target, key, newValue, receiver)
  }
})
// 以上代码执行完,得到的就是proxy对象就是obj对象的代理
// 我们只需要修改代理对象的就可以做到修改原型对象的效果
// 而且我们对代理对象的修改使我们能够监听到的
console.log(proxy.name)
proxy.name = 'bbb'

到此这篇关于详解Vue响应式的部分实现的文章就介绍到这了,更多相关Vue响应式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 从dubbo源码分析qos-server端口冲突问题及解决

    从dubbo源码分析qos-server端口冲突问题及解决

    这篇文章主要介绍了从dubbo源码分析qos-server端口冲突问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Spring mvc Controller和RestFul原理解析

    Spring mvc Controller和RestFul原理解析

    这篇文章主要介绍了Spring mvc Controller和RestFul原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java接口方法默认静态实现代码实例

    Java接口方法默认静态实现代码实例

    这篇文章主要介绍了Java接口方法默认静态实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Java中的MapStruct用法详解

    Java中的MapStruct用法详解

    这篇文章主要介绍了Java中的MapStruct用法详解,MapStuct的使用非常简单,把对应的jar包引入即可,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • SpringBoot整合Aop全过程

    SpringBoot整合Aop全过程

    AOP(面向切面编程)技术可以高效地解决日志记录、事务管理、权限控制等问题,日志记录通过自定义注解和切面类,自动记录方法调用详情,减少重复代码,事务管理方面,通过AOP可以在不改变业务代码的情况下,实现事务的自动开启、提交和回滚,保证数据一致性
    2024-10-10
  • SpringBoot整合Mybatis-plus案例及用法实例

    SpringBoot整合Mybatis-plus案例及用法实例

    mybatis-plus是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生,下面这篇文章主要给大家介绍了关于SpringBoot整合Mybatis-plus案例及用法实例的相关资料,需要的朋友可以参考下
    2022-11-11
  • MyBatis一级与二级缓存相关配置

    MyBatis一级与二级缓存相关配置

    mybatis-plus是一个Mybatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发、提高效率而生,这篇文章带你了解Mybatis的一级和二级缓存
    2023-01-01
  • Spring-Data-JPA整合MySQL和配置的方法

    Spring-Data-JPA整合MySQL和配置的方法

    这篇文章主要介绍了Spring Data JPA整合MySQL和配置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • SpringBoot使用AOP记录接口操作日志的方法

    SpringBoot使用AOP记录接口操作日志的方法

    日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch中,如果存在数据库中,分表是不错的选择,这篇文章主要介绍了SpringBoot使用AOP记录接口操作日志的方法,需要的朋友可以参考下
    2022-08-08
  • 浅谈Java的两种多线程实现方式

    浅谈Java的两种多线程实现方式

    本篇文章主要介绍了浅谈Java的两种多线程实现方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论