亮神知识库 亮神知识库
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)

亮神

前程明亮,未来可期
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)
  • Vue2

    • 响应式

      • MVVM概念
      • 响应式原理
        • 响应式原理
        • 核心角色
        • 核心代码
        • 响应式对数组是如何处理
        • 面试
      • nextTick原理
      • watch原理
      • computed原理
      • 面试
    • 模版编译

    • 虚拟DOM

    • 整体流程

    • vuex&vue-router

  • Vue3

  • Vue原理
  • Vue2
  • 响应式
0zcl
2021-10-18
目录

响应式原理

# 响应式原理

原理简述:采用数据劫持+发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者Watcher,触发相应的监听回调。

  • 数据监听器Observer: 对vue实例的data所有属性进行监听,如有变动可拿到属性最新值并通知订阅者Watcher
  • 指令解析器Compile: 对每个元素节点的指令进行扫描和解析,实例化Watcher,绑定Watcher回调函数,通过回调函数来将指令模板替换成数据
  • Watcher: 连接Observer和Compile的桥梁,能订阅并收到每个属性变动的通知,执行Watcher相应的回调函数,从而更新视图。

observe数据劫持在beforeCreate之后,created之前

在编译Compile阶段时,首先会执行update来第一次初始化视图;接着会实例化Watcher,将Dep.target赋值为watcher实例,并触依赖收集,将watcher实例添加到属性对应的dep实例中(defineProperty数据劫持时一个属性会实例化一个Dep对象)。当watcher实例收到通知时,会比较新值 、旧值,如果不相等,则触发 watcher 实例的回调来更新视图

reactive

# 核心角色

reactive

# 核心代码

<code>observer </code>

点击查看
// 遍历对象
function observer(value) {
  // 递归终止条件
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (Array.isArray(value) || isPlainObject(value)) {
    ob = new Observer(value)
  }
  return ob
}

class Observer {
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 处理数组
      <!-- this.observeArray(value) -->
    } else {
      // 处理对象
      this.walk(value)
    }
  }

  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])
    }
  } -->
}

// 用defineProperty监听当前属性
function defineReactive(obj: Object, key: string, val: any) {
  const dep = new Dep()

  // 如果属性是不可配置的,则return。会导致该属性无法做依赖依集与数据劫持
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get
  const setter = property && property.set

  // 递归
  observer(val)
  Object.defineProperty(obj, key, {
    enumerable: true, // 是否可以for in
    configurable: true,
    get: function() {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 依赖收集
        dep.depend()
        <!-- if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        } -->
      }
      return val
    },
    set: function(newVal) {
      const value = getter ? getter.call(obj) : val
      // 新旧值相同,不触发watcher更新
      if (newVal === value) {
        return
      }
      // 更新值
      setter.call(obj, newVal)
      // 派发更新
      dep.notify()
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

<code>Dep </code>

点击查看
class Dep {
  constructor() {
    // 初始化订阅队列
    this.subs = []
  }

  // 增加订阅
  addSub(sub) {
    this.subs.push(sub)
  }

  // 依赖收集
  depend () {
    if (Dep.target) {
      // 让watcher调用addDep,添加watcher到dep实例,参数为属性对应的dep实例
      Dep.target.addDep(this)
    }
  }

  // 通知订阅者watcher
  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

<code>Watcher </code>

点击查看
class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    ......
  ) {
    this.vm = vm
    <!-- if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this) -->
    this.cb = cb
    this.deps = []
    this.newDeps = []
    // 集合。不重复
    this.depIds = new Set()
    this.newDepIds = new Set()
  }

  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 依赖收集
        dep.addSub(this)
      }
    }
  }

  update () {
    this.run()
  }

  run () {
    if (this.active) {
      const value = this.get()
      // 新值不等于旧值,触发watcher回调函数cb
      if (value !== this.value) {
        const oldValue = this.value
        this.value = value
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 响应式对数组是如何处理

<code>Object.defineProperty </code>无法检测数组变化吗? 实际上,是可以的!

function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    configurable: true,
    get: () => {
        console.log('get')
        return val
    },
    set: (newVal) => {
        val = newVal
        console.log('set')
    }
  })
}

// 测试
arr = [1,2]
defineReactive(arr, 0, 1)
arr[0] // get 1
arr[0] = 'zcl' // set 'zcl'
arr // ['zcl', 1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

不用 <code>Object.defineProperty </code>来处理数组的响应式,个人理解是:监听数组所有索引性能有影响。

源码的处理是使用代理的方式,重写了Array.prototype的数组操作API。当数组使用api时,实际上是执行代理重写的数组api,在重写的api中会调用 <code>dep.notify()</code>, 通知watcher, 执行 update

if (Array.isArray(value)) {
  if (hasProto) {
    debugger
    protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods 指向代理对象
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  this.observeArray(value)
}

// src/core/observer/array.js
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) {
    debugger
    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
  })
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 面试

问:说说Vue响应式原理

答:

  1. 什么是响应式原理,修改data数据即可更新UI视图
  2. 为什么需要响。提高效率,无需手动操作DOM
  3. 数据劫持。initState时候会去遍历data,递归重写每个属性的defineProperty, 获取数据会触发get, 修改数据会触发set
  4. 依赖收集。页面渲染的时候,会去触发数据对应的getter,触发依赖收集。每个属性会有对应的dep实例, 每个属性对应的watch会放入dep.subs队列(收集每一个watcher)
  5. 派发更新。修改属性的时候,触发set, 会先通知dep进行notify, 循环触发dep.subs队列中的每个watch的更新

问:Vue的响应式对数组是如何处理?

答: 使用代理拦截数组的api操作,当调用数组的api时,实际上会执行代理重写的数组api,在重写的api中会执行dep.notify(),执行watcher的update

参考: 剖析Vue原理&实现双向绑定MVVM (opens new window)

编辑 (opens new window)
上次更新: 2025/07/20, 06:21:22
MVVM概念
nextTick原理

← MVVM概念 nextTick原理→

最近更新
01
2024年
07-20
02
2023年
07-20
03
2022年
07-20
更多文章>
Theme by Vdoing | Copyright © 2025-2025 亮神 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式