vue2响应式原理

69 阅读8分钟

一、组件实例数据初始化过程

  • methods、computed 和 watch 有什么区别?

ustbhuangyi.github.io/vue-analysi…

caibaojian.com/vue-design/…

vue-js.com/learn-vue/l…

winteroo.github.io/ylblog/docs…

1、initState

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) {

    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)

  }

}

主要作用是对props、methods、data、computed、watch进行初始化

props、methods、data初始化过程

computed与watch的区别

watch的懒执行

2、initData

二、observer观察者类扮演的角色

1、observe

///src/core/observer/index.js

/**

 * 响应式处理的真正入口

 * 为对象创建观察者实例,如果对象已经被观察过,则返回已有的观察者实例,否则创建新的观察者实例

 * @param {*} value 对象 => {}

 */

export function observe (value: any, asRootData: ?boolean): Observer | void {

  // 非对象和 VNode 实例不做响应式处理

  if (!isObject(value) || value instanceof VNode) {

    return

  }

  let ob: Observer | void

  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {

  // 如果 value 对象上存在 __ob__ 属性,则表示已经做过观察了,直接返回 __ob__ 属性

    ob = value.__ob__

  } else if (

    shouldObserve &&

    !isServerRendering() &&

    (Array.isArray(value) || isPlainObject(value)) &&

    Object.isExtensible(value) &&

    !value._isVue

  ) {

  // 创建观察者实例

    ob = new Observer(value)

  }

  if (asRootData && ob) {

    ob.vmCount++

  }

  return ob

}

2、Observer

/**

 * 观察者类,会被附加到每个被观察的对象上,value.__ob__ = this

 * 而对象的各个属性则会被转换成 getter/setter,并收集依赖和通知更新

 */

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

    this.dep = new Dep()

    this.vmCount = 0

    // 在 value 对象上设置 __ob__ 属性

    def(value, '__ob__', this)

    if (Array.isArray(value)) {

      /**

       * value 为数组

       * hasProto = '__proto__' in {}

       * 用于判断对象是否存在 __proto__ 属性,通过 obj.__proto__ 可以访问对象的原型链

       * 但由于 __proto__ 不是标准属性,所以有些浏览器不支持,比如 IE6-10,Opera10.1

       * 为什么要判断,是因为一会儿要通过 __proto__ 操作数据的原型链

       * 覆盖数组默认的七个原型方法,以实现数组响应式

       */

      if (hasProto) {

        // 有 __proto__

        protoAugment(value, arrayMethods)

      } else {

        copyAugment(value, arrayMethods, arrayKeys)

      }

      this.observeArray(value)

    } else {

      // value 为对象,为对象的每个属性(包括嵌套对象)设置响应式

      this.walk(value)

    }

  }



  /**

   * 遍历对象上的每个 key,为每个 key 设置响应式

   * 仅当值为对象时才会走这里

   */

  walk (obj: Object) {

    const keys = Object.keys(obj)

    for (let i = 0; i < keys.length; i++) {

      defineReactive(obj, keys[i])

    }

  }



  /**

   * 遍历数组,为数组的每一项设置观察,处理数组元素为对象的情况

   */

  observeArray (items: Array<any>) {

    for (let i = 0, l = items.length; i < l; i++) {

      observe(items[i])

    }

  }

}

3、defineReactive

/**

 * 拦截 obj[key] 的读取和设置操作:

 *   1、在第一次读取时收集依赖,比如执行 render 函数生成虚拟 DOM 时会有读取操作

 *   2、在更新时设置新值并通知依赖更新

 */

