vue响应式原理(一)
🤔什么是响应式?
通俗来说,在使用 vue
脚手架开发项目的时候,当我们修改 data
中的数据,视图会自动更新,这种我们称为“响应式”。那为什么修改 data
中的数据视图会自动更新? vue
又是如何实现响应式的呢?这里我们就不得不提一个名词叫“数据代理”。
数据代理
什么是数据代理呢?其实很好理解,就是当我修改或者访问 data
中的属性时候是 vue
帮我们去修改和访问的,这就相当于 vue
中指定的行为代替了我们的默认行为。举个例子:比如小王让小李去咖啡店买杯咖啡,但是小李不想自己去,于是委托小明去,咖啡买回来后小李再拿给小王。这里的小明就是“代理”。同样的对于 vue
中的属性也可以设置代理。
在 Vue2.x
中,使用Object.defineProperty()
方法来进行数据代理,但是这种方法无法代理数组类型的数据属性,Vue2.x
中通过改写数组方法的方式来监听数组的改变。在 Vue3.x
时候改用 ES6 的新特性Proxy
来进行数据代理,就可以方便地监听数组变化了。
如何实现数据代理?
如果想要实现数据代理就需要对数据进行劫持,通俗来说就是当 data
中的数据发生变化的时候会被捕获到,而在 vue
中是通过 Object.defineProperty()
对 data中的每个属性添加 getter
和 setter
方法进行数据的劫持。如果仔细的观察过 data 中的属性都会有 getter
和 setter
方法。
Object.defineProperty()
上面我们提到了要实现数据代理就必须搞明白 Object.defineProperty()
是干嘛用的?
定义:
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:
Object.defineProperty(obj, prop, descriptor)
// `obj`: 要定义属性的对象。
// `prop`:要定义或修改的属性的名称或 [`Symbol`]
// `descriptor`:要定义或修改的属性描述符。
🪖案例展示
const person = {
age: 18,
name: '小明'
}
let nowAge
Object.defineProperty(person, 'age', {
// 读取数据的时候会触发getter
get() {
console.log(`有人访问了小明信息—————${nowAge}`); // 有人访问了小明信息————————undefined 有人访问了小明信息————————22
return nowAge
},
// 修改数据的时候会触发setter
set(value) {
console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); // 小明的信息被修改了,赶紧去更新视图吧=====22
nowAge = value
}
})
console.log(person.age); // undefined
person.age = 22
console.log(person.age); // 22
运行代码后:
分析:Object.defineProperty
中的描述符为我们提供了两个方法。分别是 getter
,当用户访问当前数据时触发,getter
是当用户修改数据的时候触发。所以当我们每次去访问 age
的时候都会触发 get
方法 ,而当我们去修改 age
的时候都会触发 set
方法,这里我们用一个全局临时变量 nowAge
去周转修改后 age
。
📦封装defineProperty
const person = {
age: 18,
name: '小明'
}
let nowAge
Object.defineProperty(person, 'age', {
// 读取数据的时候会触发getter
get() {
console.log(`有人访问了小明信息—————${nowAge}`); // 有人访问了小明信息————————undefined 有人访问了小明信息————————22
return nowAge
},
// 修改数据的时候会触发setter
set(value) {
console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); // 小明的信息被修改了,赶紧去更新视图吧=====22
nowAge = value
}
})
console.log(person.age); // undefined
person.age = 22
console.log(person.age); // 22
运行代码后:
分析:这里把 defineProperty
封装在 defineProperty
方法中,这样就不需要设置临时变量,利用闭包保证了数据的安全。
递归监听对象全部属性
在上面的案例中,我们实现了对于对象的第一层属性进行监听,但是如果出来了对象嵌套对象的情况,则无法进行监听,这个时候就需要使用递归监听对象的每一层属性。
const person = {
age: 18,
name: '小明',
hobby: {
ball: '篮球',
}
}
observer(person) // 第一次监听,这里只会监听 person 对象的第一层属性
function observer(data) {
if (typeof data === 'object') {
for (let key in data) {
defineReactive(data, key, data[key])
}
} else {
return data
}
}
function defineReactive(obj, key, nowAge) {
// 深度监听对象中每一层
observer(nowAge)
Object.defineProperty(obj, key, {
// 读取数据的时候会触发getter
get() {
console.log(`有人访问了小明信息————${nowAge}`); // 有人访问了小明信息————[object Object],篮球,[object Object],[object Object],足球
return nowAge
},
// 修改数据的时候会触发setter
set(value) {
console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); // 小明的信息被修改了,赶紧去更新视图吧=====足球
nowAge = value
}
})
}
console.log(person.hobby.ball); // 篮球
person.hobby.ball = '足球'
console.log(person.hobby.ball); // 足球
运行代码后:
分析:这里的 observer
函数是为了实现对于对象中嵌套的每一层属性都可以实时监听。可以看出对于 ball
修改的时候触发了 setter
方法去更新视图。值得注意的是这里的 getter
方法被触发了5次。
🤔️是否对于现有属性的修改都会被监听?
const person = {
age: 18,
name: '小明',
hobby: {
ball: '篮球',
}
}
observer(person) // 第一次监听,这里只会监听 person 对象的第一层属性
function observer(data) {
if (typeof data === 'object') {
for (let key in data) {
defineReactive(data, key, data[key])
}
} else {
return data
}
}
function defineReactive(obj, key, nowAge) {
// 深度监听对象中每一层
observer(nowAge)
Object.defineProperty(obj, key, {
// 读取数据的时候会触发getter
get() {
console.log(`有人访问了小明信息————${nowAge}`); // 有人访问了小明信息————[object Object],篮球,[object Object],[object Object],足球
return nowAge
},
// 修改数据的时候会触发setter
set(value) {
// observer(value)
console.log(`小明的信息被修改了,赶紧去更新视图吧=====${value}`); // 小明的信息被修改了,赶紧去更新视图吧=====足球
nowAge = value
}
})
}
person.hobby = {
reading: '鲁滨逊漂流记'
}
person.hobby.reading = '上下五千年'
运行代码后:
分析:上面的 hobby
被修改了两次,但是页面只更新了一次,这是由于在第一次修改 hobby
的时候我们并没有对 hobby
中的 reading
进行监听,这样第二次修改 reading
也没并不会更新,这个时候就需要在设置的时候也去监听,代码中就是在 set
方法中添加 observer(value)
。
🤔️删除或者添加新的属性会不会被监听?
实际上使用 Object.defineProperty
方法只能监听和修改已有属性值。如果添加和删除新的属性值是不会被 Object.defineProperty
监听到的, vue2.x 官方提供了两个方法用于处理删除和添加新的属性页面不更新的问题:Vue.set(target, propertyName/index, value)
, Vue.delete(target, propertyName/index, value)
. 当然在 vue3.x
中不会出现这样的问题,vue3.x
中对于属性的监听使用的 Proxy
。。。
转载自:https://juejin.cn/post/7225820682432823353