详解实现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-calendar-component 封装多日期选择组件的实例代码
Dec 04 Vue.js
Vue 实现一个简单的鼠标拖拽滚动效果插件
Dec 10 Vue.js
vue 使用rules对表单字段进行校验的步骤
Dec 25 Vue.js
通过vue.extend实现消息提示弹框的方法记录
Jan 07 Vue.js
Vue中ref和$refs的介绍以及使用方法示例
Jan 11 Vue.js
详解Vue3.0 + TypeScript + Vite初体验
Feb 22 Vue.js
Vue项目中如何封装axios(统一管理http请求)
May 02 Vue.js
vue中利用mqtt服务端实现即时通讯的步骤记录
Jul 01 Vue.js
Vue Element-ui表单校验规则实现
Jul 09 Vue.js
详细聊聊vue中组件的props属性
Nov 02 Vue.js
关于Vue中的options选项
Mar 22 Vue.js
vue封装数字翻牌器
Apr 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实现图书管理案例
Jan 20 #Vue.js
详解vue之自行实现派发与广播(dispatch与broadcast)
Jan 19 #Vue.js
You might like
NOT NULL 和NULL
2007/01/15 PHP
php 移除数组重复元素的一点说明
2008/11/27 PHP
PHP-CGI进程CPU 100% 与 file_get_contents 函数的关系分析
2011/08/15 PHP
基于PHP创建Cookie数组的详解
2013/07/03 PHP
php 模拟post_验证页面的返回状态(实例讲解)
2013/10/28 PHP
php除数取整示例
2014/04/24 PHP
php实现批量压缩图片文件大小的脚本
2014/07/04 PHP
yii2中使用Active Record模式的方法
2016/01/09 PHP
PHP针对字符串开头和结尾的判断方法
2016/07/11 PHP
PHP mongodb操作类定义与用法示例【适合mongodb2.x和mongodb3.x】
2018/06/16 PHP
yii框架结合charjs统计上一年与当前年数据的方法示例
2020/04/04 PHP
JavaScript语言中的Literal Syntax特性分析
2007/03/08 Javascript
JavaScript 基础篇之对象、数组使用介绍(三)
2012/04/07 Javascript
网页右侧悬浮滚动在线qq客服代码示例
2014/04/28 Javascript
javascript实现检验的各种规则
2015/07/31 Javascript
JavaScript实现动态删除列表框值的方法
2015/08/12 Javascript
JS基于MSClass和setInterval实现ajax定时采集信息并滚动显示的方法
2016/04/18 Javascript
jQuery实现的分页功能示例
2017/01/22 Javascript
VUE实现表单元素双向绑定(总结)
2017/08/08 Javascript
Django模板继承 extend标签实例代码详解
2019/05/16 Javascript
微信小程序 scroll-view的使用案例代码详解
2020/06/11 Javascript
Vue $emit()不能触发父组件方法的原因及解决
2020/07/28 Javascript
如何搭建一个完整的Vue3.0+ts的项目步骤
2020/10/18 Javascript
[02:34]DOTA2亚洲邀请赛 BG战队出场宣传片
2015/03/09 DOTA
Python查看多台服务器进程的脚本分享
2014/06/11 Python
python中enumerate的用法实例解析
2014/08/18 Python
Python入门篇之编程习惯与特点
2014/10/17 Python
pandas全表查询定位某个值所在行列的方法
2018/04/12 Python
python 申请内存空间,用于创建多维数组的实例
2019/12/02 Python
Django windows使用Apache实现部署流程解析
2020/10/12 Python
基于OpenCV的路面质量检测的实现
2020/11/04 Python
Python使用cn2an实现中文数字与阿拉伯数字的相互转换
2021/03/02 Python
HTML5+CSS3实现机器猫
2016/10/17 HTML / CSS
俄罗斯连接商品和买家的在线平台:goods.ru
2020/11/30 全球购物
大学生党员学习焦裕禄精神思想汇报
2014/09/10 职场文书
小学秋季运动会加油口号及加油稿
2019/08/19 职场文书