title: vue源码解析之响应式原理
date: 2020-11-06 18:21:41
tags:

  • vue
  • 源码
    categories: 学习
    copyright: false
    top_img: /img/bg1.png

vue响应式原理的核心代码主要是在:src\core\instance\state.js

  1. export function initState (vm: Component) {
  2. vm._watchers = []
  3. const opts = vm.$options
  4. if (opts.props) initProps(vm, opts.props)
  5. if (opts.methods) initMethods(vm, opts.methods)
  6. if (opts.data) {
  7. initData(vm)
  8. } else {
  9. observe(vm._data = {}, true /* asRootData */)
  10. }
  11. if (opts.computed) initComputed(vm, opts.computed)
  12. if (opts.watch && opts.watch !== nativeWatch) {
  13. initWatch(vm, opts.watch)
  14. }
  15. }

直截了当的看下initData

initData:

  1. function initData (vm: Component) {
  2. let data = vm.$options.data
  3. data = vm._data = typeof data === 'function'
  4. ? getData(data, vm)
  5. : data || {}
  6. if (!isPlainObject(data)) {
  7. data = {}
  8. process.env.NODE_ENV !== 'production' && warn(
  9. 'data functions should return an object:\n' +
  10. 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
  11. vm
  12. )
  13. }
  14. // proxy data on instance
  15. const keys = Object.keys(data)
  16. const props = vm.$options.props
  17. const methods = vm.$options.methods
  18. let i = keys.length
  19. while (i--) {
  20. const key = keys[i]
  21. if (process.env.NODE_ENV !== 'production') {
  22. if (methods && hasOwn(methods, key)) {
  23. warn(
  24. `Method "${key}" has already been defined as a data property.`,
  25. vm
  26. )
  27. }
  28. }
  29. if (props && hasOwn(props, key)) {
  30. process.env.NODE_ENV !== 'production' && warn(
  31. `The data property "${key}" is already declared as a prop. ` +
  32. `Use prop default value instead.`,
  33. vm
  34. )
  35. } else if (!isReserved(key)) {
  36. proxy(vm, `_data`, key)
  37. }
  38. }
  39. // observe data
  40. observe(data, true /* asRootData */)
  41. }

上边代码,我们的主线任务就是observe(data, true /* asRootData */)

直线任务就是数据代理 proxy(vm, _data, key)

proxy

  1. const sharedPropertyDefinition = {
  2. enumerable: true,
  3. configurable: true,
  4. get: noop,
  5. set: noop
  6. }
  7. export function proxy (target: Object, sourceKey: string, key: string) {
  8. sharedPropertyDefinition.get = function proxyGetter () {
  9. return this[sourceKey][key]
  10. }
  11. sharedPropertyDefinition.set = function proxySetter (val) {
  12. this[sourceKey][key] = val
  13. }
  14. Object.defineProperty(target, key, sharedPropertyDefinition)
  15. }

数据代理其实非常简单,也就这几行代码,vuedata是储存在_data中的,通过Object.defineProperty,设置getset,将target(vm)取值代理到了,vm._data,赋值也是代理到了,vm._data

observe:

  1. export function observe (value: any, asRootData: ?boolean): Observer | void {
  2. if (!isObject(value) || value instanceof VNode) {
  3. return
  4. }
  5. let ob: Observer | void
  6. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  7. ob = value.__ob__
  8. } else if (
  9. shouldObserve &&
  10. !isServerRendering() &&
  11. (Array.isArray(value) || isPlainObject(value)) &&
  12. Object.isExtensible(value) &&
  13. !value._isVue
  14. ) {
  15. ob = new Observer(value)
  16. }
  17. if (asRootData && ob) {
  18. ob.vmCount++
  19. }
  20. return ob
  21. }

上边代码中,主要关注的是ob = new Observer(value)vuevalue(data)传进了Observer构造函数进行实例化,那我们就看下Observer构造函数的相关代码

