深入理解Vue的数据响应式


Posted in Vue.js onMay 15, 2021

1. ES语法的getter和setter

在开始了解 Vue 的数据响应式原理前应该先搞清楚 ES语法 中的 getter 和 setter 方法的具体用法。

getter和setter 方法是以 get 和 set 关键字来为对象添加虚拟属性的一种方式。这种属性其实并不真实存在,而是以取值函数 getter 和存值函数 setter 来模拟的一种属性。目的是对某个属性设置存值函数和取值函数,拦截该属性的存取行为,以便于对该属性的存取做一些限定处理。如下所示(以下代码来源于 mdn)

getter 方法

const obj = {
  log: ['a', 'b', 'c'],
  get latest() { //在正常方法前加 get 关键字
    if (this.log.length == 0) {
      return undefined;
    }
    return this.log[this.log.length - 1];
  }
}

console.log(obj.latest);// 输出 c,获取的是属性名不用带括号

setter方法

const language = {
  set current(name) {
    this.log.push(name);
  },
  log: []
}

language.current = 'EN';
language.current = 'FA';

console.log(language.log);//输出 Array ["EN", "FA"]

2. ES语法的 defineProperty

defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,可用于在一个对象定义好后为其修改或添加属性。
语法为:

Object.defineProperty(obj, prop, descriptor)

添加常规属性:

let data = {
  m: 0
}

Object.defineProperty(data, 'n', {
  value: 1 //添加属性的 value 就是其值
})
console.log(`${data.n}`) //则会输出n值为1

也可用来添加 getter 和 setter 的虚拟属性

let data1 = {
    _n: 0
}

Object.defineProperty(data1, 'n', {
  get(){
    return this._n
  }, 
  set(value){
    if(value < 0) return
    this._n = value
  }//直接写明 get / set 即可
}) //由于指明了虚拟属性为 n,即 get n(){}、set n(value){},因此在函数定义时就不用再写n了

3. Vue对数据的代理和监听

代理,即 proxy,简单来说我自己的一些事情我自己不亲自处理,而是交给一个人让他去帮我做,那个做事的人就是代理。这个逻辑中有两个关键点需要搞清楚,代理是处理操作的人,而其处理操作的事情不属于他,而是属于委托其代理的人的。

因此类比到 Vue数据代理 ,委托代理的是 data{} 数据对象,其找到代理就是 Vue实例vm ,data{} 数据对象要代理 vm 做的事情是管理 data{} 数据对象里数据操作。因此 data{} 数据对象只负责内部数据的生产即可,对生产出来的数据的管理和操作全权交给 vm 处理。

那么 vm 如何对 data{} 数据对象里的数据进行控制和操作呢?换句话说,vm 如何在 data{} 数据对象里面的任意一个属性值变化时都及时知道呢?

于是便用到了 ES 语法中的 getter和setter 方法,通过 getter和setter 方法控制的属性的任何操作都会被这两个函数检测到,而 getter和setter 方法形成的属性是虚拟属性,真实并不存在,因此如果用户想私自不经过代理 vm 直接修改 data{} 数据对象的属性也获取不到对应的实体属性,只能通过 getter和setter 方法修改,那么其修改就必定被 vm 检测到。

因此 vm 为了实现对 data{} 数据对象里数据的全部控制,就必须在 Vue实例 创建的时候对传进来的 data{} 数据对象做一些处理,做的处理就是将 data{} 数据对象里的属性都变成了 getter和setter 方法控制的虚拟属性,并保存在代理数据对象 obj 并返回。

但为了不让用户直接修改原来的 data{} 属性,也将原来的 data{} 对象的实体属性全改变了,添加的虚拟属性名字和实体属性名一样,就会用虚拟属性覆盖原来的实际属性,用户在修改属性值是就是通过 getter和setter 方法修改的虚拟属性。这样一来 data{} 数据对象的全部属性的任何变化都会被 Vue实例vm 检测到。

let myData = {n:0}
let data = proxy({ data:myData }) // 类似于 let vm = new Vue({data: myData})

function proxy({data}/* 解构赋值*/){
  let _n = data.n
  Object.defineProperty(data, 'n', { //覆盖原来的data.n属性
    get(){
      return _n
    },
    set(newValue){
      if(newValue<0)return
      _n = newValue
    }
  })// 改变data{}数据对象本身属性,可通过闭包形成上下文,让原来的实际属性值存在闭包的上下文_n中
  
  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      data.n = value
    }
  }) //添加data{}数据对象的代理,对data{}数据对象操作
  
  return obj // obj 就是data{}的代理
}

4. Vue的数据响应式

所谓响应式就是当事物发生变化时会根据变化做出相应的反映。

Vue 中的数据 data 是响应式的,由上述 Vue 通过 Object.defineProperty()函数 来用 getter和setter方法 对 data 数据做了代理和监听,一旦数据发生变化,Vue 就会改变数据对应的 UI 视图,这就是 Vue的数据响应式

但是 Vue 使用 Object.defineProperty 来设置监听,就只能对在 Vue实例化 时 data 对象里已经存在的属性设置监听,而对不存在的或者后来添加进去的属性没有进行监听。

