Vue2.0响应式原理理解

164 阅读8分钟

理解Vue2.0响应式原理


前言:

Vue的响应式都是老生常淡的东西了,也有很多很好的文章可以去了解,在面试中也会经常问到,也能说出个一二来😆,在Vue框架中核心实现就是响应式,有必要去理解下它,走着~

Vue响应式系统简单的分为三步:

  • 通过Object.defineProperty的getter进行依赖收集,实现一个订阅者(Dep)来进行监听
  • 通过Object.defineProperty的setter进行通知
  • 通过Watcher中的update进行视图更新

Object.defineProperty

vue就是基于Object.defineProperty它实现的

使用方法:

/*
    obj: 目标对象
    prop: 需要操作的目标对象的属性名
    descriptor: 描述符

    return value 传入对象
*/

Object.defineProperty(obj, prop, descriptor)

descriptor的主要有几个属性

  • enumerable 属性是否可枚举,默认 false
  • configurable 属性是否可以被修改或者删除,默认 false。
  • writable 是否可以采用 数据运算符 进行赋值
  • get 获取属性的方法
  • set 设置属性的方法

有时configurable和writable的定义容易混淆

  • configurable为false时,不可以用delete等属性操作符进行更改

  • writable为false时,不可以用obj.a = "new" 等数据运算符进行更改

实现observer方法使对象变为可观察的

知道了Object.defineProperty基本属性后,咱们通过observer来观察对象,observer会在Vue在init阶段会进行初始化,对数据进行响应式绑定。

在源码中定义了defineReactive1这个方法,咱们就用defineReactive来申明一个函数,需要用到obj(需要绑定的对象),key(obj的某一个属性),val(具体值)。

附上简易实现代码
function defineReactive (obj, key, val{
    Object.defineProperty(obj, key, {
        enumerabletrue,       
        configurabletrue,     
        getfunction reactiveGetter ({
            return val;         
        },
        setfunction reactiveSetter (newVal{
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

//用来更新视图的方法
function cb (val{
    /* 渲染视图 */
    console.log("视图更新啦~");
}

我们上面提到了observe,这个当然需要实现,需要在上面再封装一层observer,这个observer函数需要传入一个value,这个value就是要观察的对象,借鉴源码简单实现:

function observer (value{
    if (!value || (typeof value !== 'object')) {
        return;
    }

      //通过遍历所有属性的方式对该对象的每一个属性都通过defineReactive处理
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}

接着封装一个Vue类:

class Vue {
    /* Vue构造类 */
      //options 里包含了data
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}

// new一个Vue实例
let v = new Vue({
    data: {
        test"boom sha ka la ka"
    }
});
v._data.test = "biu biu biu";  /* 视图更新啦~ */

完整代码:

//用来更新视图的方法
function cb (val{
    /* 渲染视图 */
    console.log("视图更新啦~");
}

function defineReactive (obj, key, val{
    Object.defineProperty(obj, key, {
        enumerabletrue,       
        configurabletrue,     
        getfunction reactiveGetter ({
            return val;         
        },
        setfunction reactiveSetter (newVal{
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

function observer (value{
    if (!value || (typeof value !== 'object')) {
        return;
    }

      //通过遍历所有属性的方式对该对象的每一个属性都通过defineReactive处理
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}

class Vue {
    /* Vue构造类 */
      //options 里包含了data
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}

let v = new Vue({
    data: {
        test"boom sha ka la ka"
    }
});
v._data.test = "biu biu biu";  /* 视图更新啦~ */

执行上面代码,最终输出 “视图更新啦~”,这几段代码可以体现出响应式原理了

依赖收集:

在Vue源码中,有Dep(订阅者)和Watcher(观察者)两大类,Dep主要作用是用来存放Watcher对象的

liu
liu

Dep

简单实现Dep其中部分代码

class Dep {
    constructor () {
        // 用来存放Watcher对象的数组
        this.subs = [];
    }

    //在subs中添加一个Watcher对象
    addSub (sub) {
        this.subs.push(sub);
    }

    //通知所有Watcher对象更新视图
    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}
Dep主要实现了两部分
  1. 添加Watcher观察者对象
  2. 使用notify对subs中所有Watcher对象进行触发更新操作

Watcher

简单代码实现

class Watcher {
    constructor () {
          //new 一个Watcher后 把实例赋值给Dep.target
        Dep.target = this;
    }

    update () {
        console.log("视图更新");
    }
}

Dep.target = null;

把Dep和Watcher都实现了,接下来需要把最开始那部分代码修改下来完成依赖收集

先贴完整代码:
//依赖收集
class Dep {
    constructor () {
        this.subs = [];
    }

    addSub (sub) {
        this.subs.push(sub);
    }

    notify () {
        this.subs.forEach((sub) => {
            sub.update();
        })
    }
}

class Watcher {
    constructor () {
        Dep.target = this;
    }

    update () {
        console.log("视图更新啦~");
    }
}

//监听属性
function observer (value{
    if (!value || (typeof value !== 'object')) {
        return;
    }

    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}

//拦截属性 动态添加get set方法
function defineReactive (obj, key, val{
      //创建一个Dep对象,用来收集Watcher对象
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerabletrue,
        configurabletrue,
        getfunction reactiveGetter ({
            dep.addSub(Dep.target);
            return val;         
        },
        setfunction reactiveSetter (newVal{
            if (newVal === val) return;
            val = newVal;
            dep.notify();
        }
    });
}

class Vue {
  constructor(options) {
    this._data = options.data

    observer(this._data)
    new Watcher()
    console.log('render~'this._data.test);
  }
}

let v = new Vue({
  data: {
    test'2111'
  }
})
v._data.test = '55'

//
//render~ 2111
//视图更新啦~

Dep.target = null
修改的部分:
  1. 在defineReactive方法中,添加创建Dep对象的代码
  2. 在get中进行依赖收集,把Dep.target(Watcher对象)存到Dep的subs中
  3. 在set中去调用dep的notify方法来触发所有Watcher对象的update方法更新对应视图
  4. 在Vue类的构造函数中创建一个Watcher对象,这时候Dep.target会指向这个Watcher对象
执行过程:

总结:

  1. 首先Vue初始化的时候会在observer函数中遍历value,触发Object.defineProperty 给对象进行get set绑定
  2. 在get中让当前的watcher对象存放在Dep的subs中,在数据变化时,set会调用Dep对象的notify方法对subs中的watcher对象进行视图更新

完咯~ 记录下自己所理解的Vue2.0响应式原理,比较简单,细节实现还得仔细看源码,持续学习中~

我飞翔在乌云之中,你看着我无动于衷,有多少次波涛汹涌,在我 心中~😜