详解实现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 相关文章推荐
快速解决vue2+vue-cli3项目ie兼容的问题
Nov 17 Vue.js
一篇超完整的Vue新手入门指导教程
Nov 18 Vue.js
Vue-router中hash模式与history模式的区别详解
Dec 15 Vue.js
Vue实现一种简单的无限循环滚动动画的示例
Jan 10 Vue.js
Vue实现图书管理案例
Jan 20 Vue.js
Vue基本指令实例图文讲解
Feb 25 Vue.js
vue中三级导航的菜单权限控制
Mar 31 Vue.js
vue项目两种方式实现竖向表格的思路分析
Apr 28 Vue.js
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
May 25 Vue.js
vue修饰符.capture和.self的区别
Apr 22 Vue.js
Vue操作Storage本地化存储
Apr 29 Vue.js
el-table-column 内容不自动换行的解决方法
Aug 14 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读取mssql的ntext字段返回值为空的解决方法
2014/12/30 PHP
php类常量用法实例分析
2015/07/09 PHP
thinkPHP5.0框架环境变量配置方法
2017/03/17 PHP
PHP魔术方法之__call与__callStatic使用方法
2017/07/23 PHP
Thinkphp 框架基础之入口文件功能、定义与用法分析
2020/04/27 PHP
jValidate 基于jQuery的表单验证插件
2009/12/12 Javascript
javascript数组的使用
2013/03/28 Javascript
如何使用json在前后台进行数据传输实例介绍
2013/04/11 Javascript
jquery获取html元素的绝对位置和相对位置的方法
2014/06/20 Javascript
jquery checkbox 勾选的bug问题解决方案与分析
2014/11/13 Javascript
jQuery带进度条全屏图片轮播特效代码分享
2020/06/28 Javascript
JQuery.Ajax()的data参数类型实例详解
2015/11/20 Javascript
辨析JavaScript中的Undefined类型与null类型
2016/05/26 Javascript
jquery ajax后台返回list,前台用jquery遍历list的实现
2016/10/30 Javascript
ionic2 tabs使用 Modal底部tab弹出框
2016/12/30 Javascript
JQuery Ajax 异步操作之动态添加节点功能
2017/05/24 jQuery
使用JavaScript生成罗马字符的实例代码
2018/06/08 Javascript
使用JavaScript实现node.js中的path.join方法
2018/08/12 Javascript
移动端吸顶fixbar的解决方案详解
2019/07/17 Javascript
JS中自定义事件的使用与触发操作实例分析
2019/11/01 Javascript
ZK中使用JS读取客户端txt文件内容问题
2019/11/07 Javascript
elementui更改el-dialog关闭按钮的图标d的示例代码
2020/08/04 Javascript
python实现将汉字转换成汉语拼音的库
2015/05/05 Python
python文件特定行插入和替换实例详解
2017/07/12 Python
给Python学习者的文件读写指南(含基础与进阶)
2020/01/29 Python
python使用nibabel和sitk读取保存nii.gz文件实例
2020/07/01 Python
python+playwright微软自动化工具的使用
2021/02/02 Python
最好的商品表达自己:Cafepress
2019/09/04 全球购物
工商管理专业实习生自我鉴定
2013/09/29 职场文书
普通员工辞职信
2014/01/17 职场文书
小学评语大全
2014/04/22 职场文书
乡镇党的群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
销售人才自我评价范文
2014/09/27 职场文书
MongoDB支持的索引类型
2022/04/11 MongoDB
python中mongodb包操作数据库
2022/04/19 Python
Python tensorflow卷积神经Inception V3网络结构
2022/05/06 Python