内容简介:前段时间面试,MVVM原理成为了一道必考题。由于理解不够深,最近详细了解以结构图流程分析原理。
前段时间面试,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, //不能再define get: 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; } } } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。