在Vue
中,响应式是指数据与视图之间建立一种自动关联的关系,当数据发生变化时,视图会自动更新以反应数据的变化,无需开发者手动操作DOM元素更新视图。
响应式是实现数据驱动视图的基础,在Vue
中,数据可以理解为状态state
,视图就是用户直观看到的页面UI
。页面会随着状态的改变而动态变化,因此可以得到以下公式:
UI = render(state)
上述公式中:状态state
是输入,页面UI
输出,状态输入一旦变化了,页面输出也随之而变化。这种特性就叫数据驱动视图。
公式可以进一步拆成三部分:state
、render()
以及UI
。其中state
和UI
都是用户定的或开发者实现的,而不变的是render()
函数。所以Vue
就扮演了render()
这个渲染的角色,当Vue
检测到state
变化之后,经过一系列加工,最终将变化响应式地反应在UI
界面上。
实现数据的响应式需要解决以下三个问题:
- 如何对状态
state
的变化进行监听? - 如何确定状态
state
改变之后要更新的视图? - 更新视图的时机是什么时候?
1. 状态state的变化监听
在Vue2
中,数据监听是借助了javaScript
提供的Object.defineProperty()
函数实现的。
1. Object.defineProperty()
Object.defineProperty()
静态方法用于在一个对象上定义一个新属性或修改该对象的现有属性。
Object.defineProperty(obj, prop, descriptor)
其中, descriptor
表示要定义的属性描述符object
,存在两种类型:数据描述符和访问器描述符。
注意: 描述符只能是这两种类型之一,且两者不能混合使用。
-
数据描述符是一个具有可写或不可写值的属性,对象支持四种键值:
value
、enumerable
、writable
、configurable
const person = { } Object.defineProperty(person, 'age', { value: 18, // 定义属性值,默认undefined enumerable: true, // 属性可枚举, 默认false writable: true, // 属性值可修改, 默认false configurable: true, // 属性可删除,默认false }) console.log(person.age) // > 18 person.age = 19 console.log(person.age) // > 19
-
访问器描述符是由
getter/setter
函数对描述的属性,支持四种键值:enumerable
、configurable
、get
、set
const person = { } let age = 18 Object.defineProperty(person, 'age', { enumerable: true, configurable: true, get() { console.log(`有人读取了person的年龄,当前年龄${ age}`) return age }, set(value) { console.log(`有人修改的person的年龄,新的年龄是${ value}`) age = value }, }) person.age person.age = 19 age
输出结果:
2. Vue2.0中Object的变化监听
Vue2.0
就是利用了Object.defineProperty
方法中的访问器描述符来劫持数据的读写操作。在getter
中捕获数据的读取事件,在setter
中捕获数据的修改事件,进而对数据的变化进行监听。
对于Object
类型的数据,Vue
通过递归遍历的方式将数据中的每一个属性设置为getter/setter
的形式,使对象的每一个属性都变得可观测
👉接下来是对Object
类型数据监听的简单实现:
-
模拟一个更新视图的函数
/** * @description: 更新视图函数 */ function updateView() { console.log('收到通知,我去更新视图了') }
-
定义
Observer
类在
vue
中,所有的响应式数据都是Observer
类的实例对象。/** * @description: 定义Observer类,把一个对象的所有属性转化成可观测对象 * @return {*} */ class Observer { constructor(value) { this.value = value // 给value新增一个__ob__属性,值为该value的Observer实例 // 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作 Object.defineProperty(value, '__ob__', { value: this, enumerable: false, writable: true, configurable: true, }) this.walk(value) } walk(value) { const keys = Object.keys(value) for (let i = 0; i < keys.length; i++) { defineReactive(value, keys[i]) // 遍历所有的属性,将所有属性值转为getter/setter形式 } } }
-
定义响应式函数
// 源码位置:/src/core/observer/index.ts /** * @description: 给对象的属性递归定义响应式,设置getter/setter,使对象属性的读取事件可监听 */ function defineReactive(obj, key, value) { if (arguments.length === 2) { value =