大致了解Vue2响应式原理(一)

187 阅读4分钟

一、大致阐述一下Vue2响应式的原理

1.初始化Vue实例的时候,对data对象进行劫持
  • 通过defineProperty对data对象进行数据劫持
  • 遍历data对象中的数据,对数据进行劫持
2.在劫持数据过程中,对getter和setter进行改写
  • getter:为该元素添加依赖收集,将当前的Watcher对象加到依赖列表中
  • setter:进行派发更新,通知依赖该属性的Watcher进行更新操作
3.在编译模板的过程中,对数据的访问进行改写,加入依赖收集的逻辑
  • 使用模板编译器将模板编译成render渲染函数
  • 在渲染函数中对数据的访问进行改写,添加依赖收集的逻辑
4.当数据发生变化时,触发依赖该属性的Watcher对象进行更新操作
  • 当数据发生变化时,会调用数据的setter方法
  • setter方法会通知依赖该属性的Watcher进行更新

5.在更新过程中,对更新队列进行操作优化,避免重复更新和不必要的计算

  • 将所有需要更新的Watcher对象添加到更新队列中
  • 对更新队列进行排序,以确保组件的正确更新顺序
  • 对更新队列进行去重

6.在更新过程中,对组件的生命周期进行管理,确保更新时期的正确性和性能优化

在组件更新前,调用beforeUpdate钩子函数 在组件更新后,调用updated钩子函数 在组件销毁后,调用beforeDestroy钩子函数,进行清理工作

二、大致阐述下如何在初始化Vue实例的时候,对data对象进行劫持

2.1 初始化Vue实例

当我们创建了一个Vue组件实例时,Vue会在组件实例初始化之前,created中调用initData函数,对函数进行初始化。
initData函数主要实现:

//获取options中的data数据赋值给data
let data = vm.$options.data
//判断data类型,如果是函数,则通过getData调用该函数获得纯对象,如果不是纯对象,则赋值{}
//纯对象:没有通过构造函数创建或者没有继承其他对象的属性或方法的对象,通常是通过对象字面量创建的。
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
if (!isPlainObject(data)) { 
  data = {} 
  process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) 
}
//代理实例上的data
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) { 
  const key = keys[i]
  if (process.env.NODE_ENV !== 'production') {
      //判断它是否已经在props或methods中定义过
      if (methods && hasOwn(methods, key)) {......} 
  }
  //判断它是否已经在props中定义过
  if (props && hasOwn(props, key)) { ...... }
  else if (!isReserved(key)) { 
      proxy(vm, `_data`, key)
  }
}
// observe data
observe(data, true /* asRootData */)

2.2 通过observe实现响应式

初始化完成后,执行observe函数,这是实现响应式的关键。observe的作用是将一个普通对象转换成响应式对象,即对该对象的属性进行劫持,实现数据的双向绑定。
observe函数具体如下:

function oberseve(value:any,asRootData:?boolean) Observer|void {
    //判断是不是一个对象
    if(!isObject(value) || value instanceof VNode){
        return
    }
    let ob:Observer | void;
    //判断数据是否已经是响应式的
    if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){
        ob = value.__ob__
    }else if(
       shouldObserve && 
       !isServerRendering() &&
       (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) &&
       !value._isVue
     ){
        ob = new Observer(value)
    }
    // 如果是根数据对象,即 Vue 实例的数据对象,则将其标记为根响应式对象
    if(asRootData && ob){
        ob.vmCount++;
    }
    return ob
}

在observe函数中,首先判断value是否是对象或者不是虚拟节点(虚拟节点不需要进行劫持),再判断当前value中是否存在__ob__属性来判断是否需要new一个Observer实例,如果是根数据对象,即 Vue 实例的数据对象,则将其标记为根响应式对象。

2.2.1 Observer类

接下来讲讲Observer类,在Observer类中,分别实现了对数组和对象的响应式

export class Observer{
    value:any;
    dep:Dep;
    vmCount:number;
    
    constructor(value:any){
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        def(value,'__ob__',this);
        if(Array.isArray(value)){
            //将该数组的原型链指向arrayMethods
            protoAugment(value,arrayMethods);
            this.obesrveAarray(value);
        }else{
            this.walk(value);
        }
    }
    
    walk(obj:Object){
        const keys = Object.keys(obj);
        for(let i = 0;i < keys.length; i++){
            //为对象中的元素实现响应式
            defineReactive(obj,keys[i])
        }
    }
    
    //为数组中的元素实现响应式
    observeArray(item:Array<any>){
        for(let i = 0; l = item.length; i < l; i++){
            observe(item[i])
        }
    }
}

Observer类主要完成以下几个任务:

  1. 将传进来的value标记为响应式对象,并通过def为当前value添加一个值为Observer实例的属性__ob__。
  2. 判断value是否是数组,如果是数组,则需要另外处理,通过protoAugment函数将数组的原型指向修改为传入的arrayMethods。因为数组不能通过defineProperty来进行劫持(因为setter是需要通过=赋值的时候才会触发,当我们使用数组方法来改变数组的时候,数组长度是在内部进行改变的,不会涉及到setter,所以不能通过defineProperty来操作数组),所以需要对改变数组的7个方法进行重写,以此来保证数组在变化时,能够触发回调函数。
  3. 调用observeArray函数,遍历数组的每个元素,将其转换成响应式数据。
  4. 调用walk函数,对对象中的所有元素通过defineReactive函数(重写getter和setter)转换成响应式。

2.2.2 Dep类

我们注意到,在Observer类中,有一个dep属性,类型为Dep类,接下来简单分析一下Dep类具体作用,在后续Watcher类时,会详细说明。以下是一个简单Dep类的实现。

class Dep{
    constructor(){
        //依赖列表,用于存放Watcher
        this.subs = [];
    }
    //添加Watcher实例
    addSub(sub) this.subs.push(sub);
    //删除Watcher实例
    removeSub(sub){
        const index = this.subs.indexOf(sub);
        if(index != -1){
            this.subs.splice(index,1)
        }
    }
    //添加当前的Watcher实例
    depend(){
        if(Dep.target){
            Dep.target.addDep(this)
        }
    }
    
    notify(){
        const subs = this.subs.slice();
        for(let i = 0,i<subs.length;i++){
            subs[i].update()
        }
    }
    
    Dep.target = null;
    const targetStack = [];
    
    function pushTarget(target){
        targetStack.push(target);
        Dep.target = target;
    }
    
    funciton popTarget(){
        targetStack.pop();
        Dep.target = targetStack[targetStack.length - 1]
    }
}

以上就是最近学习vue2响应式原理的一些总结,仅供参考,欢迎指正!