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

这篇文章主要是给不了解或者没接触过 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 数据响应式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    这篇文章主要是给不了解或者没接触过 vue 响应式源码的小伙伴们看的,其主要目的在于能对 vue 的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」 响应式的理解 响应式顾名思义就是数据变化,会引起视图的更新.这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析. 在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据.vue 内部通过

  • vue.js数据响应式原理解析

    目录 Object.defineProperty() 定义 defineReactive 函数 递归侦测对象的全部属性 流程分析 observe 函数 Observer 类 完善 defineReactive 函数 One More Thing Object.defineProperty() 得力于 Object.defineProperty() 的特性,vue 的数据变化有别于 react 和小程序,是非侵入式的.详细介绍可以看 MDN 文档,这里特别说明几点: get / set 属性是函数

  • 深入理解Vue的数据响应式

    1. ES语法的getter和setter 在开始了解 Vue 的数据响应式原理前应该先搞清楚 ES语法 中的 getter 和 setter 方法的具体用法. getter和setter 方法是以 get 和 set 关键字来为对象添加虚拟属性的一种方式.这种属性其实并不真实存在,而是以取值函数 getter 和存值函数 setter 来模拟的一种属性.目的是对某个属性设置存值函数和取值函数,拦截该属性的存取行为,以便于对该属性的存取做一些限定处理.如下所示(以下代码来源于 mdn) gett

  • 稍微学一下Vue的数据响应式(Vue2及Vue3区别)

    什么是数据响应式 从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom. 换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改. 因此实现数据响应式有两个重点问题: 如何知道数据发生了变化? 如何知道数据变化后哪里需要修改? 对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue

  • vue数据响应式原理知识点总结

    vue2.0数据响应式原理 对象 Obect.defineproperty 定义对象的属性mjm defineproperty 其实不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,设置一系列操作权限,只不过属性里的get和set实现了响应式 var ob = { a: 1, b: 2 } //1-对象 2-属性 3-对于属性的一系列配置 Object.defineProperty(ob, 'a' , { //a对象则是ob的绝对私有属性,,默认都是true writable: fal

  • Vue深入讲解数据响应式原理

    目录 响应式是什么 如何实现数据响应式 实现对象属性拦截 通用的劫持方案 总结 响应式是什么 简而言之就是数据变页面变 如何实现数据响应式 在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是: 对象属性拦截 (vue2.x) Object.defineProperty 对象整体代理 (vue3.x) Proxy 其中对象属性拦截,道理都是相通的 实现对象属性拦截 字面量对象定义 let data = { name:'小兰同学' }

  • 一文彻底搞懂Vue的MVVM响应式原理

    目录 前言 Vue的MVVM原理 创建一个html示例 在MVue.js中创建MVue入口 创建Compile 1.处理元素节点compileElement(child) 2.处理文本节点compileText(child) 3.实现compileUtil指令处理 更新器Updater更新数据 实现数据观察者Observer 数据依赖器Dep 观察者Watcher 实现视图驱动数据驱动视图 总结 前言 这些天都在面试,每当我被面试官们问到Vue响应式原理时,回答得都很肤浅.如果您在回答时也只是停

  • 浅谈Vue 数据响应式原理

    前言 Vue的数据响应主要是依赖了Object.defineProperty(),那么整个过程是怎么样的呢?以我们自己的想法来走Vue的道路,其实也就是以Vue的原理为终点,我们来逆推一下实现过程. 本文代码皆为低配版本,很多地方都不严谨,比如 if(typeof obj === 'object')这是在判断obj是否为为一个对象,虽然obj也有可能是数组等其他类型的数据,但是本文为了简便,就直接这样写来表示判断对象,对于数组使用Array.isArray(). 改造数据 我们先来尝试写一个函数

  • 详解vue数据响应式原理之数组

    目录 src/core/observer/index.js src/core/observer/array.js arrayMethods 总结 src/core/observer/index.js src/core/observer/array.js arrayMethods 当data的数组对象中本来没有某个属性,然后点击按钮动态增加某个属性的时候,此时此属性是没有get和set的,也就是没有响应式机制,如果想要让你动态增加的某个属性有响应式变化,那么就直接在数据的源头给他初始化这个属性,具

  • Vue3.0数据响应式原理详解

    基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理 预备知识 ES6 Proxy,整个响应式系统的基础. 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档. 先把Vue3.0跑起来 先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可. 下面一个简单计数器的DEMO: <!DOCTY

随机推荐