一、数据驱动
- 数据响应式: 数据模型仅仅是 JavaScript 对象,当数据发生改变时,视图就会更新,避免了繁琐的 dom 操作,提高开发效率.
- 双向绑定: 数据改变,视图改变;视图改变,数据改变.
- 数据驱动: 开发过程只需要注重数据本身,不需要关注数据是如何渲染到视图.
二、数据响应式原理
Vue 2.x
- Vue2.x 深入响应式原理
- 自定义 obsever 实现响应式,除了针对普通对象属性进行数据劫持之外,也对值为 object 类型的属性进行了递归拦截. 效果如下:
<!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"> <h1 class="msg">msg: hello world</h1> <h1 class="date"> <span class="start">start: 2020</span> - <span class="end">end: 2021</span> </h1> <h1 class="talk">talk: say...</h1> </div> <script> let data = { msg: "hello world", date: { start: 2020, end: 2021, }, person: { skill: { talk: "say..." } } }; obsever(data); function obsever(data) { // 遍历获取所有的 key Object.keys(data).forEach(key => { let val = data[key]; // 当前值为对象,进行递归 if (Object.prototype.toString.call(val) === "[object Object]") { obsever(val); } else { Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { console.log("getter...", key); }, set(newVal) { if (newVal === val) return; console.log("setter...", key); document .querySelector(`.${key}`) .textContent = `${key}: ${newVal}`; } }); } }); } </script> </body> </html> 复制代码
Vue 3.x
- MDN - Proxy,直接监听对象,而不是属性.
- ES6 新增,不支持 IE,性能由浏览器优化,所以优化方面要比 defineProperty 更好.
- proxy 实现响应式,效果如下:
<!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> let data = { msg: "hello world", }; let proxy = obsever(data); function obsever(data) { return new Proxy(data, { get(target, key, receiver) { console.log("getter...", key, Reflect.get(target, key, receiver)); return target[key]; }, set(target, key, newVal, receiver) { const oldValue = target[key]; if (oldValue == newVal) return true; let result = Reflect.set(target, key, newVal, receiver); console.log('setter...', key, newVal); update(); return result; } }); } update(); function update() { document.querySelector("#app").innerHTML = ` <h1 class="msg">msg: ${proxy.msg}</h1>` } </script> </body> </html> 复制代码
三、发布/订阅模式 & 观察者模式
发布/订阅模式
假定存在一个信号中心,当某个任务完成之后,就向信号中心进行 发布(public) 信号,其他任务可以向信号中心 订阅(subscribe),从而知道自己什么时候开始执行.
- 订阅者
- 发布者
- 信号中心
3.1 Vue 自定义事件
- Vue 自定义事件
- 从下图中并不能很好的区分 订阅者、发布者、信号中心
3.2 兄弟组件通信
- 假设 A 和 B 两个兄弟组件需要进行通信,我们通过 Vue 创建一个 eventHub,通过它来实现发布和订阅.
- A 组件 中存在 addTo 方法实现消息的发布;B 组件 中在 created 中实现对消息的订阅.
- eventHub(信号中心)、ComponentA(发布者)、ComponentB(订阅者)
<script> // 信号中心 let eventHub = new Vue({}); // ComponentA —— 发布者 addTo() { // 发布消息(事件) vm.$emit("dataChange", { data: 1000 }); } // ComponentB —— 订阅者 created() { // 订阅消息(事件) vm.$on("dataChange", (data) => { console.log("dataChange.......",data); }) } </script> 复制代码
3.3 模拟 Vue 自定义事件(发布/订阅)
<script> class EventEmitter { constructor() { this.subs = {}; // 用于存储注册的事件 } // 注册事件(订阅) on(type, handle) { if (this.subs[type]) { // 对应事件已存在 this.subs[type].push(handle); } else { // 对应事件首次注册 this.subs[type] = [handle]; } } // 触发事件(发布) emit(type, params) { if (this.subs[type]) { this.subs[type].forEach(handle => { handle && handle(params); }); } } } let emitter = new EventEmitter(); emitter.on("click", (data) => { console.log("click 1", data); }); emitter.on("click", (data) => { console.log("click 2", data); }); emitter.emit("click", { text: "hello world" }); </script> 复制代码
观察者模式
- 观察者 (订阅者) —— Watcher
- update():当事件发生时,具体要做的事情
- 目标 (发布者) —— Dep
- subs 数组:存储所有的观察者
- addSubs():添加观察者
- notify():当事件发生时,调用所有的观察者的 update() 方法
- 没有事件中心
观察者模式 和 发布/订阅模式 的概念非常的相似,注意不要混淆.
实现一个简单的观察者模式:
<script> /** * 观察者模式 **/ // 发布者——目标 class Dep { constructor() { this.subs = []; // 存储所有观察者 } addSubs(watcher) { // 添加观察者 if (watcher && watcher.update) { this.subs.push(watcher); } } notify(parms) { // 执行所有的观察者 this.subs.forEach(watcher => { if (watcher) { watcher.update(parms); } }); } } // 订阅者——观察者 class Watcher { update(params) { // 需要发布者调用 console.log("update......", params); } } // 测试 let dep = new Dep(); let watcher = new Watcher(); dep.addSubs(watcher); dep.notify({ data: [1, 2, 3, 4, 5, 6] }) </script> 复制代码
发布/订阅模式 和 观察者模式 的区别
- 观察者模式:【目标】 和 【观察者】 是相互依赖的关系
- 发布/订阅模式:【发布者】和【订阅者】之间需要通过【事件中心】进行通信,减少【发布者】和【订阅者】之间的依赖,更灵活