最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 实现 vue 的数据响应式原理

    正文概述 掘金(着实有点菜)   2021-01-19   409

    实现 vue 的数据响应式原理

    实现 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)
    }
    

    实现 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 的数据响应式原理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元