最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue源码,你真的看懂了吗(二)

    正文概述 掘金(蒲月阿七)   2021-02-26   807

    响应式原理

    阅读之前需了解的知识

    • MVVM模式
    • 观察者模式和发布者-订阅者模式
    • Object.defineProperty与ES6中的Proxy
    • 数据劫持
    • 原型对象和原型链

    变化侦测

    变化侦测就是侦测数据的变化。从Vue2.0开始,引入了虚拟DOM,将更新粒度调整为中等程度,也就是一个状态所绑定的依赖不再是具体的dom节点,而是一个组件。当状态变化之后,会通知到组件,组件内部再使用虚拟dom进行比对。这样可以大大降低依赖的数量,从而降低依赖追踪所消耗的内存

    Vue源码,你真的看懂了吗(二) 图:每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

    下面将具体介绍vue中是如何侦测数据的变化的,从而实现数据驱动视图变化。

    核心实现类

    • Observer

      利用 Object.defineProperty 给对象的属性添加getter和setter,用于依赖收集和派发更新。使数据的变化可以被观察到!

    • Dep

      收集当前响应式对象的依赖关系,每个响应式对象包括其子对象都拥有一个Dep实例(Dep.subs数组是watcher实例数组),当数据发生变更的时候,通过dep.notify()通知数组里面的所有watcher让其触发更新。

    • Watcher

      观察者对象,就是依赖。实例分为render(渲染) watcher、computed(计算) watcher、user(侦测器) watcher。

      依赖中记录了所有数据属性以及一些对响应式数据的操作的包装,可以响应数据的变化。

    依赖收集

    • initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集。
    • initState 时,对侦听属性初始化时,触发 user watcher 依赖收集。
    • render()的过程,触发 render watcher 依赖收集。
    • re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcher 的订阅,重新对dep.subs赋值,进行新一轮依赖的收集。

    派发更新

    • 组件中响应的数据发生变化,触发 setter 的逻辑。
    • 调用 dep.notify()通知 subs数组中所有的Watcher 实例进行更新操作。
    • 每一个 watcher 调用 update 方法触发视图更新或用户的某个回调函数。

    实现原理

    Vue源码,你真的看懂了吗(二)

    • Data通过Observer转换成了getter/setter的形式(响应式数据)来追踪变化。

    当外界通过watcher读取数据的时候,会触发getter从而将watcher添加到依赖中。

    数据发生变化后,会触发setter,从而向Dep中的依赖(wathcer)发送通知。

    wathcer接收到通知之后,会向外界发送通知,之后可能会触发视图更新,也可能会触发用户的某个回调函数等。

    核心代码

    /*
     * 数组变化侦测 array.js
     */
    
    import { def } from '../util/index'
    
    const arrayProto = Array.prototype
    export const arrayMethods = Object.create(arrayProto)
    
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
      // cache original method
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })
    
    
    /**
     * Observe a list of Array items.
     * 数组中新操作的对象进行响应式处理
     */
    Observer.prototype.observeArray = function observeArray(items) {
      for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
      }
    };
    

    总结

    vue.js采用数据劫持结合发布-订阅模式,通过Object.defineproperty来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

    注意事项——当前实现存在的不足

    由于js的限制,vue不能检测数组和对象的变化。

    • 对象

    Vue 无法检测 property 的添加或移除。

    由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

    解决方法:

    使用 Vue.set(object, propertyName, value) 方法 / vm.$set向嵌套对象添加响应式 property。

    Vue.set(vm.someObject, 'b', 2)
    
    this.$set(this.someObject,'b',2)
    
    • 数组

    Vue 不能检测以下数组的变动:

    1、当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

    2、当你修改数组的长度时,例如:vm.items.length = newLength

    解决方法:

    用Vue.set / vm.$set / Array.prototype.splice 实现和 vm.items[indexOfItem] = newValue 相同的效果。

    Vue.set(vm.items, indexOfItem, newValue)
    
    vm.$set(vm.items, indexOfItem, newValue)
    
    vm.items.splice(indexOfItem, 1, newValue)
    

    用 Array.prototype.splice 实现和 vm.items.length = newLength 相同的效果。

    vm.items.splice(newLength)
    

    思考

    Watcher 和 Dep 的关系

    watcher中(响应式数据被读取时第一次触发getter时当前watcher也会记录自己被收集进当前的dep)实例化了dep,并向dep.subs中添加了订阅者,dep通过notify遍历了dep.subs通知每个watcher进行更新。

    vue中是如何检测数组变化的?

    函数劫持。

    Vue通过原型拦截的方式重写了数据的7个方法,首先获取数组的Observer对象,如果有加入新的值,就调用observeArray对新的值进行响应式处理,然后手动调用notify,派发更新,渲染页面。

    为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

    Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

    Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

    Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

    Vue为什么不把所有的数据都放到data?

    data用来存放绑定的数据。data中的数据都会增加getter、setter,会收集对应的watcher。

    如果data里的数据是属于纯展示的数据,根本不需要对这个数据进行监听,特别是一些复杂的列表/对象,放进data中会浪费性能

    可以选择放进computed,因为如果computed是直接返回一个没有引用其他实例属性的值,即没有任何访问响应式数据(如data/props上的属性/其他依赖过的computed等)的操作,根据Vue的依赖收集机制,只有在computed中引用了实例属性,触发了属性的getter,getter会把依赖收集起来,等到setter调用后,更新相关的依赖项。

    计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算,所以使用computed会更加节约内存。

    Vue 为什么不允许动态添加根级响应式 property?

    一方面消除了依赖项追踪系统中的一类边界情况,也使Vue实例能更好地配合类型检查系统工作。因为在data对象上才能让Vue将它转换为响应式的数据。

    另一方面是在data中提前声明所有的响应式property,会使组件状态的结构更加清晰,便于维护。


    下载网 » Vue源码,你真的看懂了吗(二)

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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