说到Vue的响应式,这恐怕是我们前端人员在面试的时候必问的问题了吧,还记得我刚大三暑假出来面试的时候,面试官A问:说说你对Vue响应式的理解,3和2两者有什么区别,当时我心里就想这你***的什么呀,老子都没听过
当然是了解过的呀,我解释完了之后面试官说了一句还行,只是不太深入
那今天咱们就来认识认识什么是响应式
一、什么是响应式?
看一下代码,当我们修改num的值时,想要对应的执行某一个引用num的函数
就像Vue中,修改了data里的属性时,对应的computed也会发生改变
上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式
let num = 3;
function changeNum() {
console.log('num被响应式的改变了', num);
}
num = 10;
二、怎么实现一个简单的响应式?
我们以Vue3的Proxy为例
const obj = {
name: '张三',
age: 19
}
// 类具有更好的收集性
class Depend {
constructor() {
//这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
this.reactives = new Set()
}
addDepend(fn) {
this.reactives.add(fn)
}
notify() {
this.reactives.forEach(fn => fn && fn())
}
}
//定义一个监听对应函数变化的函数, 形参fn接收变化的函数
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
//这里不限于使用箭头函数,匿名函数,命名函数...都可以
watchFn(() => {
console.log(`${obj.name}被响应了`)
})
watchFn(() => {
console.log(`${obj.age}被响应了`)
})
depend.notify()
setTimeout(() => {
obj.name = "curry"
depend.notify()
}, 3000);
此时我们就实现了一个简单的响应式
但是这里有一个问题,我们每次修改obj属性时,得手动去调用notify(),这有点不太响应式
将上面的代码使用Proxy代理监听属性的变化,自动执行notify()
但是我们看到控制台的打印结果,我们明明是只对name属性进行了修改,但是age的响应函数也被执行了,我们应该让每一个属性对应一个depend依赖
const obj = {
name: '张三',
age: 19
}
// 类具有更好的收集性
class Depend {
constructor() {
//这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
this.reactives = new Set()
}
addDepend(fn) {
this.reactives.add(fn)
}
notify() {
this.reactives.forEach(fn => fn && fn())
}
}
const objProxy = new Proxy(obj, {
get(target, key) {
// 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
return Reflect.get(target, key)
},
set(target, key, newValue) {
Reflect.set(target, key, newValue)
depend.notify()
}
})
//定义一个监听对应函数变化的函数, 形参fn接收变化的函数
const depend = new Depend()
function watchFn(fn) {
depend.addDepend(fn)
}
//这里不限于使用箭头函数,匿名函数,命名函数...都可以
watchFn(() => {
console.log(`${objProxy.name}被响应了`)
})
watchFn(() => {
console.log(`${objProxy.age}被响应了`)
})
depend.notify()
setTimeout(() => {
objProxy.name = "curry"
}, 3000);
接下来我们来实现属性对应独立的依赖的方法
const obj = {
name: '张三',
age: 19
}
// 类具有更好的收集性
class Depend {
constructor() {
//这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
this.reactives = new Set()
}
addDepend(fn) {
this.reactives.add(fn)
}
notify() {
this.reactives.forEach(fn => fn && fn())
}
}
// 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
const weakMap = new WeakMap()
function getDepend(target, key) {
let map = weakMap.get(target)
// 在第一次获取的时候肯定是没有对应的依赖的
if (!map) {
map = new Map()
weakMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const objProxy = new Proxy(obj, {
get(target, key) {
// 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
// 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
const depend = getDepend(target, key)
depend.addDepend(reFn)
return Reflect.get(target, key)
},
set(target, key, newValue) {
Reflect.set(target, key, newValue)
// 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可
const depend = getDepend(target, key)
depend.notify()
}
})
//定义一个监听对应函数变化的函数, 形参fn接收变化的函数
// const depend = new Depend()
let reFn = null;
function watchFn(fn) {
reFn = fn
fn()
reFn = null
}
//这里不限于使用箭头函数,匿名函数,命名函数...都可以
watchFn(() => {
console.log(`${objProxy.name}被响应了`)
})
watchFn(() => {
console.log(`${objProxy.age}被响应了`)
})
setTimeout(() => {
objProxy.name = "curry"
}, 3000);
此时我们修改name,就只会执行对应的name依赖,age的响应函数就不会被执行了
但是如果我们有多个对象,就要去再重新写一下new Proxy ,换一种封装的方法,将Proxy也封装起来
// 类具有更好的收集性
class Depend {
constructor() {
//这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
this.reactives = new Set()
}
addDepend(fn) {
this.reactives.add(fn)
}
notify() {
this.reactives.forEach(fn => fn && fn())
}
}
// 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
const weakMap = new WeakMap()
function getDepend(target, key) {
let map = weakMap.get(target)
// 在第一次获取的时候肯定是没有对应的依赖的
if (!map) {
map = new Map()
weakMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 不了解Reflect可以去mdn看看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
// 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
const depend = getDepend(target, key)
depend.addDepend(reFn)
return Reflect.get(target, key)
},
set(target, key, newValue) {
Reflect.set(target, key, newValue)
// 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可
const depend = getDepend(target, key)
depend.notify()
}
})
}
const obj = {
name: '张三',
age: 19
}
const objProxy = reactive(obj)
//定义一个监听对应函数变化的函数, 形参fn接收变化的函数
// const depend = new Depend()
let reFn = null;
function watchFn(fn) {
reFn = fn
fn()
reFn = null
}
//这里不限于使用箭头函数,匿名函数,命名函数...都可以
watchFn(() => {
console.log(`${objProxy.name}被响应了`)
})
watchFn(() => {
console.log(`${objProxy.age}被响应了`)
})
setTimeout(() => {
objProxy.name = "curry"
}, 3000);
const infoProxy = reactive({
address: "金州"
})
watchFn(() => {
console.log(`我的家在${infoProxy.address}`)
})
setTimeout(() => {
infoProxy.address = "勇士"
}, 5000);
Vue2的方式,就是采用Object.defineProperty 其实这个api在设计时并不是为了响应式而生的,所以我们还是应该使用Proxy
// 类具有更好的收集性
class Depend {
constructor() {
//这里使用Set是为了解决在watch函数中多次引用同一个属性会创建多个相同函数的问题,因为Set存储的数据是不重复的
this.reactives = new Set()
}
addDepend(fn) {
this.reactives.add(fn)
}
notify() {
this.reactives.forEach(fn => fn && fn())
}
}
// 获取依赖, 根据第一个参数对象拿到map,再根据key去获取依赖
const weakMap = new WeakMap()
function getDepend(target, key) {
let map = weakMap.get(target)
// 在第一次获取的时候肯定是没有对应的依赖的
if (!map) {
map = new Map()
weakMap.set(target, map)
}
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
function reactive(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get() {
// 在每个响应式函数中获取的obj属性都会先执行get,可以获取对应的依赖
const depend = getDepend(obj, key)
depend.addDepend(reFn)
return value
},
set(newValue) {
value = newValue
// 当我们在修改值时,获取对应的依赖,调用notify 执行对应的函数即可
const depend = getDepend(obj, key)
depend.notify()
}
})
})
return obj
}
const obj1 = {
name: '张三',
age: 19
}
const objProxy = reactive(obj1)
//定义一个监听对应函数变化的函数, 形参fn接收变化的函数
// const depend = new Depend()
let reFn = null;
function watchFn(fn) {
reFn = fn
fn()
reFn = null
}
//这里不限于使用箭头函数,匿名函数,命名函数...都可以
watchFn(() => {
console.log(`${objProxy.name}被响应了`)
})
watchFn(() => {
console.log(`${objProxy.age}被响应了`)
})
setTimeout(() => {
objProxy.name = "curry"
}, 3000);
const infoProxy = reactive({
address: "金州"
})
watchFn(() => {
console.log(`我的家在${infoProxy.address}`)
})
setTimeout(() => {
infoProxy.address = "勇士"
}, 5000);
致辞,实现完毕,有疑问或者建议欢迎下方留言