前言
在上一篇《Vue2.0 如何实现数据响应式》中我们知道,Vue2.0 是通过 ES5 Object.defineProperty
来实现响应式的。但是也正是因为使用了这个方法,所以在 Vue2.0 的数据响应式中存在着缺陷:
- Vue 实例化后新增的属性,不会被监听。但可以使用
Vue.set(data, 'a', 1)
设置新属性。 Object.defineProperty
的一个缺陷是无法监听数组变化(数组的7个改变原数组本身能被监听)。同样可以使用Vue.set
来设置数组项。
这一篇,我们将来看一下新的 Vue3.0 是如何利用 ES6 Proxy 来重写实现的。
实践
在开始之前,先把 vue-next 源码克隆到本地,执行 npm run dev
,生成 vue 包。
在 dist 目录下新建一个 index.html,尝试写一下 3.0 语法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./vue.global.js"></script>
<script>
const App = {
// created 只会执行一次
// Vue.reactive() 返回一个 Proxy
setup () {
let state = Vue.reactive({
name: 'zhuojiawei'
})
// 更新数据
function changeName () {
state.name = 'allen'
}
// data
return {
state,
changeName
}
},
template: `<div @click="changeName">{{state.name}}</div>`
}
// 创建应用
// App 对象
// 父容器
Vue.createApp().mount(App, container)
</script>
</body>
</html
我们打印一下 state,看到 state 返回的是一个 Proxy 。
这里,就引出了 Vue3.0 的实现数据响应式的核心就是 ES6 Proxy(代理),Proxy 的具体用法可以参考 Proxy。
**
响应式代码实现
基础版(对象只有一层)
// vue2.0 缺点:
// 1. 默认递归,性能问题
// 2. 数组改变长度无效
// 3. 对象不存在的属性,不会被拦截
// ***************************************************
// vue3.0 响应式原理
// 判断是不是对象
function isObject (val) {
return typeof val === 'object' && val !== null
}
// 响应式的核心方法
function reactive (target) {
// 创建响应式对象
return createReactiveObject(target)
}
// 创建响应式对象
function createReactiveObject (target) {
if (!isObject(target)) { // 如果当前不是对象,直接返回
return target
}
let baseHandler = {
// Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
get (target, key, receiver){
console.log('获取')
let result = Reflect.get(target, key, receiver)
return result
},
set (target, key, value, receiver) {
console.log('设置')
let result = Reflect.set(target, key, value, receiver)
return result
},
deleteProperty (target, key) {
console.log('删除')
let result = Reflect.deleteProperty(target, key)
return result
}
}
let observed = new Proxy(target, baseHandler) // es6
return observed
}
let proxy = reactive({
name: 'zhoujiawei'
})
// 获取
console.log(proxy.name)
// 设置
proxy.name = 'allen'
console.log(proxy.name)
// 删除
delete proxy.name
console.log(proxy.name)
对象多层嵌套
// 弱映射表
// 用来屏蔽多次 new 以及多层代理
let toProxy = new WeakMap() // es6 放置的是 原对象:代理过的对象
let toRaw = new WeakMap() // 被代理过的对象:原对象
// 判断是不是对象
function isObject (val) {
return typeof val === 'object' && val !== null
}
// 判断是否含有属性
function hasOwn (target, key) {
return target.hasOwnProperty(key)
}
// 响应式的核心方法
function reactive (target) {
// 创建响应式对象
return createReactiveObject(target)
}
// 创建响应式对象
function createReactiveObject (target) {
if (!isObject(target)) { // 如果当前不是对象,直接返回
return target
}
// 如果代理过了 就将代理过的结果返回
let proxy = toProxy.get(target)
if (proxy) {
return proxy
}
// 防止代理过的对象,再次代理
if (toRaw.has(target)) {
return target
}
let baseHandler = {
// Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
get (target, key, receiver){
console.log('获取')
// Reflect 反射
let result = Reflect.get(target, key, receiver)
// result 是当前获取到的值
// **********************************
return isObject(result) ? reactive(result) : result // 有选择的递归
// **********************************
},
set (target, key, value, receiver) {
// 识别是改属性 还是新增属性
let hadKey = hasOwn(target, key)
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (!hadKey) {
console.log('新增属性')
} else if (oldValue !== value) {
console.log('修改属性')
}
console.log('设置')
return result
},
deleteProperty (target, key) {
console.log('删除')
let result = Reflect.deleteProperty(target, key)
return result
}
}
let observed = new Proxy(target, baseHandler) // es6
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
let proxy = reactive([1, 2, 3])
proxy.push(4)
effect 副作用
effect 副作用方法:默认先执行一次,当依赖数据变化的时候会再次执行,effect 也是响应式的核心。(watch 也是调用 effect )
// 弱映射表
// 用来屏蔽多次 new 以及多层代理
let toProxy = new WeakMap() // es6 放置的是 原对象:代理过的对象
let toRaw = new WeakMap() // 被代理过的对象:原对象
// 判断是不是对象
function isObject (val) {
return typeof val === 'object' && val !== null
}
// 判断是否含有属性
function hasOwn (target, key) {
return target.hasOwnProperty(key)
}
// 响应式的核心方法
function reactive (target) {
// 创建响应式对象
return createReactiveObject(target)
}
// 创建响应式对象
function createReactiveObject (target) {
if (!isObject(target)) { // 如果当前不是对象,直接返回
return target
}
// 如果代理过了 就将代理过的结果返回
let proxy = toProxy.get(target)
if (proxy) {
return proxy
}
// 防止代理过的对象,再次代理
if (toRaw.has(target)) {
return target
}
let baseHandler = {
// Reflect 优点不会报错,会有返回值 会替代掉 Object 上的方法
get (target, key, receiver){
// Reflect 反射
let result = Reflect.get(target, key, receiver)
// result 是当前获取到的值
// 收集依赖 订阅 把当前的 key 和这个 effect 对应起来
track(target, key) // 如果目标上这个 key 变化了 重新让数组中的 effect 执行
return isObject(result) ? reactive(result) : result // 有选择的递归
},
set (target, key, value, receiver) {
// 识别是改属性 还是新增属性
let hadKey = hasOwn(target, key)
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (!hadKey) {
trigger(target, 'add', key)
} else if (oldValue !== value) {
trigger(target, 'set', key)
}
return result
},
deleteProperty (target, key) {
let result = Reflect.deleteProperty(target, key)
return result
}
}
let observed = new Proxy(target, baseHandler) // es6
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
// 依赖收集(发布订阅)
// effect 副作用方法:默认先执行一次,当依赖数据变化的时候会再次执行,effect 也是响应式的核心
let activeEffectStacks = [] // 栈型结果
let targetsMap = new WeakMap()
function track (target, key) { // 如果目标上这个 key 变化了 重新让数组中的 effect 执行
let effect = activeEffectStacks[activeEffectStacks.length - 1]
if (effect) { // 有对应关系 创建关联
// 动态创建依赖关系
let depsMap = targetsMap.get(target)
if (!depsMap) {
targetsMap.set(target, depsMap = new Map)
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, deps = new Set())
}
if (!deps.has(effect)) {
deps.add(effect)
}
}
}
function trigger (target, type, key) {
let depsMap = targetsMap.get(target)
if (depsMap) {
let deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => {
effect()
})
}
}
}
function effect (fn) {
// 需要把 fn 这个函数变成响应式的函数
let effect = createReactiveEffect(fn)
effect()
}
function createReactiveEffect (fn) {
let effect = function () {
return run(effect, fn) // 让 fn 执行,把这个 effect 存到栈中
}
return effect
}
function run (effect, fn) { // 运行 fn,并且将 effect 存起来
try {
activeEffectStacks.push(effect)
fn() // js 单线程
} finally {
activeEffectStacks.pop()
}
}
let proxy = reactive({
name: 'zhoujiawei'
})
effect(() => {
console.log(proxy.name)
})
proxy.name = 'allen'