为了解决这个问题,有两种方法:

1. 将所有属性都提前声明好

2. 使用 Vue.set 和 this.$set 添加属性

使用 Vue.set 和 this.$set 添加属性是会通知 Vue 对这后添加的属性也设置监听操作。

Vue.set('this.data','m','10')
this.$set('this.data','m','10')//为vm的data对象添加属性m值为10

3.数组变异

对于数组的数据增加,无法控制其新增个数因此不能提前声明所有数据值,而一个一个 set 又太麻烦,而且数组是常用的对象数据类型中的一种,因此 vue 的作者就对数组的增删函数如 push 和 pop 等进行了篡改,用户在使用 vue 中数组增删时仍是用 push 和 pop ,但是里面进行了额外的处理,这几个被篡改的 API 会对数组新增是数据代理监听并根据数据响应改变 UI 视图。

以上就是深入理解Vue的数据响应式的详细内容,更多关于Vue的数据响应式的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互的实例
Nov 18 Vue.js
如何实现vue的tree组件
Dec 03 Vue.js
如何在vue中使用kindeditor富文本编辑器
Dec 19 Vue.js
vue 导航守卫和axios拦截器有哪些区别
Dec 19 Vue.js
vue 项目@change多个参数传值多个事件的操作
Jan 29 Vue.js
vue 根据选择的月份动态展示日期对应的星期几
Feb 06 Vue.js
Vue包大小优化的实现(从1.72M到94K)
Feb 18 Vue.js
vue路由实现登录拦截
Mar 24 Vue.js
一文带你理解vue创建一个后台管理系统流程(Vue+Element)
May 18 Vue.js
vue响应式原理与双向数据的深入解析
Jun 04 Vue.js
Vue3.0写自定义指令的简单步骤记录
Jun 27 Vue.js
vue3中provide && inject的使用
Jul 01 Vue.js
详解Vue的options
May 15 #Vue.js
vue实现无缝轮播效果(跑马灯)
May 14 #Vue.js
Vue接口封装的完整步骤记录
Vue全家桶入门基础教程
vue实现可拖拽的dialog弹框
vue如何批量引入组件、注册和使用详解
vue组件的路由高亮问题解决方法
You might like
PHP 数组基本操作方法详解
2016/06/17 PHP
PHP文件操作实例总结
2016/09/27 PHP
laravel框架的安装与路由实例分析
2019/10/11 PHP
PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)
2020/08/03 PHP
java与javascript之间json格式数据互转介绍
2013/10/29 Javascript
Asp.Net alert弹出提示信息的几种方法总结
2014/01/29 Javascript
javascript:window.open弹出窗口的位置问题
2014/03/18 Javascript
深入探讨JavaScript String对象
2015/03/09 Javascript
PHP+MySQL+jQuery随意拖动层并即时保存拖动位置实例讲解
2015/10/09 Javascript
JS实现不使用图片仿Windows右键菜单效果代码
2015/10/22 Javascript
前端jquery部分很精彩
2016/05/03 Javascript
js弹出框、对话框、提示框、弹窗实现方法总结(推荐)
2016/05/31 Javascript
jQuery的ajax和遍历数组json实例代码
2016/08/01 Javascript
JavaScript实现左右下拉框动态增删示例
2017/03/09 Javascript
js实现rem自动匹配计算font-size的示例
2017/11/18 Javascript
vue鼠标移入添加class样式,鼠标移出去除样式(active)实现方法
2018/08/22 Javascript
JS定义函数的几种常用方法小结
2019/05/23 Javascript
详解webpack打包vue项目之后生成的dist文件该怎么启动运行
2019/09/06 Javascript
vue + elementUI实现省市县三级联动的方法示例
2019/10/29 Javascript
vue实现移动端拖动排序
2020/08/21 Javascript
rabbitmq(中间消息代理)在python中的使用详解
2017/12/14 Python
python3实现多线程聊天室
2018/12/12 Python
Django认证系统实现的web页面实现代码
2019/08/12 Python
Python 发送邮件方法总结
2020/08/10 Python
基于Python正确读取资源文件
2020/09/14 Python
IE兼容css3圆角的实现代码
2011/07/21 HTML / CSS
mui几种页面跳转方式对比总结概括
2017/08/18 HTML / CSS
Lee牛仔裤澳大利亚官网:美国著名牛仔裤品牌
2017/09/02 全球购物
Gtech官方网站:地毯清洁器、吸尘器及园艺设备
2018/05/23 全球购物
奥地利智能家居和智能生活网上商店:tink.at
2019/10/07 全球购物
学校庆元旦歌咏比赛主持词
2014/03/18 职场文书
初中升旗仪式演讲稿
2014/05/08 职场文书
群众路线教育实践活动对照检查材料
2014/09/22 职场文书
党的群众路线教育实践活动个人对照检查材料(公安)
2014/11/05 职场文书
2015仓库保管员年终工作总结
2015/05/13 职场文书
2015银行年终工作总结范文
2015/05/26 职场文书