Vue的响应式原理

22 篇文章 0 订阅
9 篇文章 0 订阅

我们都知道,Vue的响应式原理是Object.defineProperty进行getset代理实现的,但他在代码中具体是怎样实现的呢?

相关链接:

整体

Vue的响应式实现主要有三块重点内容,分别为Observer,Depwatcher

我们常说的Object.defineProperty就是通过observe函数Observer类defineReactive函数的递归执行实现,defineProperty写在defineReactive函数中;而数据与视图的相互绑定(数据改变页面也跟着改变),是通过Dep类(发布者)和watcher类(观察者)实现的

整体思路
  • 本文重点: 此处为本篇文章讲的主要内容,可以结合后边代码一起看*

在开始之前,我们首先要知道,在一个组件中,data初始化是在watcher之前执行的,整体思路也可以分为两个部分,1:处理data时和2:组件初始化时

  1. 处理data时:会调用observe函数,该函数会将data的所有的属性(包括子属性)都进行Object.defineProperty进行getset代理,并为每个属性创建一个Dep(发布者),在属性.get时,让Dep收集watcherdep.depend方法)(.get的调用时机在2中),在属性.set时,调用dep.notify方法通知观察者(watcher,也是上边Dep收集的依赖)更新组件视图(.set的调用时机在3中);
  2. 组件初始化时:会调用new Watcher实例化watcher(观察者),组件的初始化时肯定会用到data中的数据,也就一定会调用上边代理过的Object.defineProperty.get收集watcherdepwatcher相互收集依赖),到这里,depwatcher的关系就已经绑定好了
  3. 😃 修改data中的值时(更新时):会执行Object.defineProperty.set,因为在1中,每个值都肯定会有一个对应的dep,而且在2中,depwatcher都已经相互绑定上了,所以这时dep中可以拿到需要更新的组件的watcher,调用dep.notify方法通知这些watcher更新组件视图;

下边的提示有助于更好的理解整体思路:

每个属性都会有一个dep,每个组件也都会有一个watcher
1中定义的getset不会立即执行(虽然大家都知道,但我觉得看的时候容易混淆)
watcher中可以存储多个depdep中可以存储多个watcher(当前流程只会存储一个watcher),二者是多对多的关系

找到代码位置

该段不重要,可以跳过本段直接看下边

observe函数位置

首先找到observe函数位置,跳过引入时的各种操作,我们先找确定到初始化data的位置,从new Vue()(位置:src/core/instance/index.js)*1开始
该方法调用this._init(options)方法(位置:src/core/instance/init.js)*2 -> 调用initState(vm) -> 调用initData(vm)(两个函数位置::src/core/instance/state.js)*3

我们要找的observe()函数就在initData里边,Object.defineProperty就是在这里实现的


/**
 * 首先说明一下属性名的意义
 * @params {Object} options 为用户new Vue({...options})时候传的options 
 * @params {vm} vm Vue
**/

// src/core/instance/index.js *1
// new Vue({...Options}) 就是new的他
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

// src/core/instance/init.js *2
Vue.prototype._init = function (options?: Object) {
    // 跳过99%的代码
    ...
    initState(vm) // 初始化data,props,watch,computed,methods
    ...
    if (vm.$options.el) { // 执行$mount()方法
      vm.$mount(vm.$options.el)
    }
}

// src/core/instance/state.js *3.1
function initState (vm: Component) {
  ...
  if (opts.data) { // opts为用户new Vue({...options})时候传的options
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
}

// *3.2
// 删掉冗余代码,只保留本流程需要的主要代码
function initData (vm: Component) {
  // 1.将var data = 对象格式的data
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 2.循环data中的属性,判断是否和props、methods冲突,然后用proxy(自定义的,非ProxyAPI)方法,将data中的属性放在vm上,以便使用时可以this.xxx取到
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // 检查methods和props中是否已经定义过了,会先检查methods,再检查props
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    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) // 通过Object.defineProperty(vm,key,{get,set})将vm._data代理到this[key]
    }
  }
  // 要说的重点函数
  observe(data, true /* asRootData */)
}

new Watcher(初始化时组件watcher)位置

