详解实现vue的数据响应式原理


Posted in Vue.js onJanuary 20, 2021

这篇文章主要是给不了解或者没接触过 vue 响应式源码的小伙伴们看的,其主要目的在于能对 vue 的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」

响应式的理解

响应式顾名思义就是数据变化,会引起视图的更新。这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析。

在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据。vue 内部通过 Object.defineProperty 方法对对象的属性进行劫持,数组则是通过重写数组的方法实现的。下面我们就简单实现一下。

首先我们定义一个需要被拦截的数据

const vm = new Vue({
 data () {
  return {
   count: 0,
   person: { name: 'xxx' },
   arr: [1, 2, 3]
  }
 }
})
let arrayMethods
function Vue (options) { // 这里只考虑对 data 数据的操作
 let data = options.data
 if (data) {
  data = this._data = typeof data === 'function' ? data.call(this) : data
 }
 observer (data)
}
function observer(data) { 
 if (typeof data !== 'object' || data === null) {
  return data
 }
 if (data.__ob__) { // 存在 __ob__ 属性,说明已经被拦截过了
  return data
 }
 new Observer(data)
}

这里的 arrayMethods、Observer 、 __ob__的实现和作用请继续往下看

实现 Observer 类

class Observer {
 constructor (data) {
  Object.defineProperty(data, '__ob__', { // 在 data 上定义 __ob__ 属性,在数组劫持里需要用到
   enumerable: false, // 不可枚举
   configurable: false, // 不可配置
   value: this // 值是 Observer 实例
  })
  if (Array.isArray(data)) { // 对数组进行拦截
   data.__proto__ = arrayMethods // 原型继承
   this.observerArray(data)
  } else { // 对象进行拦截
   this.walk(data)
  }
 }
 walk (data) {
  const keys = Object.keys(data)
  for(let i = 0; i < keys.length; i++) {
   const key = keys[i]
   defineReactive(data, key, data[key])
  }
 }
 observerArray (data) { // 拦截数组中的每一项
  data.forEach(value => observer(value))
 }
}

对象的拦截

对象的劫持需要注意的几点:

  • 遍历对象,如果值还是对象类型,需要重新调用 observer 观测方法
  • 如果设置的新值是对象类型,也需要被拦截
// 处理对象的拦截
function defineReactive(data, key, value) {
 observer(value) // 如果 value 值仍是对象类型,需要递归劫持
 Object.defineProperty(data, key, {
  get() {
   return value
  },
  set(newValue){
   if (newValue === value) return
   value = newValue
   observer(newValue) // 如果设置 newValue 值也是对象类型,需要被劫持
  }
 })
}

数组的劫持

数组的劫持需要注意的几点:

  • 数组是使用函数劫持(切片编程)的思想,对数据进行拦截的
  • 数组里新增加的值,如果是对象类型,也需要被重新拦截
