手把手教你实现MVVM架构
.markdown-body p { color: rgba(89, 89, 89, 1); font-size: 15px; line-height: 2; font-weight: 400 }
.markdown-body p+p { margin-top: 16px }
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { padding: 30px 0; margin: 0; color: rgba(19, 92, 224, 1) }
.markdown-body h1 { position: relative; text-align: center; font-size: 22px; margin: 50px 0 }
.markdown-body h1:before { position: absolute; content: ""; top: -10px; left: 50%; width: 32px; height: 32px; transform: translateX(-50%); background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABfVBMVEX///8Ad/8AgP8AgP8AgP8Aff8AgP8Af/8AgP8AVf8Af/8Af/8AgP8AgP8Af/8Afv8AAP8Afv8Afv8Aef8AgP8AdP8Afv8AgP8AgP8Acf8Ae/8AgP8Af/8AgP8Af/8Af/8AfP8Afv8AgP8Af/8Af/8Afv8Afv8AgP8Afv8AgP8Af/8Af/8AgP8AgP8Afv8AgP8Af/8AgP8AgP8AgP8Ae/8Afv8Af/8AgP8Af/8AgP8Af/8Af/8Aff8Af/8Abf8AgP8Af/8AgP8Af/8Af/8Afv8AgP8AgP8Afv8Afv8AgP8Af/8Aff8AgP8Afv8AgP8Aff8AgP8AfP8AgP8Ae/8AgP8Af/8AgP8AgP8AgP8Afv8AgP8AgP8AgP8Afv8AgP8AgP8AgP8AgP8AgP8Af/8AgP8Af/8Af/8Aev8Af/8AgP8Aff8Afv8AgP8AgP8AgP8Af/8AgP8Af/8Af/8AgP8Afv8AgP8AgP8AgP8AgP8Af/8AeP8Af/8Af/8Af//////rzEHnAAAAfXRSTlMAD7CCAivatxIDx5EMrP19AXdLEwgLR+6iCR/M0yLRzyFF7JupSXn8cw6v60Q0QeqzKtgeG237HMne850/6Qeq7QaZ+WdydHtj+OM3qENCMRYl1B3K2U7wnlWE/mhlirjkODa9FN/BF7/iNV/2kASNZpX1Wlf03C4stRGxgUPclqoAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEaBzgZ4yeM3AAAAT9JREFUOMvNUldbwkAQvCAqsSBoABE7asSOBRUVVBQNNuy9996789+9cMFAMHnVebmdm+/bmdtbQv4dOFOW2UjPzgFyLfo6nweKfIMOBYWwFtmMPGz2Yj2pJI0JDq3udJW6VVbmKa9I192VQFV1ktXUAl5NB0cd4KpnORqsEO2ZIRpF9gJfE9Dckqq0KuZt7UAH5+8EPF3spjsRpCeQNO/tA/qDwIDA+OCQbBoKA8NOdjMySgcZGVM6jwcgRuUiSs0nlPFNSrEpJfU0jTLD6llqbvKxei7OzvkFNQohi0vAsj81+MoqsCaoPOQFgus/1LyxichW+hS2JWCHZ7VlF9jb187pIAYcHiViHAMnp5mTjJ8B5xeEXF4B1ze/fTh/C0h398DDI9HB07O8ci+vRBdvdGnfP4gBuM8vw7X/G3wDmFhFZEdxzjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDEtMjZUMDc6NTY6MjUrMDE6MDA67pVWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTAxLTI2VDA3OjU2OjI1KzAxOjAwS7Mt6gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAWdEVYdFRpdGxlAGp1ZWppbl9sb2dvIGNvcHlxapmKAAAAV3pUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHic4/IMCHFWKCjKT8vMSeVSAAMjCy5jCxMjE0uTFAMTIESANMNkAyOzVCDL2NTIxMzEHMQHy4BIoEouAOoXEXTyQjWVAAAAAElFTkSuQmCC"); opacity: 0.36 }
.markdown-body h2 { position: relative; font-size: 20px; border-left: 4px solid; padding: 0 0 0 10px; margin: 30px 0 }
.markdown-body h3 { font-size: 16px }
.markdown-body ul { list-style: disc outside; margin-left: 2em; margin-top: 1em }
.markdown-body li { line-height: 2; color: rgba(89, 89, 89, 1) }
.markdown-body img.loaded { margin: 0 auto; display: block }
.markdown-body blockquote { background: rgba(255, 249, 249, 1); margin: 2em 0; padding: 2px 20px; border-left: 4px solid rgba(178, 174, 197, 1) }
.markdown-body blockquote p { color: rgba(102, 102, 102, 1); line-height: 2 }
.markdown-body a { color: rgba(3, 106, 202, 1); border-bottom: 1px solid rgba(3, 106, 202, 0.8); font-weight: 400; text-decoration: none }
.markdown-body em strong, .markdown-body strong { color: rgba(3, 106, 202, 1) }
.markdown-body hr { border-top: 1px solid rgba(19, 92, 224, 1) }
.markdown-body pre { overflow: auto }
.markdown-body code, .markdown-body pre { overflow: auto; position: relative; line-height: 1.75; font-family: Menlo, Monaco, Consolas, Courier New, monospace }
.markdown-body pre>code { font-size: 12px; padding: 15px 12px; margin: 0; word-break: normal; display: block; overflow-x: auto; color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.markdown-body code { border-radius: 2px; overflow-x: auto; background-color: rgba(255, 245, 245, 1); color: rgba(255, 80, 44, 1); font-size: 0.87em; padding: 0.065em 0.4em }
.markdown-body table { border-collapse: collapse; margin: 1rem 0; overflow-x: auto }
.markdown-body table td, .markdown-body table th { border: 1px solid rgba(223, 226, 229, 1); padding: 0.6em 1em }
.markdown-body table tr { border-top: 1px solid rgba(223, 226, 229, 1) }
.markdown-body table tr:nth-child(2n) { background-color: rgba(246, 248, 250, 1) }.markdown-body pre, .markdown-body pre>code.hljs { color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.hljs-comment, .hljs-quote { color: rgba(153, 153, 136, 1); font-style: italic }
.hljs-keyword, .hljs-selector-tag, .hljs-subst { color: rgba(51, 51, 51, 1); font-weight: 700 }
.hljs-literal, .hljs-number, .hljs-tag .hljs-attr, .hljs-template-variable, .hljs-variable { color: rgba(0, 128, 128, 1) }
.hljs-doctag, .hljs-string { color: rgba(221, 17, 68, 1) }
.hljs-section, .hljs-selector-id, .hljs-title { color: rgba(153, 0, 0, 1); font-weight: 700 }
.hljs-subst { font-weight: 400 }
.hljs-class .hljs-title, .hljs-type { color: rgba(68, 85, 136, 1); font-weight: 700 }
.hljs-attribute, .hljs-name, .hljs-tag { color: rgba(0, 0, 128, 1); font-weight: 400 }
.hljs-link, .hljs-regexp { color: rgba(0, 153, 38, 1) }
.hljs-bullet, .hljs-symbol { color: rgba(153, 0, 115, 1) }
.hljs-built_in, .hljs-builtin-name { color: rgba(0, 134, 179, 1) }
.hljs-meta { color: rgba(153, 153, 153, 1); font-weight: 700 }
.hljs-deletion { background: rgba(255, 221, 221, 1) }
.hljs-addition { background: rgba(221, 255, 221, 1) }
.hljs-emphasis { font-style: italic }
.hljs-strong { font-weight: 700 }
引言
现在的前端真可谓是百花齐放,百家争鸣,各种框架层出不穷,但是主要目前用的最多的还是要数Vue、React、以及Angular,这三种,当然不乏近期新出的一些其他框架,但是她们都有一个显著的特点,那就是使用了MVVM的架构。
首先我们要搞清楚什么是
MVVM?
MVVM就是Model-View-ViewModel的缩写,MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。改变
JavaScript对象的状态,会导致DOM结构作出对应的变化!这让我们的关注点从如何操作DOM变成了如何更新JavaScript对象的状态,而操作JavaScript对象比DOM简单多了。这就是
MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来!
接下来我会带着你们如何去实现一个简易的MVVM架构。
一、构建MVVM构造函数
创建一个MVVM构造函数,用于接收参数,如:data、methods、computed等:
function MVVM(options) {
this.$options = options;
let data = this._data = this.$options.data;
observe(data);
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key];
},
set(newVal) {
this._data[key] = newVal;
}
});
};
initComputed.call(this);
new Compile(options.el, this);
options.mounted.call(this);
}
二、构建Observe构造函数
创建一个Observe构造函数,用于监听数据变化:
function Observe(data) {
let dep = new Dep();
for (let key in data) {
let val = data[key];
observe(val);
Object.defineProperty(data, key, {
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
observe(newVal);
dep.notify();
}
});
};
};
三、构建Compile构造函数
创建一个Compile构造函数,用于解析模板指令:
function Compile(el, vm) {
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment();
while (child = vm.$el.firstChild) {
fragment.appendChild(child);
}
replace(fragment);
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();
new Watcher(vm, RegExp.$1, newVal => {
node.textContent = txt.replace(reg, newVal).trim();
});
}
if (node.nodeType === 1) {
let nodeAttr = node.attributes;
Array.from(nodeAttr).forEach(attr => {
let name = attr.name;
let exp = attr.value;
if (name.includes('v-')) {
node.value = vm[exp];
}
new Watcher(vm, exp, newVal => {
node.value = newVal;
});
node.addEventListener('input', e => {
let newVal = e.target.value;
vm[exp] = newVal;
});
});
};
if (node.childNodes && node.childNodes.length) {
replace(node);
}
});
}
vm.$el.appendChild(fragment);
}
四、构建Watcher构造函数
创建一个Watcher构造函数,用于更新视图:
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;
}
Watcher.prototype.update = function() {
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach(key => {
val = val[key];
});
this.fn(val);
}
五、构建Dep构造函数
创建一个Dep构造函数,用于管理Watcher:
function Dep() {
this.subs = [];
}
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
}
Dep.prototype.notify = function() {
this.subs.forEach(sub => {
sub.update();
});
}
六、构建initComputed构造函数
创建一个initComputed构造函数,用于初始化计算属性:
function initComputed() {
let vm = this;
let computed = this.$options.computed;
Object.keys(computed).forEach(key => {
Object.defineProperty(vm, key, {
get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
set() {}
});
});
}
总结:
至此我们就完成了一个简易的MVVM框架,虽然简易,但是基本的核心思想差不多都已经表达出来了,最后还是希望大家不要丢在收藏文件夹里吃灰,还是要多多动手练习一下,所谓眼过千遍,不如手过一遍。
手把手教你实现MVVM架构的更多相关文章
- Delphi - 手把手教你基于D7+Access常用管理系统架构的设计与实现 (更新中)
前言 从事软件开发工作好多年了,学的越深入越觉得自己无知,所以还是要对知识保持敬畏之心,活到老,学到老! 健身和代码一样都不能少,身体是革命的本钱,特别是我们这种高危工种,所以小伙伴们运动起来!有没有 ...
- 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境
庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...
- 3、手把手教你Extjs5(三)MVVM特性的简单说明
下面我们来看一下自动生成的代码中的MVVM架构的关系.Main是一个可视的控件,MainController是这个控件的控制类,MainModel是这个控件的模型类. 在上面的图片中,左边是Main. ...
- 手把手教你用动软.NET代码生成器实例教程
动软实战攻略 手把手教你用动软 文档编号:20110421 版权所有 © 2004-2011 动软 在线帮助:http://help.maticsoft.com 目录 一. 产品介绍 ...
- [原创]手把手教你写网络爬虫(4):Scrapy入门
手把手教你写网络爬虫(4) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 上期我们理性的分析了为什么要学习Scrapy,理由只有一个,那就是免费,一分钱都不用花! 咦?怎么有人扔西红柿 ...
- [原创]手把手教你写网络爬虫(5):PhantomJS实战
手把手教你写网络爬虫(5) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 大家好!从今天开始,我要与大家一起打造一个属于我们自己的分布式爬虫平台,同时也会对涉及到的技术进行详细介绍.大 ...
- 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 用Python手把手教你搭一个Transformer!
来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...
- 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 手把手教你如何玩转Activiti工作流
手把手教你如何玩转Activiti工作流 置顶 2018年01月30日 19:51:36 Cs_hnu_scw 阅读数:24023 版权声明:本文为博主原创文章,未经博主允许不得转载. https ...
随机推荐
- el-radio-group之迷惑操作:label和label
el-radio-group之迷惑操作:label和label 今天学习element-ui的el-radio-group的时候发现el-radio-group的默认值设置无效,但是点击其他单选框可以 ...
- 如何修改JSONObject 的值
问 题 { "result": { "total": "3", "shops": [ { "shopId&qu ...
- Javascript 方法有多个参数有默认值,但是只想为其中某个参数赋值
例子: function log(a, b = 2, c = 3, d = 4) { console.log(a, b, c, d) } log(1); // output: 1 2 3 4 log( ...
- springboot+vue项目:工具箱
常用账号管理:工作相关账号.游戏账号.各平台账号 加班调休管理:公司没有对应的系统,需要自己记录加班调休情况. 待办事项:方便记录待办,以提醒还有哪些事情没有办理. 待实现功能: 1.点击侧边栏菜单, ...
- Flink学习(五) Flink 的核心语义和架构模型
Flink 的核心语义和架构模型我们在讲解 Flink 程序的编程模型之前,先来了解一下 Flink 中的 Streams.State.Time 等核心概念和基础语义,以及 Flink 提供的不同层级 ...
- 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
图表图片由阿里巴巴提供 本文作者的观点:QwQ-32B 作为小型开源 AI 模型,在数学.编程等任务上表现接近甚至超越 DeepSeek-R1 671B模型,同时计算资源占用大幅降低,使其更易部署和使 ...
- python 读写csv文件(创建,追加,覆盖)
读取csv文件 利用 csv.reader 可以读 csv 文件,然后返回一个可迭代的对象 csv_read,我们可以直接从 csv_read 中取数据 import csv def read_csv ...
- go 密码 hash 加密
目录 bcrypt加密算法原理和应用 简单使用 一起实现一个demo 获取用户输入的密码 Hash & Salt 用户的密码 目前我们做了什么 验证密码 更新 Main 函数 全部代码 bcr ...
- python正则表达式笔记2
由 '\' 和一个字符组成的特殊序列在以下列出. 如果普通字符不是ASCII数位或者ASCII字母,那么正则样式将匹配第二个字符.比如,\$ 匹配字符 '$'. \number匹配数字代表的组合.每个 ...
- Docker管理面板系列——Portainer
一.介绍 Portainer是个轻量级的Docker管理面板,和Rancher这种重量级选手没法比,Portainer倾向于单机的管理(后来发现能够添加多个单机节点切换着管理),当然,在Docker搞 ...