new Watcher的位置在本篇中其实不重要,我们只要知道data初始化是在watcher之前执行的就可以了,下面代码中可以明显的看到new Watcher是晚于data执行的

// src/core/instance/init.js (对应找observe函数位置的*2)
Vue.prototype._init = function (options?: Object) {
    // 跳过99%的代码
    ...
    initState(vm) // 初始化data,props,watch,computed,methods
    ...
    if (vm.$options.el) { // 执行$mount()方法
      vm.$mount(vm.$options.el)
    }
}

// src/platforms/web/runtime/index.js
// 创建$mount函数的位置
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined // 获取el节点
  return mountComponent(this, el, hydrating)
}

// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 调用生命周期函数:beforeMount
  callHook(vm, 'beforeMount')

  let updateComponent
  updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }
  // 在此执行的new Watcher
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // 初次渲染
  if (vm.$vnode == null) {
    vm._isMounted = true
    // 调用生命周期函数:mounted
    callHook(vm, 'mounted')
  }
  return vm
}

源码解析

以下将分为Observedepwatcher来解释

Observe

src/core/observer/index.js

整体:

Observe系列函数的主要功能有两个

  • 一个是为属性创建Dep实例
  • 另一个是区分value是数组还是对象,然后做不同的处理(对象使用Object.defineProperty对对象属性进行劫持,数组是劫持push/pop/splice等方法)
流程:

从(2.)observe函数开始,该函数主要判断了一下该属性(首次传入时为data对象)需不需要执行new Observer,如果需要,就调用(1.)new Observer,在new Observer中,区分数组还是对象,对其进行不同的处理,数组就代理其push/pop/shift/splice等方法,对象就执行Observer.walk方法,调用defineReactive(3.),对其创建dep实例并进行Object.defineProperty代理,重点为defineReactive函数

1. class Observer {}