Observer

  1. export class Observer {
  2. value: any;
  3. dep: Dep;
  4. vmCount: number; // number of vms that have this object as root $data
  5. constructor (value: any) {
  6. this.value = value
  7. this.dep = new Dep()
  8. this.vmCount = 0
  9. def(value, '__ob__', this)
  10. if (Array.isArray(value)) {
  11. if (hasProto) {
  12. protoAugment(value, arrayMethods)
  13. } else {
  14. copyAugment(value, arrayMethods, arrayKeys)
  15. }
  16. this.observeArray(value)
  17. } else {
  18. this.walk(value)
  19. }
  20. }
  21. /**
  22. * Walk through all properties and convert them into
  23. * getter/setters. This method should only be called when
  24. * value type is Object.
  25. */
  26. walk (obj: Object) {
  27. const keys = Object.keys(obj)
  28. for (let i = 0; i < keys.length; i++) {
  29. defineReactive(obj, keys[i])
  30. }
  31. }
  32. /**
  33. * Observe a list of Array items.
  34. */
  35. observeArray (items: Array<any>) {
  36. for (let i = 0, l = items.length; i < l; i++) {
  37. observe(items[i])
  38. }
  39. }
  40. }

上边代码在constructor中判断了是否为数组,那就是说明,vue对于对象和数组进行了不同的处理,这时候应该能联想到,vue文档中数组的7种变异方法。我们待会再来看这个,我们首先看不是数组的情况。

也就是walk方法。

walk

  1. walk (obj: Object) {
  2. const keys = Object.keys(obj)
  3. for (let i = 0; i < keys.length; i++) {
  4. defineReactive(obj, keys[i])
  5. }
  6. }

显然,defineReactive是我们需要关注的.

  1. export function defineReactive (
  2. obj: Object,
  3. key: string,
  4. val: any,
  5. customSetter?: ?Function,
  6. shallow?: boolean
  7. ) {
  8. const dep = new Dep()
  9. const property = Object.getOwnPropertyDescriptor(obj, key)
  10. if (property && property.configurable === false) {
  11. return
  12. }
  13. // cater for pre-defined getter/setters
  14. const getter = property && property.get
  15. const setter = property && property.set
  16. if ((!getter || setter) && arguments.length === 2) {
  17. val = obj[key]
  18. }
  19. let childOb = !shallow && observe(val)//递归,拦截对象中的所有对象
  20. Object.defineProperty(obj, key, {
  21. enumerable: true,
  22. configurable: true,
  23. get: function reactiveGetter () {
  24. const value = getter ? getter.call(obj) : val
  25. if (Dep.target) {
  26. dep.depend()
  27. if (childOb) {
  28. childOb.dep.depend()
  29. if (Array.isArray(value)) {
  30. dependArray(value)
  31. }
  32. }
  33. }
  34. return value
  35. },
  36. set: function reactiveSetter (newVal) {
  37. const value = getter ? getter.call(obj) : val
  38. /* eslint-disable no-self-compare */
  39. if (newVal === value || (newVal !== newVal && value !== value)) {
  40. return
  41. }
  42. /* eslint-enable no-self-compare */
  43. if (process.env.NODE_ENV !== 'production' && customSetter) {
  44. customSetter()
  45. }
  46. // #7981: for accessor properties without setter
  47. if (getter && !setter) return
  48. if (setter) {
  49. setter.call(obj, newVal)
  50. } else {
  51. val = newVal
  52. }
  53. childOb = !shallow && observe(newVal)
  54. dep.notify()
  55. }
  56. })
  57. }

我们直接来看 Object.defineProperty,

