vue底层-响应式原理

2 篇文章 0 订阅
2 篇文章 0 订阅

先简单了解下object.defineProperty方法

// Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
// get 方法 使用该属性的时候会调用get方法
// set 方法 修改或者赋值该属性时调用该方法
Object.defineProperty(obj, key, {
  enumerable: true,  //可枚举的
  configurable: true, //可删除,可配置
  get: ()=>{
    // 使用调用      
  },
  set:newVal=> {
    // 赋值调用   
  }
})

ok,接下来进入正题

现上个图

Vue.js在这里主要做了三件事:

  •  通过 Observer 对 data 做监听,并且提供了订阅某个数据项变化的能力。
  •  把 template 编译成一段 document fragment,然后解析其中的 Directive,得到每一个 Directive 所依赖的数据项和update方法。
  •  通过Watcher把上述两部分结合起来,即把Directive中的数据依赖通过Watcher订阅在对应数据的 Observer 的 Dep 上。当数据变化时,就会触发 Observer 的 Dep 上的 notify 方法通知对应的 Watcher 的 update,进而触发 Directive 的 update 方法来更新 DOM 视图,最后达到模型和视图关联起来。

理解:

  • Observer: 数据的观察者,让数据对象的读写操作都处于自己的监管之下
  • Watcher: 数据的订阅者,数据的变化会通知到Watcher,然后由Watcher进行相应的操作,例如更新视图
  • Dep: Observer与Watcher的纽带,当数据变化时,会被Observer观察到,然后由Dep通知到Watcher

示意图如下

// src/core/instance/index.js
function Vue (options) {
  this._init(options)
}

// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    // expose real self
    vm._self = vm
    // 初始化生命周期函数
    initLifecycle(vm)
    // 初始化事件
    initEvents(vm)
    // 初始化render函数
    initRender(vm)
    // 调用beforeCreate方法
    callHook(vm, 'beforeCreate')
    // 初始化inject
    initInjections(vm) // resolve injections before data/props
    // 初始化state(包括data,props,methods,computed,watch)
    initState(vm)
    // 初始化provide
    initProvide(vm) // resolve provide after data/props
    // 调用created
    callHook(vm, 'created')


    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

// 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) {
    // 这里做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)
  }
}

// initData
function initData (vm: Component) {
  // 获取data
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 遍历data的每一个key
  while (i--) {
    const key = keys[i]
    // 防止props和data里数据重复
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 从这里开始执行obesrve方法,对data进行监听
  observe(data, true /* asRootData */)
}

从上面我们可以知道当我们执行new Vue()时  
1. 首先会执行_init()方法,这个方法内vue会初始化他的生命周期、事件以及他自身的state,这个state包括props、methods、data、computed、watch。
2. 因为我们要说响应式,因此,重点分析下data,而对data的初始化在initData()方法中。
3. 分析initData,这里主要是对data中和props中key值重复性以及数据代理的的处理,我们暂不研究,关于响应式我们从observe(data, true /* asRootData */)这个方法开始

// src/core/observer/index.js
// observe方法
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 不是对象或是VNode则退出
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 这里__ob__属性指的是已被observe监听
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    // 这里的判断是为了确保value是单纯的对象
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 如果是对象且没有__ob__属性,则new Observer()
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

这里 new Observer(value) 就是实现响应式的核心方法之一了,通过它将data转变可以成观察的,而这里正是我们开头说的,用了 Object.defineProperty 实现了data的 getter/setter 操作,通过 Watcher 来观察数据的变化,进而更新到视图中。

接下来我们就来看Observer

Observer

先来observer的构造函数

// src/core/observer/index.js
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  // 如果value是对象,执行walk方法,对每一个key进行defineReactive
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  // 如果是数组,则执行observeArray,对每一项进行observe
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

分析构造函数,主要做了以下的事:

1. 给要监听的value,添加__ob__属性,表示数据已经被observer的标志
2. 如果value是数组,则通过对value中每一个元素调用observe分别进行观察。
3. 如果value是对象,则使用walk遍历value上每个key,对每个key调用defineReactive来获得该key的set/get控制权。

示意图如下:

通过以上的分析,我们知道,真正实现数据的响应式的核心方法在defineReactive上

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建一个dep
  // get时用来进行依赖收集
  // set时用来通知watcher更新dom
  const dep = new Dep()
  
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  // 如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。
  // 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 () {
      // 执行原有的getter方法
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 进行依赖收集
        dep.depend()
        if (childOb) {
          // 如果value是对象,则对其子集进行依赖收集。
          /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
          childOb.dep.depend()
          // 如果value是数组,则对数组逐一依赖收集
          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
      // 如果之前存在setter,需要先执行
      if (setter) {
        setter.call(obj, newVal)
      } else {
        // 修改value的值
        val = newVal
      }
      // 对新生成的数据,进行监听
      childOb = !shallow && observe(newVal)
      // 通知watcher更新
      dep.notify()
    }
  })
}

分析下其中的getter方法:

1. Dep.target。Dep.target的作用是用来判断是否应该进行依赖收集,什么时候为他赋值呢?我们之后研究。
2. dep对象创建的作用是为了进行依赖收集

那么为什么要判断是否需要依赖收集呢?举个简单的例子

new Vue({
    template: 
        `<div>
            <span>name:</span> {{name}}
            <span>age:</span> {{age}}
        <div>`,
    data: {
        name: 'lily',
        age: '18',
        sex: 'male'
    }
});

上例中在模版中我们只是用了data中的两个数据,sex这个属性我们没有使用,因此再这种情况下我们没必要对sex进行监听,这样在一定程度上可以提高性能。


 这里需要注意getter方法的执行时间是render()的时候,当$el.mount()执行的时候,这时候会实例话一个watcher对象,这个watcher对象就是Dep.target,表示这个数据需要被observe。 


cb('beforeMount')
vm._watcher = new Watcher(vm, function () {
      vm._update(vm._render(), hydrating);
    }, noop);
cb('mounted')  

接着分析setter方法

1. 当数据修改完毕,时我们需要重新对这个数据进行监听。
2. 调用dep.notify()方法通知watcher数据更新。

简单来说,dep相当于一个书店,而watcher就相当于书店的订阅者,observer的数据data相当于书店的书

ok,接下来我们了解下具体是怎么进行依赖收集的

Dep

被Observer的data在触发 getter 时,Dep 就会收集依赖的 Watcher ,其实 Dep 就像刚才说的是一个书店,可以接受多个订阅者的订阅,当书有变动时即在data变动时,就会通过 Dep 给 Watcher 发通知进行更新。


// src/core/observer/dep.js
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }
  // 添加一个观察者对象
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // 移除观察者对象
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  // 依赖收集,当存在Dep.target的时候添加观察者对象
  // 这里的Dep.target其实是Watcher实例
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 通知所有观察者,执行update
  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()
    }
  }
}

1. 执行getter时,dep执行dep.depend()方法,进而会执行watcher的addDep方法将wtacher加入到dep的subs列表中
2. 执行setter时,dep执行notify方法,然后遍历subs中的所有watcher执行update方法。

 

Watcher

Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Dep的subs中,数据变动的时候Dep会通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // new Watcher时执行get方法
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 这一步对应用到的数据标记Dep.target
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  // 添加依赖,将当前watcher添加到dep的subs中去
  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)
      }
    }
  }

  

  // 当依赖发生改变的时候进行回调。
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
}

1. 执行getter时,dep执行dep.depend()方法,进而会执行watcher的addDep方法将wtacher加入到dep的subs列表中
2. 执行setter时,dep执行notify方法,然后遍历subs中的所有watcher执行update方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值