>[success] # Vue 响应式 1. 下面正常的js逻辑代码是否能像`vue`一样,当`price `发生改变期待着`total `也可以重新计算,但实际结果却是打印出来的结果为`10 `而不是`12` 2. 因此可以看出`JavaScript`是程序性的,**不是反应性**的,为了使total反应性,我们必须使用`JavaScript`使事物表现不同 ~~~ let price = 5 let quantity = 2 let total = price * quantity price++ console.log(`total is ${total}`) // 10 ~~~ >[danger] ##### 让数据具备响应收集 1. 想让代码开始变成**反应性**,据需要在想让触发的节点重新运行指定逻辑代码,我们先收集在触发 2. 我们将收集和触发函数命名为`watchFn`,在指定位置触发 ~~~ const reactiveFns = [] function watchFn(fn) { reactiveFns.push(fn) // 收集后进行触发 fn() } // 统一触发所有收集 function replay() { reactiveFns.forEach((run) => run()) } let price = 5 let quantity = 2 let total = 0 watchFn(() => { total = price * quantity }) price++ // 每次执行完都要触发一下 replay() console.log(total) // 可以得到触发后值 ~~~ >[danger] ##### 收集更多数据怎么办 1. 在实际开发中,会有更多数据需要被收集触发,如果只是单纯的使用数组收集差生问题,下面案例产生问题**触发将不希望改变的name 也被触发** ~~~ const reactiveFns = [] function watchFn(fn) { reactiveFns.push(fn) // 收集后进行触发 fn() } // 统一触发所有收集 function replay() { reactiveFns.forEach((run) => run()) } let name = 'w' // 收集名字变化 watchFn(() => { name += '1' }) let price = 5 let quantity = 2 let total = 0 watchFn(() => { total = price * quantity }) price++ // 每次执行完都要触发一下,这次触发将不希望改变的name 也被触发 replay() console.log(total) // 可以得到触发后值 ~~~ 2. 为每一个对象创建一个属于自己的收集对象这样,就不会相互忽然,创建一个 `Depend` 类,用来收集各自的响应触发 3. 让数据可以更加自由的触发对象的变化可以使用两种方案`Object.defineProperty`或`new Proxy`,代码思路是我们将每个需要响应式的数据定义属于自己的收集,触发时候触发属于自己的收集响应的方法 ~~~ // 属于每个对象自己的收集 class Depend { constructor() { this.reactiveFns = [] } addDepend(fn) { if (fn) { this.reactiveFns.push(fn) } } notify() { this.reactiveFns.forEach((fn) => { fn() }) } } // 设置一个专门执行响应式函数的一个函数 function watchFn(fn, dep) { dep.addDepend(fn) fn() } const info = { name: 'w', age: 18 } const bookInfo = { name: '语文', price: 125 } const InfoDep = new Depend() const bookDep = new Depend() // 使用Object.defineProperty 收集 function proxy(obj, dep) { Object.keys(obj).forEach((key) => { let value = obj[key] Object.defineProperty(obj, key, { set: function (newValue) { value = newValue // 进行设置值的时候进行触发收集 dep.notify() }, get: function () { return value }, }) }) } watchFn(() => { console.log(`info 信息收集`) }, InfoDep) proxy(info, InfoDep) watchFn(() => { console.log(`bookInfo 信息收集`) }, bookDep) proxy(bookInfo, bookDep) console.log('---------------') info.age++ bookInfo.price++ ~~~ >[danger] ##### 自定义收集依赖 1. 对象会有多个属性,不同属性可能需要的是不同的收集响应,因此需要一个新的收集数据解构 `{对象:{属性:Depend},对象:{属性:Depend]}}`对象能做为`key`需要使用`WeakMap` 2. `getDepend` 作为一个自动收集和使用响应的方法,主要是将数据可以存储在`WeakMap` 解构,并将每一个属性`key `都可以对应 属于自己`Depend` 3. `Object.defineProperty` 进行收集当触发`get` 时候进行收集,`set` 时候进行触发 4. `watchFn` 用来触发第一次响应,和给响应事件赋值,这里将`target ` 作为全局变量,当触发`watchFn` 收集的时候必须要触发一次收集对象对应属性的` get`,此时触发对应属性`get`后并且将对应属性响应方法收集(因为是全局定义所以方便收集),触发后`watchFn` 再将`target `置为`null` 等待下一次触发 ~~~ // 属于每个对象自己的收集 class Depend { constructor() { this.reactiveFns = [] } addDepend(fn) { if (fn) { this.reactiveFns.push(fn) } } notify() { this.reactiveFns.forEach((fn) => { fn() }) } } // 收集响应式数据解构 const objMap = new WeakMap() // 定义一个收集方法 function getDepend(obj, key) { // 判断响应式中是否收集了 对象所对应key let map = objMap.get(obj) if (!map) { // 不存在 就创建一个{对象: Map} map = new Map() objMap.set(obj, map) } // 判断对象key 是否已经创建响应收集 const getDep = map.get(key) if (!getDep) { const dep = new Depend() // 收集key依赖 和dep map.set(key, dep) } // 将对应key 收集的依赖返回 return map.get(key) } let target = null // 设置一个专门执行响应式函数的一个函数 function watchFn(fn) { target = fn fn() target = null } function proxy(obj) { Object.keys(obj).forEach((key) => { let value = obj[key] Object.defineProperty(obj, key, { get() { // 触发get 时候要收集响应依赖 const dep = getDepend(obj, key) // 将依赖收集 dep.addDepend(target) return value }, set(nvalue) { // 触发依赖 value = nvalue const dep = getDepend(obj, key) dep.notify() }, }) }) } const info = { name: 'w', age: 18 } proxy(info) watchFn(function foo() { console.log('foo function') console.log('foo:', info.name) console.log('foo', info.age) }) watchFn(function bar() { console.log('bar function') console.log('bar:', info.age + 10) }) console.log('age发生变化-----------------------') info.age = 20 ~~~ * 如果我们将`proxy` 定义的名字改为`reactive`,将里面的`Object.defineProperty` 换成 `new Proxy` 就是vue3的一个模型 ~~~ // 封装一个函数: 负责通过obj的key获取对应的Depend对象 const objMap = new WeakMap() function getDepend(obj, key) { // 1.根据对象obj, 找到对应的map对象 let map = objMap.get(obj) if (!map) { map = new Map() objMap.set(obj, map) } // 2.根据key, 找到对应的depend对象 let dep = map.get(key) if (!dep) { dep = new Depend() map.set(key, dep) } return dep } function reactive(obj) { const objProxy = new Proxy(obj, { set: function(target, key, newValue, receiver) { // target[key] = newValue Reflect.set(target, key, newValue, receiver) const dep = getDepend(target, key) dep.notify() }, get: function(target, key, receiver) { const dep = getDepend(target, key) dep.depend() return Reflect.get(target, key, receiver) } }) return objProxy } // ========================= 业务代码 ======================== const obj = reactive({ age: 18, address: "广州市" }) watchFn(function() { console.log(obj.name) console.log(obj.age) console.log(obj.age) }) // 修改name console.log("--------------") obj.age = 20 console.log("=============== user =================") const user = reactive({ nickname: "abc", level: 100 }) watchFn(function() { console.log("nickname:", user.nickname) console.log("level:", user.level) }) user.nickname = "cba" ~~~