Vue2核心原理-依赖收集原理和响应式原理

Vue框架

Vue与传统DOM框架的对比

传统DOM框架

如果有两处修改:

  1. 遍历DOM树获取DOM对象
  2. 确认需要操作的视图属性
  3. 更改对象的属性触发视图更新
  4. 视图执行一次更新
  • ......(重复上面的工作)
  1. 更改对象的属性触发视图更新
  2. 视图执行一次更新

所有视图内容更新完毕

Vue框架

如果有两处修改:

  1. 初始化框架构建虚拟DOM树
  2. 将响应式数据对象映射到虚拟DOM树中
  3. 更改需要更新的属性
  4. diff出变更项,得出更新进入更新队列
  5. 更改需要更新的属性
  6. diff出变更项,得出更新进入更新队列(4和6或可合并成一次diff)
  7. 将更新队列中的任务一次进入虚拟DOM中
  8. 将虚拟DOM中需要变更的部分一次设置到DOM对象中

对比总结出: 当页面有N步数据更新的时候就算N步数据是同时更改的,视图部分也会进行N次渲染。 而Vue框架是采用非直接DOM操作的方式进行页面渲染,这样可以将视图更新时的多次更改放到一个任务队列中,最终将本次更改的N次变化统一由一次渲染替换真实的DOM对象,这样可以实现N次变更,一次渲染,在性能上更优于传统的DOM框架。

Vue好处

  1. 响应式视图更新。
  2. 多次更改一次渲染
  3. 无需直接操作DOM。

Vue2

依赖收集原理

  1. 在初始化过程中(beforeCreate 和 created 之间) 拦截器(Object.defineProperty) 劫持了数据。
    // 劫持的过程中定义了观察者 dep
    var dep = function Dep () {
      this.id = uid++;
      this.subs = [];
    };
    复制代码
  2. 然后在元素挂载过程中(beforeMount 和 mounted 之间)触发了拦截器(Object.defineProperty) get ,get 方法里含有 dep.depend();
    • 观察者 dep 关联被观察者 watcher 的动作。
    Dep.prototype.depend = function depend () {
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    };
    复制代码
    • watcher 的结构如下
    var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
      this.vm = vm; // 保存被观察的实例
      ... 
      this.cb = cb;
      this.id = ++uid$2; // uid for batching
      ... 
      this.expression = expOrFn.toString();// 触发实例的render方法
    };
    复制代码
  3. 完成了依赖收集。最终就是我们熟知的触发流程,更改state时,拦截器的 set 触发了 dep.notify() 通知了所有被观察者 Wacher,而一番排队操作后需而触发 watcher 里的表达式,就去重新渲染这个组件。
    • 观察者和被观察者就是这样的结构:一个依赖下包含多个watcher,
    Dep {
      id: n,
      subs: [
        0: Watcher {
          ...
          // render 函数就是就是输出虚拟节点的
          expression: "function () { vm._update(vm._render(), hydrating); }"
          ...
        }
      ]
    }
    复制代码

响应式原理

function updateView() {
    console.log('视图更新')
}

function defineReactive(target, key, value) {
    Object.defineProperty(target, key, {
    	get() {
        	return value
    	},
        set(newValue) {
            if (newValue !== value) {
                value = newValue
                updateView()
            }
        }
    })
}

function observe(target) {
    if (target === null || typeof target !== 'object') {
        return target
     }
    for(let key in target) {
        defineReactive(target, key, target[key])
    }
}

const data = {
    name: '张三',  
    age: 21
}

observer(data)

data.name = 'lisi'
复制代码

oberve函数判断源数据是否是对象,然后遍历,将源数据、键值、属性值传入defineReactive函数中。

defineReactive函数中通过Object.defineProperty对对象的每一项进行监听,如果修改了对象就触发了updateView函数。

Object.defineProperty实现的响应式

Object.defineProperty:方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty存在的缺点:

  1. Object.defineProperty不能深度监听data变化
    • 在vue初始化时会递归data对象中每一个值,给予绑定get、set方法
  2. Object.defineProperty不能监听新增、删除属性
    data.sex = 'man' 
    delete data.name
    复制代码
    • vue2通过Vue.set和Vue.delete来进行新增和删除属性
    • 也可通过使用$forceUpdate强制渲染
  3. Object.defineProperty不能监听数组变化
        const data = {
            nums: [1, 2, 3]
        }
    
        data.nums.push(4)
    复制代码
    • vue2通过重写数组的方法属性实现监听数组变化

      const arrProto = Object.create(Array.property)
      // 数组的方法名称
      let arrFn = [
          'push',
          'pop',
          'shift',
          'unshift',
          ...
      ]
      arrFn.forEach(method => {
      	arrProto[method] = function() {
              // 视图更新
              updateView()
              Array.propery[method].call(this, ..arguments)
          }    
      })
      复制代码
    • 当data中的某一项是数组时,在observe函数中遍历进入defineReactive中,然后进行深度监听,如果是数组的话,就会将此数组的原型指向为arrProto,此时当前数组调用的数组方法,就会执行updateView()和数组原型的方法。

      function observe(target) {
          if (target === null || typeof target !== 'object') {
              return target
           }
          if (Array.isArray(target)) }{
              target.__proto__ = arrProto
          }
          for(let key in target) {
              defineReactive(target, key, target[key])
          }
      }
      
      复制代码
      • demo: 当执行数组方法时,data.nums.push(4):
        ① 进入observe函数
        
        …
        
        ② for…in…循环遍历数组每一个值
        
        ③ 进入defineReactive
        
        ④ 深度监听,进入observe
        
        …
        
        ⑤ 判断是否是数组
        
        ⑥ 将数组的原型指向arrProto
        
        ⑦ 执行updateView
        
        ⑧ 执行Array.prototype的push方法
        复制代码

存在的问题

当我们使用vm对象更新对象中中初始化就存在的属性时 视图会自动触发更新,而当我们对对象内部不存在的 属性设置值的时候视图不更新,这是因为Vue2.x底层的数 据响应式系统使用的是Object.defineProperty()来实现的。 Vue在初始化的时候会递归的将data选项中的属性绑定 setter和getter,通过两者来观察属性的行为,一旦初始 化完成便不会再次执行本操作,所以更改已知属性的时候 Vue实例是有感知并执行默认处理行为的,当我们操作对 象内部不存在的属性时由于其没有setter和getter,Vue将 无法观察到这些属性的赋值和取值,视图更新操作也无法进行。

Vue3

Vue2都出了,Vue3还会远吗

作者正在编写中...

猜你喜欢

转载自juejin.im/post/7033033285685116965