• Vue2、Vue3响应式原理和相关源码(持续更新~)


    目录

    一、vue2响应式原理

    对应常规源码?:

    数组有哪些不同?:

    总结下,vue2有哪些缺陷?


    在以往的经验中,我们主技术栈为vue的前端开发,在面试的时候被问到响应式原理的概率是非常大的。而现在市场上,基本上老项目还是vue2,新项目大部分都会选择vue3来开发。

    所以,我们有必要来总结对应的源码和面试题,come on ~

    一、vue2响应式原理

          简述:在组件初始化时,通过Object.defineProperty来进行对数据进行劫持,构造get set方法。get来进行依赖的收集dep,set用于通知dep.notify。

            对应常规源码?:
    1.  observe()函数返回一个ob实例。
    1. export function observe (value: any, asRootData: ?boolean): Observer | void {
    2. // 观察者
    3. let ob: Observer | void
    4. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    5. ob = value.__ob__
    6. } else if (
    7. shouldObserve &&
    8. !isServerRendering() &&
    9. (Array.isArray(value) || isPlainObject(value)) &&
    10. Object.isExtensible(value) &&
    11. !value._isVue
    12. ) {// 创建观察者
    13. ob = new Observer(value)
    14. }
    15. if (asRootData && ob) {
    16. ob.vmCount++
    17. }
    18. return ob
    19. }

    2. ob对象根据不同的数据类型进行不同的响应化操作

    1. export class Observer {
    2. value: any;
    3. dep: Dep; // 保存数组类型数据的依赖
    4. constructor (value: any) {
    5. this.value = value
    6. this.dep = new Dep()
    7. def(value, '__ob__', this) // 在getter中可以通过__ob__可获取ob实例
    8. if (Array.isArray(value)) { // 数组响应化
    9. protoAugment(value, arrayMethods)
    10. this.observeArray(value)
    11. } else { // 对象响应化
    12. this.walk(value)
    13. }
    14. }
    15. /**
    16. * 遍历对象所有属性定义其响应化
    17. */
    18. walk (obj: Object) {
    19. const keys = Object.keys(obj)
    20. for (let i = 0; i < keys.length; i++) {
    21. defineReactive(obj, keys[i])
    22. }
    23. }
    24. /**
    25. * 对数组每一项执行observe
    26. */
    27. observeArray (items: Array) {
    28. for (let i = 0, l = items.length; i < l; i++) {
    29. observe(items[i])
    30. }
    31. }
    32. }

    3. defineReactive()函数定义get、set方法,并收集依赖。

    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() // 一个key一个Dep实例
    9. // 递归执行子对象响应化
    10. let childOb = !shallow && observe(val)
    11. // 定义当前对象getter/setter
    12. Object.defineProperty(obj, key, {
    13. enumerable: true,
    14. configurable: true,
    15. get: function reactiveGetter () {
    16. // getter负责依赖收集
    17. if (Dep.target) {
    18. dep.depend()
    19. // 若存在子observer,则依赖也追加到子ob
    20. if (childOb) {
    21. childOb.dep.depend()
    22. if (Array.isArray(value)) {
    23. dependArray(value) // 数组需特殊处理
    24. }
    25. }
    26. }
    27. return value
    28. },
    29. set: function reactiveSetter (newVal) {
    30. if (newVal === value || (newVal !== newVal && value !== value)) {
    31. return
    32. }
    33. val = newVal // 更新值
    34. childOb = !shallow && observe(newVal) // childOb更新
    35. dep.notify() // 通知更新
    36. }
    37. })
    38. }

    4. Dep管理一组watcher, dep管理的值更新时,通知对应的watcher进行更新

    1. export default class Dep {
    2. static target: ?Watcher; // 依赖收集时的wacher引用
    3. subs: Array<Watcher>; // watcher数组
    4. constructor () {
    5. this.subs = []
    6. }
    7. //添加watcher实例
    8. addSub (sub: Watcher) {
    9. this.subs.push(sub)
    10. }
    11. //删除watcher实例
    12. removeSub (sub: Watcher) {
    13. remove(this.subs, sub)
    14. }
    15. //watcher和dep相互保存引用
    16. depend () {
    17. if (Dep.target) {
    18. Dep.target.addDep(this)
    19. }
    20. }
    21. notify () {
    22. // stabilize the subscriber list first
    23. const subs = this.subs.slice()
    24. for (let i = 0, l = subs.length; i < l; i++) {
    25. subs[i].update()
    26. }
    27. }
    28. }

    5. watcher监控数据或者关联组件的一个更新函数,数据更新则执行回调或者更新函数背调用。

    1. export default class Watcher {
    2. constructor (
    3. vm: Component,
    4. expOrFn: string | Function,
    5. cb: Function,
    6. options?: ?Object,
    7. isRenderWatcher?: boolean
    8. ) {
    9. this.vm = vm
    10. // 组件保存render watcher
    11. if (isRenderWatcher) {
    12. vm._watcher = this
    13. }
    14. // 组件保存非render watcher
    15. vm._watchers.push(this)
    16. // options...
    17. // 将表达式解析为getter函数
    18. // 如果是函数则直接指定为getter,那什么时候是函数?
    19. // 答案是那些和组件实例对应的Watcher创建时会传递组件更新函数updateComponent
    20. if (typeof expOrFn === 'function') {
    21. this.getter = expOrFn
    22. } else {
    23. // 这种是$watch传递进来的表达式,它们需要解析为函数
    24. this.getter = parsePath(expOrFn)
    25. if (!this.getter) {
    26. this.getter = noop
    27. }
    28. }
    29. // 若非延迟watcher,立即调用getter
    30. this.value = this.lazy ? undefined : this.get()
    31. }
    32. /**
    33. * 模拟getter, 重新收集依赖re-collect dependencies.
    34. */
    35. get () {
    36. // Dep.target = this
    37. pushTarget(this)
    38. let value
    39. const vm = this.vm
    40. try {
    41. // 从组件中获取到value同时触发依赖收集
    42. value = this.getter.call(vm, vm)
    43. }
    44. catch (e) {}
    45. finally {
    46. // deep watching,递归触发深层属性
    47. if (this.deep) {
    48. traverse(value)
    49. }
    50. popTarget()
    51. this.cleanupDeps()
    52. }
    53. return value
    54. }
    55. addDep (dep: Dep) {
    56. const id = dep.id
    57. if (!this.newDepIds.has(id)) {
    58. // watcher保存dep引用
    59. this.newDepIds.add(id)
    60. this.newDeps.push(dep)
    61. // dep添加watcher
    62. if (!this.depIds.has(id)) {
    63. dep.addSub(this)
    64. }
    65. }
    66. }
    67. update () {
    68. // 更新逻辑
    69. if (this.lazy) {
    70. this.dirty = true
    71. } else if (this.sync) {
    72. this.run()
    73. } else {
    74. //默认lazy和sync都是false,所以会走该逻辑
    75. queueWatcher(this)
    76. }
    77. }
    78. }
    数组有哪些不同?:
    1. 7个数组方法的打补丁和原型覆盖
      1. // 数组原型
      2. const arrayProto = Array.prototype
      3. // 修改后的原型
      4. export const arrayMethods = Object.create(arrayProto)
      5. // 七个待修改方法
      6. const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
      7. /**
      8. * 拦截这些方法,额外发送变更通知
      9. */
      10. methodsToPatch.forEach(function (method) {
      11. // 原始数组方法
      12. const original = arrayProto[method]
      13. // 修改这些方法的descriptor
      14. def(arrayMethods, method, function mutator (...args) {
      15. // 原始操作
      16. const result = original.apply(this, args)
      17. // 获取ob实例用于发送通知
      18. const ob = this.__ob__
      19. // 三个能新增元素的方法特殊处理
      20. let inserted
      21. switch (method) {
      22. case 'push':
      23. case 'unshift':
      24. inserted = args
      25. break
      26. case 'splice':
      27. inserted = args.slice(2)
      28. break
      29. }
      30. // 若有新增则做响应处理
      31. if (inserted) ob.observeArray(inserted)
      32. // 通知更新
      33. ob.dep.notify()
      34. return result
      35. })
      36. })
      1. if (Array.isArray(value)) {
      2. // 替换数组原型
      3. protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods
      4. this.observeArray(value)
      5. }

      2. 数组响应式的循环o'berve

      1. observeArray (items: Array) {
      2. for (let i = 0, l = items.length; i < l; i++) {
      3. observe(items[i])
      4. }
      5. }

      3. 依赖收集时做特殊处理,如果数组项中还是数组需要deepArray

      1. //getter中
      2. if (Array.isArray(value)) {
      3. dependArray(value)
      4. }
      5. // 数组中每一项也需要收集依赖
      6. function dependArray (value: Array) {
      7. for (let e, i = 0, l = value.length; i < l; i++) {
      8. e = value[i]
      9. e && e.__ob__ && e.__ob__.dep.depend()
      10. if (Array.isArray(e)) {
      11. dependArray(e)
      12. }
      13. }
      14. }
      总结下,vue2有哪些缺陷?
      1. 不能监听数组长度的变化
      2. 不能监听数组根据下标修改
      3. 不能监听对象的新增和删除
      4. 所以vue2提供了修补方法,使用$set()

    持续更新中~ 如有不足,敬请指出,共同进步~

  • 相关阅读:
    HashMap为什么会发生死循环?
    NSSCTF-Web题目5
    Spring Security 6.x 系列【68】 授权篇之基于注解 & 缓存的访问控制方案
    猿创征文 【SpringBoot2】基于SpringBoot实现SSMP整合
    uniapp封装mixins实现H5和微信小程序的微信支付
    开关、电机、断路器、电热偶、电表接线图大全
    Github主页添加贪吃蛇小组件
    PYTHON 自动化办公:更改图片后缀
    助力工业数字化!TDengine 与恩菲 MIM+ 工业互联网平台实现兼容性互认
    Kotlin协程:生命周期原理
  • 原文地址:https://blog.csdn.net/asddddd555/article/details/138162283