响应式数据/双向绑定原理
vue 的响应式数据/双向绑定主要是指:数据变化更新视图,视图变化更新数据。 将 data 中的 js 对象做遍历,利用 Object.defineProperty 将 data 中的数据全部转换成 getter/setter,每个组件都对应一个 Watcher 实例,收集数据中的依赖,当数据发生改变触发 setter 时,会通知对应的组件,组件通过虚拟 DOMdiff 算法再做改变,如官网的流程图所示:
简单的双向绑定代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <body> <div id="app"> <input type="text" id="txt" /> <p id="show"></p> </div> </body> <script type="text/javascript"> var obj = {}; Object.defineProperty(obj, "txt", { get: function () { return obj; }, set: function (newValue) { document.getElementById("txt").value = newValue; document.getElementById("show").innerHTML = newValue; } }); document.addEventListener("keyup", function (e) { obj.txt = e.target.value; }); </script>
|
监听器 observer
对 data 里面的数据对象做遍历(子对象存在时做递归遍历),利用 Object.defineProperty
将 data 中的数据全部转换成 getter/setter
,当某个属性和值发生改变时就能触发 setter,就会监听到数据的变化
模板解析器 Compile
模板解析器主要通过遍历模板,查看都使用了哪些变量、指令,为数据添加订阅者 Watcher,一旦数据发生了改变,调用更新函数更新。
订阅者 Watcher
Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式
订阅器 Dep
订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
实现一个通用的发布订阅模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class MyEvent { handlers = {};
$on(type, fn) { if (!Reflect.has(this.handlers, type)) { this.handlers[type] = []; } this.handlers[type].push(fn); }
$emit(type, ...params) { if (!Reflect.has(this.handlers, type)) { throw new Error(`未注册该事件${type}`); } this.handlers[type].forEach(fn => { fn(...params); }); }
$remove(type, fn) { if (!Reflect.has(this.handlers, type)) { throw new Error(`无效事件${type}`); } if (!fn) { return Reflect.deleteProperty(this.handlers, type); } else { const inx = this.handlers[type].findIndex(handler => handler === fn); if (inx === -1) { throw new Error("无效事件"); } this.handlers[type].splice(inx, 1); if (!this.handlers[type].length) { return Reflect.deleteProperty(this.handlers, type); } } } }
|