Skip to content

Latest commit

 

History

History
325 lines (292 loc) · 9.32 KB

File metadata and controls

325 lines (292 loc) · 9.32 KB

vue2双向绑定源码分析

先看src/core/observer/index.js:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // 把该对象作为root $data的vm个数

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)   // 添加__ob__来标示value有对应的Observer

    if (Array.isArray(value)) {
      // 处理数组
      if (hasProto) {  //是否有原型链
        protoAugment(value, arrayMethods)   //value的原型置为arrayMethods
      } else {
        copyAugment(value, arrayMethods, arrayKeys) // 把arrayMethods上的方法定义到value上
      }
      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) {
    // 给每个属性添加getter/setters
    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])
    }
  }
}

Observer会观察两种类型的数据,Object 与 Array

对于Array类型的数据,由于 JavaScript 的限制, Vue 不能检测变化,会先重写操作数组 的原型方法,重写后能达到两个目的

当数组发生变化时,触发 notify
如果是 push,unshift,splice 这些添加新元素的操作,则会使用observer观察新添加的 数据

重写完原型方法后,遍历拿到数组中的每个数据 使用observer观察它
而对于Object类型的数据,则遍历它的每个key,使用 defineProperty 设置 getter 和 setter,当触发getter的时候,observer则开始收集依赖,而触发setter的时候,observe 则触发notify。

defineReactive函数:

/**
 * 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)
  if (property && property.configurable === false) {
    return
  }

  // 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 () {  //触发get时,收集依赖
      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
    },
    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
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()  // 更新watcher
    }
  })
}

通过Object.defineProperty,添加依赖和更新watcher
在getter中,将Dep.target添加到dep内部的subs
在setter中,如果新旧值相同,直接返回,不同则调用dep.notify来更新与之相关的watcher。

observe函数:

/**
 * 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.
 */
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)  //如果是对象,继续observer
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

该方法用于观察一个对象,返回与对象相关的Observer对象,如果没有则为value创建一个对应的Observer。 defineReactive中调用该方法,其实就是为所有value为对象的值递归地观察。

我们再回到Observer,如果传入的是对象,我们就调用walk,该方法就是遍历对象,对每个值执行defineReactive。

另一种情况是传入的对象是数组,因为数组本身只引用了一个地址,所以对数组进行push、splice、sort等操作, 我们是无法监听的。所以,Vue中改写value的__proto__(如果有),或在value上重新定义这些方法。 augment在环境支持__proto__时是protoAugment,不支持时是copyAugment。

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object) {
  // 执行了value.__proto__ = arrayMethods
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  // 循环把arrayMethods上的arrayKeys方法添加到value上
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

下面看src/core/observer/array.js:
arrayMethods其实是改写了数组方法的新对象。arrayKeys是arrayMethods中的方法列表

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)  //新对象继承了数组构造方法的原型对象

//对数组需要重写的方法(会造成移位,修改数组内部变化)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]  //获取该方法的原始方法
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)  //拿到结果
    const ob = this.__ob__
    let inserted  //新增项
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)  //新增项,重新处理响应数据
    // notify change
    ob.dep.notify()  //触发视图更新
    return result
  })
})

整体上其实还是调用数组相应的方法来操作value,只不过操作之后,添加了相关watcher的更新。 这里解释一下为什么push、unshift、splice参数大于2时,要重新调用ob.observeArray, 因为这三种情况都是向数组中添加新的元素,所以需要重新观察每个子元素。

下面看src/core/observer/dep.js:

/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {  //添加watcher(具体要做的事情)添加到依赖
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  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++) {  //循环所有电话,通知watcher
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []

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

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

Dep对象,内部有一个为一个id,用于作为Dep对象的唯一标识,还有一个保存watcher的数组subs。
removeSub是从数组中移除某一watcher,depend是调用了watcher的addDep