前言
在上一篇《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() 返回一个 Proxysetup () {let state = Vue.reactive({name: 'zhuojiawei'})// 更新数据function changeName () {state.name = 'allen'}// datareturn {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) // es6return observed}let proxy = reactive({name: 'zhoujiawei'})// 获取console.log(proxy.name)// 设置proxy.name = 'allen'console.log(proxy.name)// 删除delete proxy.nameconsole.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) // es6toProxy.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) // es6toProxy.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'

