写在前面
前段时间面试,MVVM原理成为了一道必考题。由于理解不够深,最近详细了解以结构图流程分析原理。
一原型图解
使用MVVM双向绑定
- 定义双向绑定,传入元素,和数据。
var vm = new MVVM({el: '#mvvm-app',data: {someStr: 'hello ',className: 'btn',htmlStr: '<span style="color: #f00;">red</span>',child: {someStr: 'World !'}}
});
复制代码
MVVM类
- 新建劫持数据
- 编译绑定数据
class MVVM {constructor(options) {this.$options = options || {};var data = this._data = this.$options.data;Object.keys(data).forEach(key => {this._proxyData(key);})//数据劫持observe(data, this);//编译this.$compile = new Compile(options.el || document.body, this);}_proxyData(key, setter, getter) {Object.defineProperty(this, key, {configurable: false,enumerable: true,get: function proxyGetter() {return this._data[key];},set: function proxySetter(newVal) {this._data[key] = newVal;}})}
}
复制代码
Compile类
- 将真实DOM移动到虚拟DOM中
- 解析元素中的指令
- 指令新建订阅传入更新函数
class Compile {constructor(el, vm) {this.$vm = vm;this.$el = this.isElementNode(el) ? el : document.querySelector(el);if (this.$el) {//生成文档碎片this.$fragment = this.node2Fragment(this.$el);//编译this.init()//文档碎片加回容器中this.$el.appendChild(this.$fragment);}}node2Fragment(el) {var fragment = document.createDocumentFragment(),child;while (child = el.firstChild) {fragment.appendChild(child); };return fragment;}init() {this.compileElement(this.$fragment);}compileElement(el) { var childNodes = el.childNodes;[].slice.call(childNodes).forEach(node => {var text = node.textContent;var reg = /\{\{(.*)\}\}/;if(this.isElementNode(node)){//指令解析this.compile(node);}else if(this.isTextNode(node) && reg.test(text)){this.compileText(node, RegExp.$1)}if(node.childNodes && node.childNodes.length){this.compileElement(node);}})}compile(node){var nodeAttrs = node.attributes;[].slice.call(nodeAttrs).forEach(attr => {var attrName = attr.name;if(this.isDirective(attrName)){var exp = attr.value;var dir = attrName.substring(2);//事件指令if(this.isEventDirective(dir)){compileUtil.eventHandler(node, this.$vm, exp, dir);}else{compileUtil[dir] && compileUtil[dir](node, this.$vm, exp);}node.removeAttribute(attrName);}})}isDirective(attr){return attr.indexOf('v-') === 0;}isEventDirective(attr){return attr.indexOf('on') === 0;}isElementNode(node) {return node.nodeType == 1;}isTextNode(node) {return node.nodeType == 3;}compileText(node, exp) {compileUtil.text(node, this.$vm, exp);}
}//指令处理集合
var compileUtil = {...
}var updater = {...
}
复制代码
Observer类
- 劫持数据
- 数据变化通知Watcher
//数据劫持
class Observer {constructor(data) {this.data = data;this.walk(data);}walk(data) {Object.keys(data).forEach(key => {this.convert(key, data[key]);})}convert(key, val) {this.defineReactive(this.data, key, val);}//绑定数据,添加发布订阅,核心**defineReactive(data, key, val) {var dep = new Dep();var childObj = observe(val);Object.defineProperty(data, key, {enumerable: true, //可枚举configurable: false, //不能再defineget: function(){if(Dep.target){console.log(Dep.target, 'Dep.target');dep.depend();}return val;},set: function(newVal){ if(newVal === val){return;} val = newVal;// 新的值object的话,进行监听childObj = observe(newVal);console.log(newVal);//通知订阅者dep.notify();}})}
}function observe(value, vm) {if (!value || typeof value !== 'object') {return;}return new Observer(value);
}复制代码
Dep类
- 发布订阅类
var uid = 0;
class Dep {constructor() {this.id == uid++;this.subs = [];}addSub(sub) {this.subs.push(sub);}depend() {Dep.target.addDep(this)}removeSub(sub) {this.subs.remove(sub);}notify() { this.subs.forEach(sub => {sub.update();})}
}Dep.target = null;
复制代码
Watcher类
- 监控数据变化,发布消息,执行订阅函数。
class Watcher{constructor(vm, expOrFn, cb){this.cb = cb;this.vm = vm;this.expOrFn = expOrFn;this.depIds = {};if(typeof expOrFn === 'function') {this.getter = expOrFn;}else{this.getter = this.parseGetter(expOrFn);}this.value = this.get();}update(){this.run();}run(){var value = this.get();var oldVal = this.value;if (value !== oldVal) {this.value = value;this.cb.call(this.vm, value, oldVal);}}get(){Dep.target = this;var value = this.getter.call(this.vm, this.vm);Dep.target = null;return value;}parseGetter(exp){if(/[^\w.$]/.test(exp)) return;var exps = exp.split(',');return function(obj) {for (let i = 0; i < exps.length; i++) {if(!obj) return;obj = obj[exps[i]];}return obj;}}addDep(dep){if (!this.depIds.hasOwnProperty(dep.id)) {dep.addSub(this);this.depIds[dep.id] = dep;}}
}
复制代码