一、Vue2的响应式
Vue2响应式原理
1、Vue2使用的是ES5的Object.defineProperty进行数据劫持的
2、通过回调函数中的get和set进行 获取与修改
3、所有在data中声明的属性都会添加到get和set中
4、如果要读取某个数据 那么就会自动调用get方法
5、如果某个数据被修改了 那么就会自动调用set方法
6、数据如果发生改变 会自动通知watcher重新render组件(子组件不会render)
7、重新render组件后 会生成新的虚拟dom树 并会进行新旧对比 同时采用就地复用的策略 and diff算法 生成最新的节点 最新的dom元素
* 问题:
* Vue2的响应式数据 对新增、删除等属性无能为力
* 在Vue2中 存在一些问题 比如对象只能对已定义好的key响应式 数组不能通过部分js语法实现响应式数据的更改 比如通过下标改变某个值 这个值并不会更新视图
* 在以往版本中 Vue2也提供了解决方案 Vue.set可以处理这种问题 Vue.set提供三个参数 可以将值追踪成响应式数据
实现响应式代码
const obj = {
name: '悟空',
age: 24
}
Object.keys(obj).forEach((item) => {
let val = obj[item]
Object.defineProperties(obj, item, {
get() {
console.log('在获取我的数据', item)
},
set(newVal) {
// newVal 最新的数据 val 上波数据
console.log('在修改我的数据', val, newVal)
val = newVal
}
})
})
console.log(obj.name) // 获取数据 触发get方法
obj.name = '大魔王' // 修改数据 触发set方法 将数据更改
二、Vue3的响应式原理
Vue3使用的是ES6的Proxy进行数据劫持从而进行某些操作
并且不会有Vue2版本中响应式遗留的问题 也是通过回调函数中的get与set进行操作数据的
介绍ES6中的Proxy
/**
* 介绍ES6中的Proxy
* new Proxy()
* new Proxy这个词的英文解释是 "代理" , 可以理解成在目标对象来之前 设置了一层拦截 并且外界要对此目标对象进行访问的话 也需要通过走这层拦截
* 因此可以在其中对数据进行获取和改写
*
* let proxy = new Proxy(target, handler) // 由两个参数组成
* target:目标对象, handler:是一个对象,内含多个方法 也就是拦截操作 例如 get、set
* 那么写法就是
* return Proxy(obj,{
* get(target,key,receiver){
return target[key]
* },
* set(target,key,value,receiver){
* target[key] = value
* }
* })
*
* get存在三个参数 1、target(目标对象,侦听的对象) 2、key(被获取的属性key) 3、receiver(proxy实例本身)
* set存在四个参数 2、target(目标对象,侦听的对象) 2、key(被获取的属性key) 3、value(新属性值) 4、receiver(proxy实例本身)
*/
Proxy源码
let obj2 = {
name: '悟空1',
age: 25
}
let objProxy = new Proxy(obj2, {
get(target, key, receiver) {
console.log(target, key, receiver)
},
set(target, key, value, receiver) {
console.log(target, key, value, receiver)
// target:{ name: '悟空1', age: 25 }
// key:name
// value:大魔王1
// receiver:{ name: '悟空1', age: 25 }
target[key] = value // 将原值更新为新值
}
})
objProxy.name = '大魔王1' // 触发set
console.log(name) // 触发get
那么如何实现Vue3的reactive源码 Vue3的响应式就是依赖了 Proxy 这个核心API
export const reactive = (obj) => {
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
return res
}
})
}
注意:仅此不够 仅此只是实现了对数据的控制,我们还要做对视图的更新 我们需要副作用函数去操作dom
三、副作用函数
// 实现effect 副作用函数
let activeEffect
const effect = (fn) => {
const _effect = function () {
activeEffect = _effect
fn()
}
_effect()
}
// 实现track
const targetMap = new WeakMap()
const track = (target, key) => {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
// 实现trigger
const trigger = (target, key) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach((effect) => effect())
}
四、demo测试Vue3的响应式,实现数据同步更新视图 vivwmodel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module">
// 1、定义好reactive 依赖Proxy
const reactive = (obj) => {
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
}
})
}
// 2、实现effect 副作用函数
let activeEffect
const effect = (fn) => {
const _effect = function () {
activeEffect = _effect
fn()
}
_effect()
}
// 3、实现track
const targetMap = new WeakMap()
const track = (target, key) => {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
// 4、实现trigger
const trigger = (target, key) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach((effect) => effect())
}
const user = reactive({
name: '悟空',
age: 18
})
effect(() => {
document.querySelector('#app').innerText = `${user.name} - ${user.age}`
})
setTimeout(() => {
user.name = '悟空大魔王'
setTimeout(() => {
user.age = '24'
}, 1000)
}, 2000)
</script>
</body>
</html>