export function defineReactive (

  obj: Object,

  key: string,

  val: any,

  customSetter?: ?Function,

  shallow?: boolean

) {

  // 实例化 dep,一个 key 一个 dep

  const dep = new Dep()



  // 获取 obj[key] 的属性描述符,发现它是不可配置对象的话直接 return

  const property = Object.getOwnPropertyDescriptor(obj, key)

  if (property && property.configurable === false) {

    return

  }



  // 记录 getter 和 setter,获取 val 值

  const getter = property && property.get

  const setter = property && property.set

  if ((!getter || setter) && arguments.length === 2) {

    val = obj[key]

  }



  // 递归调用,处理 val 即 obj[key] 的值为对象的情况,保证对象中的所有 key 都被观察

  let childOb = !shallow && observe(val)

  // 响应式核心

  Object.defineProperty(obj, key, {

    enumerable: true,

    configurable: true,

    // get 拦截对 obj[key] 的读取操作

    get: function reactiveGetter () {

      const value = getter ? getter.call(obj) : val

      /**

       * Dep.target 为 Dep 类的一个静态属性,值为 watcher,在实例化 Watcher 时会被设置

       * 实例化 Watcher 时会执行 new Watcher 时传递的回调函数(computed 除外,因为它懒执行)

       * 而回调函数中如果有 vm.key 的读取行为,则会触发这里的 读取 拦截,进行依赖收集

       * 回调函数执行完以后又会将 Dep.target 设置为 null,避免这里重复收集依赖

       */

      if (Dep.target) {

        // 依赖收集,在 dep 中添加 watcher,也在 watcher 中添加 dep

        dep.depend()

        // childOb 表示对象中嵌套对象的观察者对象,如果存在也对其进行依赖收集

        if (childOb) {

          // 这就是 this.key.chidlKey 被更新时能触发响应式更新的原因

          childOb.dep.depend()

          // 如果是 obj[key] 是 数组,则触发数组响应式

          if (Array.isArray(value)) {

            // 为数组项为对象的项添加依赖

            dependArray(value)

          }

        }

      }

      return value

    },

    // set 拦截对 obj[key] 的设置操作

    set: function reactiveSetter (newVal) {

      // 旧的 obj[key]

      const value = getter ? getter.call(obj) : val

      // 如果新老值一样,则直接 return,不跟新更不触发响应式更新过程

      /* 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()

      }

      // setter 不存在说明该属性是一个只读属性,直接 return

      // #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()

    }

  })

}

4、dependArray

/**

 * 遍历每个数组元素,递归处理数组项为对象的情况,为其添加依赖

 * 因为前面的递归阶段无法为数组中的对象元素添加依赖

 */

function dependArray (value: Array<any>) {

  for (let e, i = 0, l = value.length; i < l; i++) {

    e = value[i]

    e && e.__ob__ && e.__ob__.dep.depend()

    if (Array.isArray(e)) {

      dependArray(e)

    }

  }

}

5、数组响应式

src/core/observer/array.js

/**

 * 遍历每个数组元素,递归处理数组项为对象的情况,为其添加依赖

 * 因为前面的递归阶段无法为数组中的对象元素添加依赖

 */

function dependArray (value: Array<any>) {

  for (let e, i = 0, l = value.length; i < l; i++) {

    e = value[i]

    e && e.__ob__ && e.__ob__.dep.depend()

    if (Array.isArray(e)) {

      dependArray(e)

    }

  }

}

6、def

/src/core/util/lang.js

/**

 * Define a property.

 */

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {

  Object.defineProperty(obj, key, {

    value: val,

    enumerable: !!enumerable,

    writable: true,

    configurable: true

  })

}

7、rpotoAugment

/src/core/observer/index.js

/**

 * 设置 target.__proto__ 的原型对象为 src

 * 比如 数组对象,arr.__proto__ = arrayMethods

 */

function protoAugment (target, src: Object) {

  /* eslint-disable no-proto */

  target.__proto__ = src

  /* eslint-enable no-proto */

}

8、copyAugment

/src/core/observer/index.js

/**

 * 在目标对象上定义指定属性

 * 比如数组:为数组对象定义那七个方法

 */

function copyAugment (target: Object, src: Object, keys: Array<string>) {

  for (let i = 0, l = keys.length; i < l; i++) {

    const key = keys[i]

    def(target, key, src[key])

  }

}

1、会被添加到被观察的对象上,而被观察对象的getter/setter会被劫持,用于依赖收集和通知更新。2、内部会递归处理object、遍历Array

三、Dep与Watcher有什么关系

1、Dep

/src/core/observer/dep.js

import type Watcher from './watcher'

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

import config from '../config'



let uid = 0



/**

 * 一个 dep 对应一个 obj.key

 * 在读取响应式数据时,负责收集依赖,每个 dep(或者说 obj.key)依赖的 watcher 有哪些

 * 在响应式数据更新时,负责通知 dep 中那些 watcher 去执行 update 方法

 */

export default class Dep {

  static target: ?Watcher;

  id: number;

  subs: Array<Watcher>;



  constructor () {

    this.id = uid++

    this.subs = []

  }



  // 在 dep 中添加 watcher

  addSub (sub: Watcher) {

    this.subs.push(sub)

  }



  removeSub (sub: Watcher) {

    remove(this.subs, sub)

  }



  // 像 watcher 中添加 dep

  depend () {

    if (Dep.target) {

      Dep.target.addDep(this)

    }

  }



  /**

   * 通知 dep 中的所有 watcher,执行 watcher.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)

    }

    // 遍历 dep 中存储的 watcher,执行 watcher.update()

    for (let i = 0, l = subs.length; i < l; i++) {

      subs[i].update()

    }

  }

}



/**

 * 当前正在执行的 watcher,同一时间只会有一个 watcher 在执行

 * Dep.target = 当前正在执行的 watcher

 * 通过调用 pushTarget 方法完成赋值,调用 popTarget 方法完成重置(null)

 */

Dep.target = null

const targetStack = []



// 在需要进行依赖收集的时候调用,设置 Dep.target = watcher

export function pushTarget (target: ?Watcher) {

  targetStack.push(target)

  Dep.target = target

}



// 依赖收集结束调用,设置 Dep.target = null

export function popTarget () {

  targetStack.pop()

  Dep.target = targetStack[targetStack.length - 1]

}

2、Watcher

/src/core/observer/watcher.js

/**

 * 一个组件一个 watcher(渲染 watcher)或者一个表达式一个 watcher(用户watcher)

 * 当数据更新时 watcher 会被触发,访问 this.computedProperty 时也会触发 watcher

 */

export default class Watcher {

  vm: Component;

  expression: string;

  cb: Function;

  id: number;

  deep: boolean;

  user: boolean;

  lazy: boolean;

  sync: boolean;

  dirty: boolean;

  active: boolean;

  deps: Array<Dep>;

  newDeps: Array<Dep>;

  depIds: SimpleSet;

  newDepIds: SimpleSet;

  before: ?Function;

  getter: Function;

  value: any;



  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 = function() { return this.xx }

      // 在 this.get 中执行 this.getter 时会触发依赖收集

      // 待后续 this.xx 更新时就会触发响应式

      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

        )

      }

    }

    this.value = this.lazy

      ? undefined

      : this.get()

  }



  /**

   * 执行 this.getter,并重新收集依赖

   * this.getter 是实例化 watcher 时传递的第二个参数,一个函数或者字符串,比如:updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数

   * 为什么要重新收集依赖?

   *   因为触发更新说明有响应式数据被更新了,但是被更新的数据虽然已经经过 observe 观察了,但是却没有进行依赖收集,

   *   所以,在更新页面时,会重新执行一次 render 函数,执行期间会触发读取操作,这时候进行依赖收集

   */

  get () {

    // 打开 Dep.target,Dep.target = this

    pushTarget(this)

    // value 为回调函数执行的结果

    let value

    const vm = this.vm

    try {

      // 执行回调函数,比如 updateComponent,进入 patch 阶段

      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)

      }

      // 关闭 Dep.target,Dep.target = null

      popTarget()

      this.cleanupDeps()

    }

    return value

  }



  /**

   * Add a dependency to this directive.

   * 两件事:

   *   1、添加 dep 给自己(watcher)

   *   2、添加自己(watcher)到 dep

   */

  addDep (dep: Dep) {

    // 判重,如果 dep 已经存在则不重复添加

    const id = dep.id

    if (!this.newDepIds.has(id)) {

      // 缓存 dep.id,用于判重

      this.newDepIds.add(id)

      // 添加 dep

      this.newDeps.push(dep)

      // 避免在 dep 中重复添加 watcher,this.depIds 的设置在 cleanupDeps 方法中

      if (!this.depIds.has(id)) {

        // 添加 watcher 自己到 dep

        dep.addSub(this)

      }

    }

  }



  /**

   * Clean up for dependency collection.

   */

  cleanupDeps () {

    let i = this.deps.length

    while (i--) {

      const dep = this.deps[i]

      if (!this.newDepIds.has(dep.id)) {

        dep.removeSub(this)

      }

    }

    let tmp = this.depIds

    this.depIds = this.newDepIds

    this.newDepIds = tmp

    this.newDepIds.clear()

    tmp = this.deps

    this.deps = this.newDeps

    this.newDeps = tmp

    this.newDeps.length = 0

  }



  /**

   * 根据 watcher 配置项,决定接下来怎么走,一般是 queueWatcher

   */

  update () {

    /* istanbul ignore else */

    if (this.lazy) {

      // 懒执行时走这里,比如 computed



      // 将 dirty 置为 true,可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果

      this.dirty = true

    } else if (this.sync) {

      // 同步执行,在使用 vm.$watch 或者 watch 选项时可以传一个 sync 选项,

      // 当为 true 时在数据更新时该 watcher 就不走异步更新队列,直接执行 this.run 

      // 方法进行更新

      // 这个属性在官方文档中没有出现

      this.run()

    } else {

      // 更新时一般都这里,将 watcher 放入 watcher 队列

      queueWatcher(this)

    }

  }



  /**

   * 由 刷新队列函数 flushSchedulerQueue 调用,完成如下几件事:

   *   1、执行实例化 watcher 传递的第二个参数,updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)

   *   2、更新旧值为新值

   *   3、执行实例化 watcher 时传递的第三个参数,比如用户 watcher 的回调函数

   */

  run () {

    if (this.active) {

      // 调用 this.get 方法

      const value = this.get()

      if (

        value !== this.value ||

        // Deep watchers and watchers on Object/Arrays should fire even

        // when the value is the same, because the value may

        // have mutated.

        isObject(value) ||

        this.deep

      ) {

        // 更新旧值为新值

        const oldValue = this.value

        this.value = value



        if (this.user) {

          // 如果是用户 watcher,则执行用户传递的第三个参数 —— 回调函数,参数为 val 和 oldVal

          try {

            this.cb.call(this.vm, value, oldValue)

          } catch (e) {

            handleError(e, this.vm, `callback for watcher "${this.expression}"`)

          }

        } else {

          // 渲染 watcher,this.cb = noop,一个空函数

          this.cb.call(this.vm, value, oldValue)

        }

      }

    }

  }



  /**

   * 懒执行的 watcher 会调用该方法

   *   比如:computed,在获取 vm.computedProperty 的值时会调用该方法

   * 然后执行 this.get,即 watcher 的回调函数,得到返回值

   * this.dirty 被置为 false,作用是页面在本次渲染中只会一次 computed.key 的回调函数,

   *   这也是大家常说的 computed 和 methods 区别之一是 computed 有缓存的原理所在

   * 而页面更新后会 this.dirty 会被重新置为 true,这一步是在 this.update 方法中完成的

   */

  evaluate () {

    this.value = this.get()

    this.dirty = false

  }



  /**

   * Depend on all deps collected by this watcher.

   */

  depend () {

    let i = this.deps.length

    while (i--) {

      this.deps[i].depend()

    }

  }



  /**

   * Remove self from all dependencies' subscriber list.

   */

  teardown () {

    if (this.active) {

      // remove self from vm's watcher list

      // this is a somewhat expensive operation so we skip it

      // if the vm is being destroyed.

      if (!this.vm._isBeingDestroyed) {

        remove(this.vm._watchers, this)

      }

      let i = this.deps.length

      while (i--) {

        this.deps[i].removeSub(this)

      }

      this.active = false

    }

  }

}

