本文最后更新于 2025年8月4日 晚上
参考:https://juejin.cn/post/6844903586103558158 。
现在无论是 React 还是 Vue 还是最新的 Angular,MVVM 双向数据绑定通过 数据劫持+发布订阅模式 完成。真正实现其实靠的是 ES5 中提供的 Object.defineProperty() 方法。
Object.defineProperty()用法
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
| let obj = {}; let song = "发如雪"; obj.singer = "周杰伦";
Object.defineProperty(obj, "music", { value: "七里香", configurable: true, writable: true, enumerable: true, set(val) { song = val; }, get() { return song; }, });
console.log(obj);
delete obj.music; console.log(obj);
obj.music = "听妈妈的话"; console.log(obj);
for (let key in obj) { console.log(key); }
console.log(obj.music);
obj.music = "夜曲"; console.log(obj.music);
|
打造 MVVM
以 Vue 为参照去实现 MVVM。
1 2 3 4 5 6 7 8 9
| function Mvvm(options = {}) { this.$options = options; let data = (this._data = this.$options.data); Observe(data); }
|
数据劫持
为什么要做数据劫持?
- 观察对象,给对象增加 Object.defineProperty()
- vue 特点是不能新增不存在的属性 不存在的属性没有 get 和 set
- 深度响应 因为每次赋予一个新对象时会给这个新对象增加 Object.defineProperty()
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
|
function Observe(data) { for (let key in data) { let val = data[key]; observe(val); Object.defineProperty(data, key, { configurable: true, get() { return val; }, set(newVal) { if (val === newVal) return; val = newVal; observe(newVal); }, }); } }
function observe(data) { if (!data || typeof data !== "object") return; return new Observe(data); }
|
数据代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Mvvm(options = {}) { Observe(data); for (let key in data) { Object.defineProperty(this, key, { configurable: true, get() { return this._data[key]; }, set(newVal) { this._data[key] = newVal; }, }); } }
|
数据编译
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
| function Mvvm(options = {}) { observe(data); new Compile(options.el, this); }
function Compile(el, vm) { vm.$el = document.querySelector(el); let fragment = document.createDocumentFragment(); while ((child = vm.$el.firstChild)) { fragment.appendChild(child); } function replace(frag) { Array.from(frag.childNodes).forEach((node) => { let txt = node.textContent; let reg = /\{\{(.*?)\}\}/g; if (node.nodeType === 3 && reg.test(txt)) { console.log(RegExp.$1); let arr = RegExp.$1.split("."); let val = vm; arr.forEach((key) => { val = val[key]; }); node.textContent = txt.replace(reg, val).trim(); } if (node.childNodes && node.childNodes.length) { replace(node); } }); } replace(fragment); vm.$el.appendChild(fragment); }
|
发布订阅
数据更新视图
双向数据绑定