vue源码(九) 响应式入口 observer/index.js分析

今日目标:找到响应式文件(憋一发大的)

路径:src\core\observer\index.js

在之前提到过流程中,出现过许多次“将其添加到观察者中”、“将其响应化”、“通过defineReactive将其添加到观察者中”诸如此类的描述

这些其实就是对vue响应式的描述,提到过这么久的响应式,今天将带领大家进入这神奇的世界,看看他的入口

入口

src\core\instance\state.js在initData中,可以看到最后通过Observe将data变成响应化

observe(data, true /* asRootData */)

可以通过ctrl+左键跟踪到observe所在的文件

现在来到了src\core\observer\index.js

可以看到已经出了之前的instance文件夹,进入了observer文件夹中,此文件夹中的文件就是vue响应式实现的所有代码了。今天就引出这个文件夹,因为分开讲响应式的话会很乱很懵,因此下几期将带来observer文件夹和响应式的分析!

这里先附上src\core\observer\index.js这个文件的逐行分析,可以先看看

/* @flow */

import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'
// 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作
// 为名称的属性)组成的数组,只包括实例化的属性和方法,不包括原型上的。
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 * 在某些情况下,我们可能希望禁用组件内部的观察更新计算。
 */
export let shouldObserve: boolean = true

// 是否可以添加到观察者模式
export function toggleObserving (value: boolean) {
  shouldObserve = value
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 * 附加到每个被观察者的观察者类对象。一旦链接,观察者就会转换目标对象的属性键放入getter/setters中收集依赖项并发送更新。
 */
// Observer类 :todo
export class Observer {
  value: any;
  // Dep类
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data  将此对象作为根$数据的VM数

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // def添加__ob__属性,value必须是对象
    def(value, '__ob__', this)
    // 判断当前value是不是数组
    if (Array.isArray(value)) {
      // 如果是数组
      // 检测当前浏览器中有没有Array.prototype
      // 当能使用__proto__时
      // 这里完成了数组的响应式,不使用这7个方法都不会触发响应式
      if (hasProto) {
        // 有原型时  将arrayMethods覆盖value.__proto__,也就是把增加了副作用的7个数组方法放了进来
        protoAugment(value, arrayMethods)
      } else {
        // 复制增加了副作用的7个数组方法
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 遍历将数组所有元素进行observe
      this.observeArray(value)
    } else {
      // 不是数组是对象,执行这里
      // walk就是给对象的所有key进行响应化
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   * 遍历所有属性,将其转换为getter/setters。这个方法只应该在value的类型为对象时调用
   */
  // walk就是给对象的所有key进行响应化
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      // 遍历对象的每个key,通过defineReactive进行响应化
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  // 遍历将数组所有元素进行observe
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 * 通过拦截来扩充目标对象或数组原型链使用__proto__
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  // 这里直接用劫持的7个数组覆盖
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 * 通过定义隐藏属性。
 */
/* istanbul ignore next */
// target: value数组 src arrayMethods  keys arrayKeys
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    // 给target设置key属性 内容为src[key] 也就是arrayMethods的值
    def(target, key, src[key])
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试给一个value对象创建一个observer实例,
 * 如果观察成功,返回一个新的observer实例
 * 或者返回一个已经存在的observer 如果这个value对象早已拥有
 */
// observe作用就是为了拿到Observe实例并返回,从缓存中或者new一个
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断是否为对象 判断是否为VNode
  if (!isObject(value) || value instanceof VNode) {
    // 如果不是对象 或者 是实例化的Vnode 也就是vdom
    return
  }
  // 观察者 创建一个ob
  let ob: Observer | void
  // 检测是否有缓存ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 直接将缓存的ob拿到
    ob = value.__ob__
  } else if (
    // 如果没有缓存的ob
    shouldObserve && // 当前状态是否能添加观察者
    !isServerRendering() && // 不是ssr
    (Array.isArray(value) || isPlainObject(value)) && // 是对象或数组
    Object.isExtensible(value) && // 是否可以在它上面添加新的属性
    !value._isVue  // 是否是Vue实例
  ) {
    // new 一个Observer实例 复制给ob
    ob = new Observer(value)
  }
  // 如果作为根data 并且当前ob已有值
  if (asRootData && ob) {
    // ++
    ob.vmCount++
  }
  // 最后返回ob,也就是一个Obesrver实例 有这个实例就有__ob__,然后其对象和数组都进行了响应化
  return ob
}

/**
 * Define a reactive property on an Object.
 * 在对象上定义一个响应式属性
 */
export function defineReactive (
  obj: Object,  // 对象
  key: string,  // 对象的key
  val: any, // 监听的数据
  customSetter?: ?Function, //日志函数
  shallow?: boolean // 是否要添加__ob__属性
) {
  // 实例化一个Dep对象, 其中有空的观察者列表
  const dep = new Dep()
  
  // 获取obj的key的描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 检测key中是否有描述符 如果是不可配置 直接返回
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 满足预定义的getter/setters
  // 获取key中的get
  const getter = property && property.get
  // 获取key中的set
  const setter = property && property.set
  // 如果getter不存在或setter存在 并且参数长度为2
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 递归响应式处理 给每一层属性附加一个Obeserver实例
  // shallow不存在时代表没有__ob__属性 将val进行observe返回一个ob实例赋值给childOb
  let childOb = !shallow && observe(val)
  // 数据拦截
  // 通过Object.defineProperty对obj的key进行数据拦截
  Object.defineProperty(obj, key, {
    // 枚举描述符
    enumerable: true,
    // 描述符
    configurable: true,
    get: function reactiveGetter () {
      // 如果有用户定义的getter,就用用户定义的getter
      const value = getter ? getter.call(obj) : val
      // 判断是否有Dep.target 如果有就代表Dep添加了Watcher实例化对象
      if (Dep.target) {
        // 加入到dep去管理watcher 
        dep.depend()
        // 如果存在子对象
        if (childOb) {
          // 也加进去管理
          childOb.dep.depend()
          // 如果值是数组,要特殊处理
          if (Array.isArray(value)) {
            // 循环添加watcher
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 获取value值 触发依赖收集
      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
      // 对于没有setter的访问器属性 返回
      if (getter && !setter) return
      // 如果setter存在
      if (setter) {
        // 设置新值
        setter.call(obj, newVal)
      } else {
        // 如果没有setter ,直接给新值
        val = newVal
      }
      // 递归,对新来的值 对新值进行observe 返回ob实例
      childOb = !shallow && observe(newVal)
      // 当set时触发通知
      dep.notify()
    }
  })
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 * 给对象设置一个属性,添加新属性和添加触发更改通知(dep.notify),如果这个属性不是早已存在
 * Vue.set
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    // 判断数据 是否是undefined或者null
    // 判断数据类型是否是string,number,symbol,boolean
    (isUndef(target) || isPrimitive(target))
  ) {
    // target必须是对象或者数组,否则发出警告
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 如果是数组 并且检查key是否是有效的数组索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 设置数组长度
    target.length = Math.max(target.length, key)
    // 像数组尾部添加一个新数据,相当于push
    target.splice(key, 1, val)
    // 返回val
    return val
  }
  // 如果key在target上 并且不是通过原型链查找的 
  if (key in target && !(key in Object.prototype)) {
    // 赋值
    target[key] = val
    return val
  }
  // 声明一个对象ob 值为该target对象中的原型上面的所有方法和属性,表明该数据加入过观察者中
  const ob = (target: any).__ob__
  // 如果是vue 或者  检测vue被实例化的次数 vmCount
  if (target._isVue || (ob && ob.vmCount)) {
    // 如果不是生产环境,发出警告 
    // 避免添加响应式属性给vue实例或者根$data
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 如果ob不存在,证明没有添加观察者,不是相应,直接赋值返回
  if (!ob) {
    target[key] = val
    return val
  }
  // 通过defineReactive将ob.value加入的观察者
  defineReactive(ob.value, key, val)
  // 触发通知更新,通知订阅者obj.value更新数据
  ob.dep.notify()
  return val
}

/**
 * Delete a property and trigger change if necessary.
 * 删除属性并在必要时触发更改数据。
 * Vue.delete
 */
export function del (target: Array<any> | Object, key: any) {
  // 如果不是生产环境
  if (process.env.NODE_ENV !== 'production' &&
    // 是否是undefined null sttring boolean symbol number
    (isUndef(target) || isPrimitive(target))
  ) {
    // 发出警告,无法删除这些值
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 如果是数组 并且检查key是否是有效的数组索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 使用splice删除
    target.splice(key, 1)
    // 返回
    return
  }
  // 获取__ob__属性
  const ob = (target: any).__ob__
  // 如果是vue 或者vue的实例化次数不为0
  if (target._isVue || (ob && ob.vmCount)) {
    // 如果生产环境 发出警告 不能删除vue实例上的响应式属性
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    // 返回
    return
  }
  // 如果不是target对象本身的属性,因为delete只能删除自身对象的属性
  if (!hasOwn(target, key)) {
    // 返回
    return
  }
  // 删除对象中的属性方法
  delete target[key]
  // 如果没有__ob__属性,代表没有添加观察者
  if (!ob) {
    // 直接返回
    return
  }  
  // 通知更新 更新数据
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 * 在接触数组时收集对数组元素的依赖关系,因为我们不能像属性getter那样拦截数组元素访问。
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    // 判断是否存在__ob__实例,并且每个都调用depend添加wathcer管理
    e && e.__ob__ && e.__ob__.dep.depend()
    // 递归完数组所有内容,直到不是数组,跳出递归
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

请期待下几期

你可能感兴趣的:(vue,源码,javascript)