Vue源码学习 -- 响应式原理之观察者模式

文章目录

        • 1. 观察者模式
        • 2. Vue 的响应式原理
        • 3. 简要分析源码中的观察者模式部分
        • 4. 简易实现

1. 观察者模式

  • 概念

观察者模式又被称为 发布-订阅 模式,这种模式定义了对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并完成自动更新。

  • 优点
  1. 观察者与被观察者是抽象耦合的
  2. 建立了一套触发机制
  • 缺点
  1. 被观察者有很多的观察者时,通知更新这一过程会花费很多的时间
  2. 观察者和被观察者之间存在循环依赖的话,可能导致系统奔溃
  • 现实中的例子
    Vue源码学习 -- 响应式原理之观察者模式_第1张图片

从上图可以看到,一个 subject 可以被多个 observer 订阅/观察。当 subject 状态发生变化时,就会通知订阅者进行更新操作。在这样的一个模式中,订阅者的数量以及具体订阅者都是不确定的(订阅者列表是动态变化的),但仍能保证整个机制的正常运转。

2. Vue 的响应式原理

Vue.js 实现响应式的核心是利用了 ES5 中的 Object.defineProperty 方法(这也是为什么 Vue.js 不能兼容 IE8 及以下版本浏览器的原因)。

Vue 会为每个组件实例都创建一个 watcher ,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖(依赖收集)。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染(派发更新)。

代码实现上: 创建 Vue 实例时,会传入一个 function / object 作为 data 属性。Vue 会遍历 data 属性所对应的对象的属性,并借助 Object.defineProperty 方法,进行数据劫持,将每个数据属性都转化为响应式对象(拥有settergetter)。

  1. 在 getter 方法中,进行依赖收集
  2. 在 setter 方法中,进行派发更新

3. 简要分析源码中的观察者模式部分

说明: 下文分析的是 vue.js 2 的部分源码。

  1. 数据劫持,将数据属性转化为响应式对象
    核心为 defineReactive 方法
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

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

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 依赖收集
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 派发更新
      dep.notify()
    }
  })
}
  1. 依赖收集
    依赖收集从 getter 中的 dep.depend() 方法说起,下面是 Dep 类中的 depend 方法的实现:
depend () {
    if (Dep.target) {
     // 订阅
      Dep.target.addDep(this)
    }
  }

其中调用了 Watcher 实例中的 addDep 方法:

addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
      	// 添加当前 watcher 到依赖列表 subs 中
        dep.addSub(this)
      }
    }
  }
  1. 派发更新
    派发更新从 setter 中的 dep.notify() 说起,下面是 Dep 类中的 notify 方法的实现:
notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    // 遍历依赖列表,通知订阅者更新
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

其中调用了 watcher 实例中的 update 方法:

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      // 将更新 watcher 放入更新队列中,在下一个 tick 调用
      queueWatcher(this)
    }
  }

4. 简易实现

源码中考虑的情况比较多,看起来比较复杂。为了便于理解观察者模式使用的核心思想,可以暂时忽略分支逻辑,只查看主要的逻辑。下面的代码,就是忽略各种分支情况后,最最简单的一个实现:

// 观察者模式
class Watcher {
	constructor(vm, expr, callback) {
		this.vm = vm;
		this.expr = expr;
		this.callback = callback;
		this.oldValue = this.get();
	}
	get() {
		Dep.target = this;
		let value = CompileUtil.getValue(this.vm, this.expr);
		Dep.target = null;
		return value;
	}
	update() {
		let newValue = CompileUtil.getValue(this.vm, this.expr)
		if (this.oldValue !== newValue) {
			this.callback(newValue)
		}
	}
}
// 发布/订阅
class Dep {
	constructor() {
		// 存储所有的观察者
		this.subs = [];
	}
	// 订阅
	addSub(watcher) {
		this.subs.push(watcher);
	}
	// 发布
	notify() {
		this.subs.forEach((watcher) => {
			watcher.update()
		})
	}
}

// 实现数据劫持
// 观察类 将传入的数据的定义都改为 defineProperty 的方式
class Observer {
	constructor(data) {
		this.observe(data);
	}
	observe(data) {
		if (data && typeof data === 'object') {
			for (let key in data) {
				this.defineReactive(data, key, data[key]);
				this.observe(data[key]);
			}
		}
	}
	defineReactive(obj, key, value) {
		this.observe(value);
		let dep = new Dep();
		Object.defineProperty(obj, key, {
			get() {
				// 添加观察者
				Dep.target && dep.addSub(Dep.target);
				return value;
			},
			set: (newValue) => {
				if (newValue !== value) {
					// 将新值转为 get set 形式后再进行赋值
					this.observe(newValue);
					value = newValue;
					// 通知观察者
					dep.notify();
				}
			}
		})
	}
}

你可能感兴趣的:(vue,设计模式,javascript,javascript,vue.js,设计模式)