阅读Vue源码(一)响应式原理

vue的响应式原理,是发布-订阅模式的应用。学习vue响应式原理前,先来了解下,到底什么是发布-订阅模式。vue

从订鞋子理解发布-订阅模式

有一天去商城买鞋子,结果没货了。店员拿出一个本对你说,能够先登记电话,到货再通知你。react

这个例子中,店员是消息的发布者,店员有个小本本,只要货到了,就会通知小本本上的客户。客户是消息的订阅者。能够在将来某个时候接收信息发布者(售货员)的消息,而不用一直轮训消息的变化。用代码实现这个例子。git

const sales = {
    //售货员的小本本
	noteBook :[],
	//在本上登记电话
	add(customer){
	     let isExist = this.noteBook.find((item) =>{
		    return item.phone == customer.phone
		 })
		 if(!isExist){
		 this.noteBook.push(customer)
		 }

	},
	//删除小本本上的记录
	delete(customer){
		  let getIndex = this.noteBook.findIndex((v) =>{
		    return v.phone == customer.phone
		  })
		  if(getIndex!==-1){
		   this.noteBook.splice(getIndex,1)
		  }
	},
     //通知顾客
	notify(){
	 this.noteBook.forEach((item) =>{
	   item.upDate();
	 })
	}
 }

 const customer1 = {
   phone:'12345678',
   upDate(){
     console.log(`customer1电话号码${this.phone}`)
   },
  }

const customer2 = {
   phone:'87654321',
   upDate(){
     console.log(`customer2电话号码${this.phone}`)
   },
  }

   //记录客户1信息
sales.add(customer1)
   //记录客户2信息
sales.add(customer2)
//到货了,通知客户
sales.notify()
复制代码

vue源码中的发布-订阅模式

1.响应式对象

1.1什么是响应式对象

Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 get 是一个给属性提供的 getter 方法,当咱们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当咱们对该属性作修改的时候会触发 setter 方法。github

一旦对象拥有了 getter 和 setter,咱们能够简单地把这个对象称为响应式对象编程

vue在何时,把哪些对象建立为响应对象呢。设计模式

1.2响应式对象的建立过程

initState 在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法(src/core/instance/state.js)数组

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

复制代码

initDatabash

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    .......
  // observe data
  observe(data, true /* asRootData */)
}
复制代码

observe闭包

observe 的功能就是用来监测数据的变化。若是没有_ob_属性,而且不是Observer的实例,就为数据添加一个Observer类。observe定义在(src/core/observer/index.js)编程语言

export function observe (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)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
复制代码

Observer类
Observer类 主要作了三件事。1.为当前值添加_ob_属性。2.当前值为数组调用observeArray方法,循环调用observe方法,给里面的每一个值变为响应对象。3.当前值是对象,调用walk方法

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
   ...
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
复制代码

walk函数里,调用defineReactive方法,给响应式对象动态添加 getter 和 setter

walk (obj: Object) {
   const keys = Object.keys(obj)
   for (let i = 0; i < keys.length; i++) {
     defineReactive(obj, keys[i])
   }
 }
复制代码

defineReactive.
对数据进行响应式化,为数据添加getter/setter,为每一个数据添加一个订阅者列表的过程,。这个列表是getter闭包中的属性,会记录依赖这个数据的组件。 响应式化后的数据至关于消息的发布者。

new Dep()建立一个数组,里面保存全部对这个数据依赖的订阅者

export function defineReactive (
....
) {
  const dep = new Dep()
 ....
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true
    //依赖收集
    get: function reactiveGetter () {
    ....
    },
    // 派发更新
    set: function reactiveSetter (newVal) {
     .....
    }
  })
}
复制代码

2.依赖收集

2.1 什么是依赖收集

每一个组件都对应一个Watcher订阅者。当每一个组件的渲染函数被执行时,都会将本组件的Watcher放到本身所依赖的响应式数据的订阅者列表里,这就至关于完成了订阅。

2.2 源码中依赖收集的过程

2.2.1 getter是何时触发的

当对数据对象的访问会触发他们的 getter 方法,那么这些对象何时被访问呢?

在Vue 的 mount 过程是经过 mountComponent 函数。(core/instance/lifecycle.js)。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
复制代码

当实例化一个渲染Watcher时,.首先进入 watcher 的构造函数逻辑,而后会执行它的 this.get() 方法(src/core/observer/watcher.js).而后调用pushTarget

pushTarget(this)
复制代码

pushTarget(src/core/observer/dep.js )把当的渲染watcher赋值给 Dep.target

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}
复制代码

在warcher构造函数里,继续执行this.getter,对应就是 updateComponent 函数,这实际上就是在执行:

vm._update(vm._render(), hydrating)
复制代码

vm._render(),执行这个方法会对vm上的数据访问,这时就触发了数据对象的getter.

src/core/observer/index.js

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
    },
复制代码

触发getter,调用dep.depend,也就是调用 Dep.target.addDep,这时Dep.target已经被赋值为当前的渲染watcher。

src/core/observer/watcher.js

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)) {
        dep.addSub(this)
      }
    }
  }
复制代码

执行dep.addSub,那么就会执行 this.subs.push(sub),也就是说把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 作准备。

3.派发更新

当响应式数据发生变化的时候,也就是触发了setter时,setter会负责通知该数据的订阅者列表里的Watcher,Watcher会触发组件从新渲染来更新视图

参考:
JavaScript 设计模式精讲

Vue.js技术揭秘

相关文章
相关标签/搜索