Vue 的数据响应式原理

深入剖析Vue源码 - 响应式系统构建(上)

依赖收集

  1. 如何做到与视图无关的data更新,不会触发watcher?

在第一次渲染时,在getter里收集Dep的subs,在setter时仅仅触发这些subs。

响应式:数据变了,可以更新视图。

function observer(target) {

    if(typeof target !== 'object' || target == null) {

        return target;

    }

    if(Array.isArray(target)) {

        Object.setPrototypeOf(target, proto); // 更改它的原型

    }

    for (let key in target) {

        // 如果是对象,则递归调用。如果嵌套很深,很浪费性能。

        defineReactive(target, key, target[key]);

    }

}



function defineReactive(target, key, value) {

    Object.defineProperty(target, key, {

        get() { // get中依赖收集

            return value;

        },

        set(newValue) {

            if(newValue !== value) {

                // 对于新添加的属性,进行数据检测

                observer(newValue); 

                updateView();

                value = newValue;

            }

        }

    })

}



function updateView() {

    console.log('更新视图');

}

// 通过Object.defineProperty 就是可以重新定义属性  给属性加 getter 和 setter。

let data = { name: 'zf' };

observer(data);

data.name = 'jw';



let oldArrayPrototype = Array.prototype;

let proto = Object.create(oldArrayPrototype);

['push', 'pop'].forEach(method => {

    updateView();

    oldArrayPrototype[method].call(this, ...arguments);

})

Vue的缺点

  1. 需要对数组上的方法进行重写。 push shift unshift pop reverse。不能直接在原型上重写。数组改变是无效的。

  2. 对于对象嵌套的数据,需要递归。

  3. 不支持检测新属性