详解实现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如何循环提取对象数组中的值
Nov 18 Vue.js
vue自定义插件封装,实现简易的elementUi的Message和MessageBox的示例
Nov 20 Vue.js
Vue3配置axios跨域实现过程解析
Nov 25 Vue.js
vue实现按钮切换图片
Jan 20 Vue.js
如何使用RoughViz可视化Vue.js中的草绘图表
Jan 30 Vue.js
Vue单页面应用中实现Markdown渲染
Feb 14 Vue.js
vue实现可移动的悬浮按钮
Mar 04 Vue.js
Vue接口封装的完整步骤记录
May 14 Vue.js
详细聊聊vue中组件的props属性
Nov 02 Vue.js
vue使用wavesurfer.js解决音频可视化播放问题
Apr 04 Vue.js
vue中的可拖拽宽度div的实现示例
Apr 08 Vue.js
vue的项目如何打包上线
Apr 13 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 面试碰到过的问题 在此做下记录
2011/06/09 PHP
解析如何通过PHP函数获取当前运行的环境 来进行判断执行逻辑(小技巧)
2013/06/25 PHP
php中instanceof 与 is_a()区别分析
2015/03/03 PHP
PHP中的密码加密的解决方案总结
2016/10/26 PHP
JavaScript基本概念初级讲解论坛贴的学习记录
2009/02/22 Javascript
基于JQuery的简单实现折叠菜单代码
2010/09/15 Javascript
简单几行JS Code实现IE邮件转发新浪微博
2013/07/03 Javascript
将数字转换成大写的人民币表达式的js函数
2014/09/21 Javascript
在JavaScript中操作数组之map()方法的使用
2015/06/09 Javascript
window.onload使用指南
2015/09/13 Javascript
深入理解bootstrap框架之入门准备
2016/10/09 Javascript
关于jQuery.ajax()的jsonp碰上post详解
2017/07/02 jQuery
使用JavaScript根据图片获取条形码的方法
2017/07/04 Javascript
微信小程序下拉刷新界面的实现
2017/09/28 Javascript
详解Vue组件实现tips的总结
2017/11/01 Javascript
vue 使用vue-i18n做全局中英文切换的方法
2018/10/29 Javascript
JS代码检查工具ESLint介绍与使用方法
2020/02/04 Javascript
[46:12]完美世界DOTA2联赛循环赛 DM vs Matador BO2第一场 11.04
2020/11/04 DOTA
[57:22]完美世界DOTA2联赛PWL S2 FTD vs PXG 第二场 11.27
2020/12/01 DOTA
Python实现查找匹配项作处理后再替换回去的方法
2017/06/10 Python
详解Python 装饰器执行顺序迷思
2018/08/08 Python
深入了解和应用Python 装饰器 @decorator
2019/04/02 Python
解决python 读取excel时 日期变成数字并加.0的问题
2019/10/08 Python
解决更改AUTH_USER_MODEL后出现的问题
2020/05/14 Python
django 将自带的数据库sqlite3改成mysql实例
2020/07/09 Python
FLIR美国官网:热成像, 夜视和红外摄像系统
2018/07/13 全球购物
Roxy荷兰官方网站:冲浪、滑雪板、服装和配件
2019/10/22 全球购物
外贸英语毕业生自荐信
2013/11/14 职场文书
会计专业个人自我鉴定
2014/03/21 职场文书
合作经营协议书范本
2014/04/17 职场文书
小学教师个人总结
2015/02/05 职场文书
房贷工资证明范本
2015/06/12 职场文书
如何撰写创业策划书
2019/06/27 职场文书
uniapp开发小程序的经验总结
2021/04/08 Javascript
iPhone13将有八大升级
2021/04/15 数码科技
CSS精灵图的原理与使用方法介绍
2022/03/17 HTML / CSS