Observer主要是用来区分数组还是对象然后执行不同的操作,并给数据添加Dep实例

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
    // 创建dep,dep为被观察者,每个属性都有自己的dep,当dep改变时,会通知所有的watcher(观察者)更新视图
    this.dep = new Dep()
    this.vmCount = 0
    // 为value添加"__ob__"属性,值为this
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 对数组处理,由于数组没办法进行defineProperty,所以采用拦截数组的push,pop...等方法来实现
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 对数组中的属性循环调用observe
      this.observeArray(value)
    } else {
      // 如果是对象则使用walk遍历每个属性
      this.walk(value)
    }
  }

  /**
   * 处理对象的方法
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * 对数组中的属性循环调用observe
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
2. observe函数

在初始化data时,首先进入的就是observe函数,该函数主要功能是判断是否需要执行new Observer(value)

详细:

  1. 该函数首先保证进入的value为一个对象/数组
  2. 然后判断value中是否有__ob__属性(如果有就说明已经被劫持过了,在(1.)中添加的该属性) 如果有,直接返回__ob__
  3. 调用 new Observe(value)方法创建实例,最后返回该实例
// 逐行解析
function observe (value: any, asRootData: ?boolean): Observer | void {
  // 保证只有对象会进入到这个函数
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果这个数据身上有__ob__,代表已经有ob实例,直接返回那个ob实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    // 是对象(包括数组)的话就深入进去遍历属性,observe每个属性
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
3. defineReactive函数

该函数的主要目的是对传入的属性做响应式绑定和为该属性生成一个Dep实例

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 为该属性生成一个Dep实例(发布者)
  const dep = new Dep()
  // Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符(可以获取到defineProperty定义的内容)
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    // 设置为不可修改就直接return
    return
  }

  // 获取已经定义的get、set(如果已经定义了的话)
  const getter = property && property.get
  const setter = property && property.set
  
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 对该属性(val)重新执行observe方法,整体上实现递归,便利处理所有的子属性
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 可修改
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // Dep.target:此处简单解释下,Dep.target是一个“全局变量”,他会在组件运行时将watcher(观察者)存入到这里面
      // if里的代码目的是将dep与watcher关联起来
      if (Dep.target) {
        // dep.depend 将watcher和dep相互关联起来
        dep.depend()
        // 对子集进行处理
        if (childOb) {
          childOb.dep.depend()
          // 如果value是对象,那就让生成的Observer实例当中的dep也收集依赖
          if (Array.isArray(value)) { // 数组处理
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // 新老值相同不做处理
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      // 测试环境执行的暂时不看
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      // 如果已经定义过setter执行setter
      if (setter) {
        setter.call(obj, newVal)
      } else {
        // 默认就执将val置换为新的val
        val = newVal
      }
      // observe这个新set的值,对其做代理
      childOb = !shallow && observe(newVal)
      // 执行dep.notify方法,通知订阅了该dep的watcher们更新了(执行dep.notify方法,新欢调用dep.subs总的所有watcher中update方法)
      dep.notify()
    }
  })
}

Dep、Watcher

5. class Dep {}

Dep:充当发布者的角色,data中的每个对象与属性包括子属性都会有一个DepDep的功能很明确,一个是收集依赖,一个是发布更新
收集依赖:通过Dep.depend方法收集依赖,将watcher收集到subs数组中(Dep.target存的就是需要收集的watcher
发布更新:在Dep对应的属性更新时,会调用Depnotify方法,通知subs数组中的watcher进行更新

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++ // 一个自增的id
    this.subs = [] // 用来存放该Dep对应的watcher
  }

  // 向Dep中添加watcher,一般不会直接调用此方法,而是通过Dep.depend后在watcher实例中调用该方法
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  // 删除subs中的watcher
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  // 调用watcher中的addDep,让watcher收集dep,并且在watcher的函数中会调用Dep的addSub方法已达到互相收集对方
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  // 调用watcher中的更新方法
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}


// class之外的内容,创建的是全局的属性,用于存放当前的watcher
// 在watcher中会调用pushTarget方法,然后会调用 *组件需要用到的属性对应的实例Dep的depend方法* ,最后再调用popTarget方法清除Dep.target

Dep.target = null
const targetStack = [] // targetStack一般用于计算属性watcher用到的data中属性的依赖收集,暂时不需要考虑

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

4. class Watcher {}

watcher:(此处只说名组件watcher)充当观察者角色,在组件初始化的时候创建watcher的实例,在创建实例过程中通过Watcher.get函数,内部调用DeppushTarget方法,将target置位该watcher,再调用getter方法,在getter方法执行时,页面中所用到的属性就会被调用defineProperty.get的内容,defineProperty.getdep.depend()方法就会将watcher(组件)和dep(属性)关联起来,在属性改变时(defineProperty.set),执行dep.notify()方法,就会通知dep中存的所有watcher,执行update方法更新组件

// pushTarget和popTarget方法在Dep中有讲
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上存储该watcher
    vm._watchers.push(this)
    // options:配置项
    if (options) {
      this.deep = !!options.deep // 深度监听(用于watch)
      this.user = !!options.user // 是否是用户传入
      this.lazy = !!options.lazy // 表示为计算属性
      this.sync = !!options.sync // 表示是否改变了值之后立即触发回调。如果用户定义为true,则立即执行 this.run
      this.before = options.before // before:		更新前调用的函数
    } else {
      // 不传全部默认false
      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 // 该值是否为脏值(用于计算属性)
    // 用于存储dep
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()

    this.expression = process.env.NODE_ENV !== 'production' // expOrFn
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else { // 一般用于watch中执行
      this.getter = parsePath(expOrFn) // 返回一个函数,函数调用后会返回expOrFn字符串对应的属性(走defineProperty的get)
      // 没有就置为空并报错
      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
        )
      }
    }
    // lazy:用于计算属性的
    // 如果是计算属性,则不需要在这里就求值,在外界get这个计算属性时再求值
    // 如果非计算属性,调用get方法收集依赖
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this) // 将该watcher(this)push到dep.target上
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) // 组件watcher在执行getter方法时,会走组件中所有用到的属性的defineProperty.get
    } 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() // 将watcher pop出dep.target
      this.cleanupDeps()
    }
    return value
  }
  // 为当前watcher收集dep(一般在dep的depend方法中调用该函数)
  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 () {
    // if (xxx) ...
    // 组件watcher只会走这一句话
    queueWatcher(this) // 排序并通过nextTick执行要更新的watcher
  }
}

Vue源码正在学习中…如果有写的不对或不好的地方,希望大佬们帮忙多多指点~~~
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值