数据驱动
- 数据驱动
- 只需要关心数据处理,不用关心数据是如何渲染到视图的
- 属性响应式
- 数据驱动的具体实现方式
- 双向数据绑定
- 数据更新会导致视图更新,视图变化会影响数据变化
Vue2 响应式原理
- Vue 2.x 的数据响应式通过 Object.defineProperty() 实现
- 设置 data 后,遍历所以属性,转换为 Getter 和 Setter,从而在数据变化时进行视图更新操作
<div id="app">原始内容</div><script>// 声明数据对象,模拟 Vue 实例的 data 属性let data = {msg: 'hello'}// 模拟 Vue 实例的对象let vm = {}// 通过数据劫持的方式,将 data 的属性设置为 getter/setterObject.defineProperty(vm, 'msg', {// 可遍历enumerable: true,// 可配置configurable: true,get () {console.log('访问了属性')return data.msg},set (newValue) {// 更新数据data.msg = newValue// 数据更改,更新视图中 DOM 元素的内容document.querySelector('#app').textContent = data.msg}})</script>
- 设置 data 后,遍历所以属性,转换为 Getter 和 Setter,从而在数据变化时进行视图更新操作
处理多个属性的情况
<div id="app">原始内容</div><script>// 声明数据对象,模拟 Vue 实例的 data 属性let data = {msg1: 'hello',msg2: 'world',}// 模拟 Vue 实例的对象let vm = {}// 遍历被劫持对象的所有属性Object.keys(data).forEach(key => {// 通过数据劫持的方式,将 data 的属性设置为 getter/setterObject.defineProperty(vm, key, {enumerable: true,configurable: true,get () {console.log('访问了属性')return data[key]},set (newValue) {// 更新数据data[key] = newValue// 数据更改,更新视图中 DOM 元素的内容document.querySelector('#app').textContent = data[key]}})})</script>
监测数组方法
<div id="app">原始内容</div><script>// 声明数据对象,模拟 Vue 实例的 data 属性let data = {msg1: 'hello',msg2: 'world',arr: [1, 2, 3]}// 模拟 Vue 实例的对象let vm = {}// --- 添加数组方法支持 ---const arrMethodName = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']// 用于存储处理结果的对象,准备替换掉数组实例的原型指针 __proto__const customProto = {}// 为了避免数组实例无法再使用其他的数组方法customProto.__proto__ = Array.prototypearrMethodName.forEach(method => {customProto[method] = function () {// 确保原始功能可以使用(this 为数组实例)const result = Array.prototype[method].apply(this, arguments)// 进行其他自定义功能设置,例如,更新视图document.querySelector('#app').textContent = thisreturn result}})// 遍历被劫持对象的所有属性Object.keys(data).forEach(key => {// 检测是否为数组if (Array.isArray(data[key])) {// 将当前数组实例的 __proto__ 更换为 customProto 即可data[key].__proto__ = customProto}// 通过数据劫持的方式,将 data 的属性设置为 getter/setterObject.defineProperty(vm, key, {enumerable: true,configurable: true,get () {console.log('访问了属性')return data[key]},set (newValue) {// 更新数据data[key] = newValue// 数据更改,更新视图中 DOM 元素的内容document.querySelector('#app').textContent = data[key]}})})</script>
封装递归
<div id="app">原始内容</div><script>// 声明数据对象,模拟 Vue 实例的 data 属性let data = {msg1: 'hello',msg2: 'world',arr: [1, 2, 3],obj: {name: 'jack',age: 18}}// 模拟 Vue 实例的对象let vm = {}// 封装为函数,用于对数据进行响应式处理const createReactive = (function () {const arrMethodName = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']const customProto = {}customProto.__proto__ = Array.prototypearrMethodName.forEach(method => {customProto[method] = function () {const result = Array.prototype[method].apply(this, arguments)document.querySelector('#app').textContent = thisreturn result}})// 需要进行数据劫持的主体功能,也是递归时需要的功能return function (data, vm) {// 遍历被劫持对象的所有属性Object.keys(data).forEach(key => {// 检测是否为数组if (Array.isArray(data[key])) {// 将当前数组实例的 __proto__ 更换为 customProto 即可data[key].__proto__ = customProto} else if (typeof data[key] === 'object' && data[key] !== null) {// 检测是否为对象,如果为对象,进行递归操作vm[key] = {}createReactive(data[key], vm[key])return}// 通过数据劫持的方式,将 data 的属性设置为 getter/setterObject.defineProperty(vm, key, {enumerable: true,configurable: true,get () {console.log('访问了属性')return data[key]},set (newValue) {// 更新数据data[key] = newValue// 数据更改,更新视图中 DOM 元素的内容document.querySelector('#app').textContent = data[key]}})})}})()createReactive(data, vm)</script>
Vue3 响应式原理
Vue 3.x 与 Vue 2.x 的区别为数据响应式是通过 Proxy 实现的,其他的都相同
<div id="app">原始内容</div><script>const data = {msg: 'hello',content: 'world',arr: [1, 2, 3],obj: {name: 'william',age: 18}}const vm = new Proxy(data, {get (target, key) {return target[key]},set (target, key, newValue) {// 数据更新target[key] = newValue// 视图更新document.querySelector('#app').textContent = target[key]}})</script>
观察者模式
- 观察者模式(Observer pattern)指的是在对象间定义一个—对多个(被观察者与多个观察者)的关联,当一个对象改变了状态,所有其他相关的对象会被通知并且自动刷新
- 核心概念
- 观察者Observer
- 被观察者(观察目标)Subject

// 被观察者 (观察目标)// 1 添加观察者// 2 通知所有观察者class Subject {constructor () {// 存储所有的观察者this.observers = []}// 添加观察者功能addObserver (observer) {// 检测传入的参数是否为 观察者 实例if (observer && observer.update) {this.observers.push(observer)}}// 通知所有观察者notify () {// 调用观察者列表中每个观察者的更新方法this.observers.forEach(observer => {observer.update()})}}// 观察者// 1 当观察目标发生状态变化时,进行"更新"class Observer {update () {console.log('事件发生了,进行相应的处理...')}}// 功能测试const subject = new Subject()const ob1 = new Observer()const ob2 = new Observer()// 将观察者添加给要观察的观察目标subject.addObserver(ob1)subject.addObserver(ob2)// 通知观察者进行操作(某些具体的场景下)subject.notify()
发布/订阅模式
- 发布-订阅模式(Publish-subscribe pattern)可认为是为观察者模式解耦的进阶版本
- 在发布与订阅者之间添加消息中心,所有的消息均通过消息中心管理,而发布者与订阅者不会直接联系,实现了两者的解耦
- 核心概念
- 消息中心 Dep
- 订阅者 Subscriber
- 发布者 Publisher

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script><script>// 创建了一个 Vue 实例(消息中心)const eventBus = new Vue()// 注册事件(设置订阅者)eventBus.$on('dataChange', () => {console.log('事件处理功能1')})eventBus.$on('dataChange', () => {console.log('事件处理功能2')})// 触发事件(设置发布者)eventBus.$emit('dataChange')</script>
小结
- 观察者模式是由观察者与观察目标组成的,适合组件内操作
- 特性:特殊事件发生后,观察目标统一通知所有观察者
- 发布/订阅模式是由发布者与订阅者以及消息中心组成,更在适合消息类型复杂的情况
- 特性:特殊事件发生,消息中心接到发布指令后,会根据事件类型给对应的订阅者发送消息
Vue 响应式模拟
整体分析

- Vue
- 目标:将 data 数据注入到 Vue 实例,便于方法内操作
- Observer(发布者)
- 目标:数据劫持,监听数据变化,并在变化时通知 Dep
- Dep(消息中心)
- 目标:存储订阅者以及管理消息的发送
- Watcher(订阅者)
- 目标:订阅数据变化,进行视图的更新
- Compiler
- 解析模板中的指令与插值表达式,并替换相应的数据
Vue 类
- 功能
- 接收配置信息
- 将 data 的属性转换成 Getter/Setter,并注入到 Vue 实例中
- *监听 data 中所有属性的变化,设置成响应式数据
- *调用解析功能(解析模板内插值表达式、指令等)
- 结构
- $optons
- $el
- $data
- _proxyData()
Observer 类
- 功能
- 通过数据劫持方式监视 data 中属性变化,变化时通知消息中心 Dep
- 需要考虑 data 的属性也可能是对象,也要转换成响应式数据
- 结构
- walk(data)
- defineReactive(data, key, value)
Dep 类
- Dep 是 Dependency 的简写,含义为“依赖”,指的是 Dep 用于收集与管理订阅者与发布者之间的依赖关系
- 功能
- *为每个数据收集对应的依赖,存储依赖
- 添加并存储订阅者
- 数据变化时,通知所有观察者
- 结构
- subs
- addSub(sub)
- notify()
Watcher 类
- 功能
- 实例化 Watcher 时,往 dep 对象中添加自己
- 当数据变化触发 dep, dep通知所有对应的 Watcher 实例更新视图
- 结构
- vm
- key
- cb
- oldValue
- update()
Compiler 类
- 功能
- 进行编译模板,并解析内部指令与插值表达式
- 进行页面的首次渲染
- 数据变化后重新渲染视图
- 结构
- el
- vm
- compiler(el)
- compilerElement(node)
- compilerText(node)
- isDirective(attrname)
- isTextNode(node)
- isElementNode(node)
总结
- Vue类
- 把 data 的属性注入到 Vue 实例
- 调用 Observer 实现数据响应式处理
- 调用 Compiler 编译模板
- Observer 类
- 将 data 的属性转换成 Getter/Setter
- 为 Dep 添加订阅者 Watcher
- 数据变化发送通知 Dep
- Dep 类
- 收集依赖,添加订阅者 (watcher)
- 通知订阅者
- Watcher 类
- 编译模板时创建订阅者,订阅者数据变化
- 接到 Dep 通知时,调用 Compiler 中的模板功能更新视图
- Compiler 类
- 编译模板,解析指令与差值表达式
- 负责页面首次渲染与数据变化后重新渲染

