Vue 之 Data 监听与更新

优质
小牛编辑
126浏览
2023-12-01

Object.defineProperty()

简单用法:

/* Object.defineProperty(obj, prop, descriptor) */

var o = {
    a: 0
}

Object.defineProperty(o, 'b', {
    get: function () {
        console.log('get')
        return 'get'
    },
    set: function (value) {
        console.log(value)
    },
    configurable: true,
    enumerable: true
})

例中

  • Object.defineProperty(o, 'b', {})表示给o添加一个叫b的属性
  • get定义在读取o.b时执行的方法,get方法的执行结果为o.b的值
  • set定义在给o.b设置值时执行的方法
  • configurable表示o.b是否可变及可被删除
  • enumerable表示ob属性是否可以通过for-in遍历获取到

简单说,我们可以通过Object.defineProperty给一个对象添加一个属性,并且这个属性的值在被查询和被改变时都可以触发我们事先定义好的事件。

观察者模式

Vue实现观察者模式主要通过三个类:Observer类、Watcher类、Dep类

  • Observer:通过defineProperty给数据添加getset方法并在get中收集依赖,在set中通知更新
  • Watcher:观察数据变化(接收Depnotify发出的通知),执行回调
  • Dep:一个可观察对象,收集订阅(在Observerget时收集)、发送通知(在Observerset时发布)

举例:

var app = new Vue({
    el: '#J_app',
    data: {
        message: 'hello world'
    },
    mounted: function(){
        console.log(this.message)
    }
})

app.message = 'hellp vue'

console.log(`'app.message:' ${app.message}`)

例中:我们给data添加了message属性,然后,在数据初始化过程中:Observer通过definePropertymessage设置get/set属性,并在get中调用Depdepend方法,将message添加为可观察对象,之后在Watcher中,对message进行求值,调用其get方法,同时将Watcher实例添加到可观察对象message的观察者数组里;同时,在set时,通过Depnotify发布message更新的事件,通知调用观察者数组中的update方法。

在Vue中,上面流程中Observer负责的部分是通过defineReactive$$1实现的,可以简写如下:

/*
*参数含义:
*obj: 需要添加属性的对象
*key: 要添加的属性名
*value: 要添加的属性名对应的值
*customSetter: 在非生产环境,一些自定义的错误警告
*shallow: 属性值是否添加到代理到vm上
*/
function defineReactive(obj, key, value, customSetter, shallow) {

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
        return
    }

    var getter = property && property.get;
    var setter = property && property.set;

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            var value = getter ? getter.call(obj) : val;

            //收集依赖  交由Dep类负责

            return value
        },
        set: function () {
            var value = getter ? getter.call(obj) : val;

            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }

            //开发环境属性检查并警告
            if ("development" !== 'production' && customSetter) {
                customSetter();
            }

            if (setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }

            observe(newVal);

            //发布更新消息
            dep.notify();
        }
    })

}

这里的代码相对好理解,get方法里的收集依赖其实也是一个曲折的过程。在watcher中,每添加一个新的watcher实例时,都会对相应的对象进行求值,也就是会主动触发一次defineReactiveget方法,然后就在get方法里进行依赖收集。同时,数据有变更时,也会触发defineReactiveset方法,然后在set方法里发布数据更新的消息dep.notify(),然后dep.notify里会对调相应的watcher进行update

Dep类负责依赖收集和观察者存储,来看下Dep类的代码:

/**
 * 每个Dep的实例都是一个可观察对象,可以被多个观察者订阅
*/

var uid = 0;

var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
var targetStack = [];

function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}

function popTarget () {
  Dep.target = targetStack.pop();
}

初始每个可观察对象dep都有一个id和观察者数组(subs),可以对当前id的可观察对象:添加观察者(addSub)、移除观察者(removeSub)、设置为可观察对象(depend)、通知变更到观察者们(notify),添加和移除观察者相对简单,往数组pushsplice即可;设置为可观察对象调用的是Dep.target.addDep(this)Dep.target其实是Watcher的实例,下节再看;通知变更到观察者们只是执行了是观察者们的update方法,update方法也会在下节Wather的实现一起看下。