在get中,是给dep实例添加依赖,

  1. if (Dep.target) {
  2. dep.depend()
  3. if (childOb) {
  4. childOb.dep.depend()
  5. if (Array.isArray(value)) {
  6. dependArray(value)
  7. }
  8. }

Dep类

  1. export default class Dep {
  2. static target: ?Watcher;
  3. id: number;
  4. subs: Array<Watcher>;
  5. constructor () {
  6. this.id = uid++
  7. this.subs = []
  8. }
  9. addSub (sub: Watcher) {
  10. this.subs.push(sub)
  11. }
  12. removeSub (sub: Watcher) {
  13. remove(this.subs, sub)
  14. }
  15. depend () {
  16. if (Dep.target) {
  17. Dep.target.addDep(this)
  18. }
  19. }
  20. notify () {
  21. // stabilize the subscriber list first
  22. const subs = this.subs.slice()
  23. if (process.env.NODE_ENV !== 'production' && !config.async) {
  24. // subs aren't sorted in scheduler if not running async
  25. // we need to sort them now to make sure they fire in correct
  26. // order
  27. subs.sort((a, b) => a.id - b.id)
  28. }
  29. for (let i = 0, l = subs.length; i < l; i++) {
  30. subs[i].update()
  31. }
  32. }
  33. }

Dep类,调用实例方法depend,接着调用了类静态属性target(target表示当前取值的组件watcher)的addDep方法.

Watcher类

  1. export default class Watcher {
  2. vm: Component;
  3. expression: string;
  4. cb: Function;
  5. id: number;
  6. deep: boolean;
  7. user: boolean;
  8. lazy: boolean;
  9. sync: boolean;
  10. dirty: boolean;
  11. active: boolean;
  12. deps: Array<Dep>;
  13. newDeps: Array<Dep>;
  14. depIds: SimpleSet;
  15. newDepIds: SimpleSet;
  16. before: ?Function;
  17. getter: Function;
  18. value: any;
  19. constructor (
  20. vm: Component,
  21. expOrFn: string | Function,
  22. cb: Function,
  23. options?: ?Object,
  24. isRenderWatcher?: boolean
  25. ) {
  26. this.vm = vm
  27. if (isRenderWatcher) {
  28. vm._watcher = this
  29. }
  30. vm._watchers.push(this)
  31. // options
  32. if (options) {
  33. this.deep = !!options.deep
  34. this.user = !!options.user
  35. this.lazy = !!options.lazy
  36. this.sync = !!options.sync
  37. this.before = options.before
  38. } else {
  39. this.deep = this.user = this.lazy = this.sync = false
  40. }
  41. this.cb = cb
  42. this.id = ++uid // uid for batching
  43. this.active = true
  44. this.dirty = this.lazy // for lazy watchers
  45. this.deps = []
  46. this.newDeps = []
  47. this.depIds = new Set()
  48. this.newDepIds = new Set()
  49. this.expression = process.env.NODE_ENV !== 'production'
  50. ? expOrFn.toString()
  51. : ''
  52. // parse expression for getter
  53. if (typeof expOrFn === 'function') {
  54. this.getter = expOrFn
  55. } else {
  56. this.getter = parsePath(expOrFn)
  57. if (!this.getter) {
  58. this.getter = noop
  59. process.env.NODE_ENV !== 'production' && warn(
  60. `Failed watching path: "${expOrFn}" ` +
  61. 'Watcher only accepts simple dot-delimited paths. ' +
  62. 'For full control, use a function instead.',
  63. vm
  64. )
  65. }
  66. }
  67. this.value = this.lazy
  68. ? undefined
  69. : this.get()
  70. }
  71. /**
  72. * Evaluate the getter, and re-collect dependencies.
  73. */
  74. get () {
  75. pushTarget(this)
  76. let value
  77. const vm = this.vm
  78. try {
  79. value = this.getter.call(vm, vm)
  80. } catch (e) {
  81. if (this.user) {
  82. handleError(e, vm, `getter for watcher "${this.expression}"`)
  83. } else {
  84. throw e
  85. }
  86. } finally {
  87. // "touch" every property so they are all tracked as
  88. // dependencies for deep watching
  89. if (this.deep) {
  90. traverse(value)
  91. }
  92. popTarget()
  93. this.cleanupDeps()
  94. }
  95. return value
  96. }
  97. /**
  98. * Add a dependency to this directive.
  99. */
  100. addDep (dep: Dep) {
  101. const id = dep.id
  102. if (!this.newDepIds.has(id)) {
  103. this.newDepIds.add(id)
  104. this.newDeps.push(dep)
  105. if (!this.depIds.has(id)) {
  106. dep.addSub(this)
  107. }
  108. }
  109. }
  110. /**
  111. * Clean up for dependency collection.
  112. */
  113. cleanupDeps () {
  114. let i = this.deps.length
  115. while (i--) {
  116. const dep = this.deps[i]
  117. if (!this.newDepIds.has(dep.id)) {
  118. dep.removeSub(this)
  119. }
  120. }
  121. let tmp = this.depIds
  122. this.depIds = this.newDepIds
  123. this.newDepIds = tmp
  124. this.newDepIds.clear()
  125. tmp = this.deps
  126. this.deps = this.newDeps
  127. this.newDeps = tmp
  128. this.newDeps.length = 0
  129. }
  130. /**
  131. * Subscriber interface.
  132. * Will be called when a dependency changes.
  133. */
  134. update () {
  135. /* istanbul ignore else */
  136. if (this.lazy) {
  137. this.dirty = true
  138. } else if (this.sync) {
  139. this.run()
  140. } else {
  141. queueWatcher(this)
  142. }
  143. }
  144. /**
  145. * Scheduler job interface.
  146. * Will be called by the scheduler.
  147. */
  148. run () {
  149. if (this.active) {
  150. const value = this.get()
  151. if (
  152. value !== this.value ||
  153. // Deep watchers and watchers on Object/Arrays should fire even
  154. // when the value is the same, because the value may
  155. // have mutated.
  156. isObject(value) ||
  157. this.deep
  158. ) {
  159. // set new value
  160. const oldValue = this.value
  161. this.value = value
  162. if (this.user) {
  163. try {
  164. this.cb.call(this.vm, value, oldValue)
  165. } catch (e) {
  166. handleError(e, this.vm, `callback for watcher "${this.expression}"`)
  167. }
  168. } else {
  169. this.cb.call(this.vm, value, oldValue)
  170. }
  171. }
  172. }
  173. }
  174. /**
  175. * Evaluate the value of the watcher.
  176. * This only gets called for lazy watchers.
  177. */
  178. evaluate () {
  179. this.value = this.get()
  180. this.dirty = false
  181. }
  182. /**
  183. * Depend on all deps collected by this watcher.
  184. */
  185. depend () {
  186. let i = this.deps.length
  187. while (i--) {
  188. this.deps[i].depend()
  189. }
  190. }
  191. /**
  192. * Remove self from all dependencies' subscriber list.
  193. */
  194. teardown () {
  195. if (this.active) {
  196. // remove self from vm's watcher list
  197. // this is a somewhat expensive operation so we skip it
  198. // if the vm is being destroyed.
  199. if (!this.vm._isBeingDestroyed) {
  200. remove(this.vm._watchers, this)
  201. }
  202. let i = this.deps.length
  203. while (i--) {
  204. this.deps[i].removeSub(this)
  205. }
  206. this.active = false
  207. }
  208. }
  209. }
  1. addDep (dep: Dep) {
  2. const id = dep.id
  3. if (!this.newDepIds.has(id)) {
  4. this.newDepIds.add(id)
  5. this.newDeps.push(dep)
  6. if (!this.depIds.has(id)) {
  7. dep.addSub(this)
  8. }
  9. }
  10. }

addDepdep的相关信息,添加到了watcher中,同时执行了depaddSubwatcher添加到dep中的subs数组中,形成相互依赖。

  1. set: function reactiveSetter (newVal) {
  2. const value = getter ? getter.call(obj) : val
  3. /* eslint-disable no-self-compare */
  4. if (newVal === value || (newVal !== newVal && value !== value)) {
  5. return
  6. }
  7. /* eslint-enable no-self-compare */
  8. if (process.env.NODE_ENV !== 'production' && customSetter) {
  9. customSetter()
  10. }
  11. // #7981: for accessor properties without setter
  12. if (getter && !setter) return
  13. if (setter) {
  14. setter.call(obj, newVal)
  15. } else {
  16. val = newVal
  17. }
  18. childOb = !shallow && observe(newVal)
  19. dep.notify()
  20. }

我们看到在set函数中,执行了dep.notify()这个函数就是来发布,通知订阅者进行更新的。

  1. notify () {
  2. // stabilize the subscriber list first
  3. const subs = this.subs.slice()
  4. if (process.env.NODE_ENV !== 'production' && !config.async) {
  5. // subs aren't sorted in scheduler if not running async
  6. // we need to sort them now to make sure they fire in correct
  7. // order
  8. subs.sort((a, b) => a.id - b.id)
  9. }
  10. for (let i = 0, l = subs.length; i < l; i++) {
  11. subs[i].update()
  12. }
  13. }

我们知道,subs数组中的都是watcher,而每一个watcher中都有一个update函数,是用来更新页面的。

update之前nextTick介绍过就不重复介绍了。到此为止,vue响应式原理的核心内容已经结束。