对于MVVM的实现原理之前其实就知道了,但是一直没有自己去尝试实现过,今个在这里实现一下,也加深下印象
参考文章:MVVM原理
入口函数
1 2 3 4 5 6 7 8 9 10
| function Mvvm(options = {}){ this.$options = options; let data = this._data = this.$options.data;
observe(data); }
|
数据劫持
- 观察对象,遍历属性调用Object.defineProperty
- vue 不支持对不存在的属性进行get/set
- 深度响应,赋予新对象时会给这个新对象增加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
| 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(newVal === val) return; val = newVal; observe(newVal); } }) } }
function observe(data){ if(!data || typeof data !== 'object') return; return new Observe(data); }
|
数据代理
数据代理就是不需要我们写成mvvm._data.a.b
这种形式,而是mvvm.a.b
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 = 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)){ 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); }
|
发布订阅
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 Dep() { this.subs = []; } Dep.prototype = { addSub(sub) { this.subs.push(sub); }, notify() { this.subs.forEach(sub => sub.update()); } };
function Watcher(fn) { this.fn = fn; } Watcher.prototype.update = function() { this.fn(); };
let watcher = new Watcher(() => console.log(111)); let dep = new Dep(); dep.addSub(watcher); dep.addSub(watcher); dep.notify();
|
数据更新视图
- 订阅一个事件,当数据改变需要重新刷新视图,这就需要在replace替换的逻辑里来处理
- 通过new 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
| function replace(frag){ node.text.Content = txt.replace(reg, val).trim();
new Watcher(vm, RegExp, $1, newVal => { node.textContent = txt.replace(reg, newVal).trim(); }) }
function Watcher(vm, exp, fn){ this.fn = fn; this.vm = vm; this.exp = exp; Dep.target = this; let arr = exp.split('.'); let val = vm; arr.forEach(key => { val = val[key]; }); Dep.target = null; }
|
由于数据劫持的原因,当获取值的时候就会自动调用get方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Observe(data) { let dep = new Dep(); Object.defineProperty(data, key, { get() { Dep.target && dep.addSub(Dep.target); return val; }, set(newVal) { if (val === newVal) { return; } val = newVal; observe(newVal); dep.notify(); } }) }
|
当set修改值的时候执行了dep.notify方法,这个方法是执行watcher的update方法,需要对update进行修改
1 2 3 4 5 6 7 8 9 10
| Watcher.prototype.update = function() { let arr = this.exp.split('.'); let val = this.vm; arr.forEach(key => { val = val[key]; }); this.fn(val); };
|
mounted函数
mounted函数实际就是在所有响应式数据处理完,即compile函数执行之后执行的
computed函数
在compile函数执行前调用,会从options属性里获取computed对象,之后遍历key值设置响应式。如果当前key对应的value是对象,则需要手动调用get方法,如果是函数则不需要手动调用