const oldArrayPrototype = Array.prototype
arrayMethods = Object.create(oldArrayPrototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 能够改变原数组的方法
methods.forEach(method => {
 arrayMethods[methods] = function (...args) {
  const result = oldArrayPrototype[methods].call(this, ...args)
  const ob = this.__ob__ // this 就是调用改方法的数组
  let inserted; // 数组新增的项的集合,需要再对其进行拦截
  switch(methods) {
   case 'push': 
   case 'unshift':
    inserted = args
   case 'splice':
    inserted = args.slice(2) // 因为 splice 第二个参数后面的才是新增的
  }
  if (inserted) {
   ob.observerArray(inserted)
  }
  return result
 }
})

原理总结

在面试中,如果我们需要手写 vue 的响应式原理,上面的代码足矣。但是我们通过学习 vue 的源码,如果在面试中能够给出以下加以总结性的回答更能得到面试官的青睐。

vue 2.0 源码的响应式原理:

  • 因为使用了递归的方式对对象进行拦截,所以数据层级越深,性能越差
  • 数组不使用 Object.defineProperty 的方式进行拦截,是因为如果数组项太多,性能会很差
  • 只有定义在 data 里的数据才会被拦截,后期我们通过 vm.newObj = 'xxx' 这种在实例上新增的方式新增的属性是不会被拦截的
  • 改变数组的索引和长度,不会被拦截,因此不会引起视图的更新
  • 如果在 data 上新增的属性和更改数组的索引、长度,需要被拦截到,可以使用 $set 方法
  • 可以使用 Object.freeze 方法来优化数据,提高性能,使用了此方法的数据不会被重写 set 和 get 方法

vue 3.0 源码响应式原理:

  • 3.0 版本中使用了 proxy 代替了 Object.defineProperty ,其有13中拦截方式,不需要对对象和数组分别进行处理,也无需递归进行拦截,这也是其提升性能最大的地方
  • vue 3.0 版本响应式原理的简单实现
const handler = {
 get (target, key) {
  if (typeof target[key] === 'object' && target[key] !== null) {
   return new Proxy(target[key], handler)
  }
  return Reflect.get(target, key)
 },
 set (target, key, value) {
  if(key === 'length') return true
  console.log('update')
  return Reflect.set(target, key, value)
 }
}
const obj = {
 arr: [1, 2, 3],
 count: { num: 1 }
}
// obj 是代理的目标对象, handler 是配置对象
const proxy = new Proxy(obj, handler)

到此这篇关于详解实现vue的数据响应式原理的文章就介绍到这了,更多相关vue 数据响应式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木! 

Vue.js 相关文章推荐
Vue使用Element实现增删改查+打包的步骤
Nov 25 Vue.js
vue开发chrome插件,实现获取界面数据和保存到数据库功能
Dec 01 Vue.js
如何实现vue的tree组件
Dec 03 Vue.js
vue+elementui通用弹窗的实现(新增+编辑)
Jan 07 Vue.js
基于VUE实现简单的学生信息管理系统
Jan 13 Vue.js
vue完美实现el-table列宽自适应
May 08 Vue.js
详解vue身份认证管理和租户管理
May 25 Vue.js
Vue实现tab导航栏并支持左右滑动功能
Jun 28 Vue.js
Vue中Object.assign清空数据报错的解决方案
Mar 03 Vue.js
vue3使用vuedraggable实现拖拽功能
Apr 06 Vue.js
vue数据字典取键值项目的字典问题
Apr 12 Vue.js
ant design vue的form表单取值方法
Jun 01 Vue.js
vue实现简易计算器功能
Jan 20 #Vue.js
vue使用过滤器格式化日期
Jan 20 #Vue.js
Vue实现简单计算器
Jan 20 #Vue.js
vue实现验证用户名是否可用
Jan 20 #Vue.js
vue实现按钮切换图片
Jan 20 #Vue.js
Vue实现图书管理案例
Jan 20 #Vue.js
详解vue之自行实现派发与广播(dispatch与broadcast)
Jan 19 #Vue.js
You might like
PHP备份/还原MySQL数据库的代码
2011/01/06 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
ThinkPHP框架实现的MySQL数据库备份功能示例
2018/05/24 PHP
js常用代码段收集
2011/10/28 Javascript
javascript ie6兼容position:fixed实现思路
2013/04/01 Javascript
javascript标签在页面中的位置探讨
2013/04/11 Javascript
IE6已终止操作问题的2种情况及解决
2014/04/23 Javascript
Node.js开发之访问Redis数据库教程
2015/01/14 Javascript
vue webuploader 文件上传组件开发
2017/09/23 Javascript
webpack源码之loader机制详解
2018/04/06 Javascript
详解解决使用axios发送json后台接收不到的问题
2018/06/27 Javascript
解决vue数组中对象属性变化页面不渲染问题
2018/08/09 Javascript
详解一次Vue低版本安卓白屏问题的解决过程
2019/05/30 Javascript
p5.js实现故宫橘猫赏秋图动画
2019/10/23 Javascript
[02:05]2014DOTA2西雅图国际邀请赛 BBC第二天小组赛总结
2014/07/11 DOTA
[56:24]DOTA2上海特级锦标赛主赛事日 - 3 胜者组第二轮#1Liquid VS MVP.Phx第二局
2016/03/04 DOTA
详解在Python程序中使用Cookie的教程
2015/04/30 Python
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
Python简单计算数组元素平均值的方法示例
2017/12/26 Python
[原创]Python入门教程2. 字符串基本操作【运算、格式化输出、常用函数】
2018/10/29 Python
Python使用pandas对数据进行差分运算的方法
2018/12/22 Python
python求最大值最小值方法总结
2019/06/25 Python
python django 原生sql 获取数据的例子
2019/08/14 Python
完美解决python针对hdfs上传和下载的问题
2020/06/05 Python
浅析Python 多行匹配模式
2020/07/24 Python
设计师家具购买和委托在线市场:Viyet
2016/11/16 全球购物
Speedo速比涛法国官方网站:泳衣、泳镜、泳帽、泳裤
2019/07/30 全球购物
大学本科生的个人自我评价
2013/12/09 职场文书
开学典礼主持词
2014/03/19 职场文书
责任书范本
2014/08/25 职场文书
2014年教师教学工作总结
2014/11/08 职场文书
2015年宣传部个人工作总结
2015/05/14 职场文书
祝寿主持词
2015/07/02 职场文书
2016年学校爱国卫生月活动总结
2016/04/06 职场文书
Python实现提取PDF简历信息并存入Excel
2022/04/02 Python
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
2022/04/22 SQL Server