前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue 响应式原理

vue 响应式原理

作者头像
大当家
发布2020-04-01 17:48:00
5770
发布2020-04-01 17:48:00
举报
文章被收录于专栏:web

响应式原理

简单点讲 vue 的响应式是通过 Object.defineProperty 和 观察者模式来实现的。 vue 初始化的时候 watcher 构造函数通过 Object.defineProperty 方法对 data 属性进行递归遍历,设置 get、set,初始化编译的时候会触发 getter 函数,进行依赖收集,将观察者 watcher 添加到目标对象 dep 中。改变数据的时候会触发 set, 执行 notify 方法,调用 dep 中 watcher 对象的 update 方法,update 方法将 watcher 添加到 watcher 队列中, 通过调用 nextTick 异步执行,触发更新。

至于实现的细节,现在细细道来...

我们需要分析的大致是以下源码文件:

  • observer
    • dep.js
    • index.js
    • scheduler.js
    • watcher.js

先看 index.js

代码语言:javascript
复制
export class Observer { // 定义 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)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { // 对属性进行遍历
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

...
...
...

/**
 * Define a reactive property on an Object.
 */
export function defineReactive ( // 把对象的属性变成响应式 
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key) // 获取obj对象key属性的数据属性
  if (property && property.configurable === false) { // 如果该属性是不能修改或删除的,则直接返回
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get //尝试拿到该对象原生的get属性,保存到getter中
  const setter = property && property.set //尝试拿到该对象原生的set属性,保存到setter中
  if ((!getter || setter) && arguments.length === 2) { //如果getter不存在或者setter存在,且参数只有两个
    val = obj[key] //则直接通过obj[ke]获取值,并保存到val中
  }

  let childOb = !shallow && observe(val) //递归调用observe:当某个对象的属性还是对象时会进入
  Object.defineProperty(obj, key, { //调用Object.defineProperty设置obj对象的访问器属性
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend() //调用depend()收集依赖, 
        if (childOb) { // 如果childOb存在
          childOb.dep.depend()  //则调用childOb.dep.depend()收集依赖
          if (Array.isArray(value)) { // 如果 val 是数组则调用小下面的函数处理
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val //如果之前有定义getter,则调用getter获取值,否则就赋值为val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== 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) // newVal调用observe处理,newVal为数组或对象其属性也是响应式
      dep.notify() // 通知订阅的 watcher 做更新
    }
  })
}

接下来看 dep.js

代码语言:javascript
复制
 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 中存的 wather,循环执行 watcher 中的 update 方法
      subs[i].update()
    }
  }

接着看 watcher.js

代码语言:javascript
复制
/**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this) // 执行 watcher 队列 queueWatcher 方法
    }
  }

scheduler.js:

代码语言:javascript
复制
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) { // 相同的 watcher 只添加进队列一次
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)  // 调用异步 nestTick 处理 watcher 队列
    }
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 响应式原理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档