记一次数据、逻辑、视图分离的原生JS项目实践
一切的开始源于这篇文章:一句话理解Vue核心内容。
在文章中,作者给出了这样一个思考:
假设现在有一个这样的需求,有一张图片,在被点击时,可以记录下被点击的次数。这看起来很简单吧, 按照上面提到到开发方式,应该很快就可以搞定。那么接下来,需求稍微发生了点变动, 要求有两张图片,分别被点击时,可以记录下各自的点击次数。这次似乎也很简单,只需把原先的代码复制粘贴一份就可以了。那么当这个需求变成五张图片时,你会怎么做? 还是简单复制粘贴吧,这样完全可以完成这个需求,但是你会觉得很别扭,因为你的代码此时变得很臃肿,存在很多重复的过程,但是似乎还在你的忍受范围内。这时候需求又发生了微小的变动,还是五张照片分别记录被点击次数,不过这样单独罗列五张图片似乎太占空间,现在只需要存在一个图片的位置,通过选择按钮来切换被点击的图片。 这时候你可能会奔溃掉,因为要完成这个看似微小的改动,你原先写的大部分代码可能都需要被删掉,甚至是完全清空掉,从零开始写起。
也许你应该像我一样,从一张图片到五张图片完成上面的需求。相信我,这个过程很有趣。因为每增加一次需求,你或多或少都会需要重构你的代码。特别是如果你直接从一张跳到五张的话,那么你就需要完全重构你的代码。
二话不说,先看整个项目的效果。这里我直接放了五张图片实现的效果。
说实话,这其实是一个非常简单的demo,只要对JS的知识稍微熟悉一点,并且在写代码时注意一下闭包的问题,就可以轻松的实现效果。在没学vue之前,我们一定是这样写代码的。
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>fore</li>
<li>five</li>
</ul>
<div class="container">
<img class="pic" src='http://www.jqhtml.com/wp-content/themes/sc/images/logo.png'>
<img class="pic" src='http://www.jqhtml.com/wp-content/themes/sc/images/logo.png'>
<img class="pic" src='http://www.jqhtml.com/wp-content/themes/sc/images/logo.png'>
<img class="pic" src='http://www.jqhtml.com/wp-content/themes/sc/images/logo.png'>
<img class="pic" src='http://www.jqhtml.com/wp-content/themes/sc/images/logo.png'>
<p class='num'></p>
<p class='num'></p>
<p class='num'></p>
<p class='num'></p>
<p class='num'></p>
</div>
<script>
var img = document.getElementsByTagName('img');
var num = document.getElementsByTagName('p');
var li = document.getElementsByTagName('li');
for (let i = 0; i < 5; i++) {
li[i].onclick = (function(index) {//形成闭包
return (function(e) {
for (let j = 0; j < 5; j++) {
//console.log(num);
num[j].removeAttribute('class');
img[j].removeAttribute('class');
}
num[index].setAttribute('class','show');
img[index].setAttribute('class','show');
})
})(i)
img[i].onclick = counter(num[i]);
} //计数器函数
function counter(ele) {
var num = 0,//点击的次数
node = ele;
return function(e) {//形成闭包让每个元素都有自己私有num变量
node.innerHTML = ++num; }
}
</script>
这种直接操作DOM来改变视图的开发方式似乎并不能hold住复杂的逻辑和代码量,况且在这个例子中逻辑并非很复杂。这也证明了由JS来直接操作DOM以改变视图的开发方式并不适合如今的前端开发。这也是前端开发为什么需要类似vue这样的框架。
如果你学过vue,你会发现完成这个需求,只需要改一下data对象里的图片数就轻松的实现了需求。(用vue实现上面的需求更加简单,只需要几行代码就可以实现,并且可扩展性也好,感兴趣的同学可以用vue实现一下上面的需求)
我们可以明显的感觉到vue这种数据和视图分离的代码组织方式更加的容易实现扩展,并且代码可读性更强。而我们上面的原生JS 的实现方式将数据和视图都混在一起了,当项目需求越来越复杂的时候会让代码越臃肿,且越不易于扩展。
其实数据和视图分离并不是框架的专利,要知道框架也是由原生的JS实现的。因此原生JS也可以写出数据和视图分离的代码,让项目变得更加易于扩展。
下面我们就按照数据、视图、逻辑分离的思路来重构一下我们这个项目的代码。项目源码链接
首先,我们把数据给抽离,可以看到视图的样子大概是这样的一个形式。
<body>
<ul id="cat-list">
//列表
</ul> <section id="cat">//猫图片的显示区域
<h2 id="cat-name"></h2>
<div id="cat-count"></div>
<img src="" alt="" id="cat-img">
</section>
</body>
我们将数据存储在一个名为model的对象中。
var model = {
currentCat: null,
cats: [ //猫的图片数据
{
clickCount : 0,
name : 'Tabby',
imgSrc : 'img/434164568_fea0ad4013_z.jpg',
},
//省略余下的图片数据
]
}
在初始化页面的时候,我们要加载数据,渲染页面。
var catView = { //图片区域的视图
init: function() {
//储存DOM元素,方便后续操作
this.cat = document.getElementById('cat');
this.catName = document.getElementById('cat-name');
this.catCount = document.getElementById('cat-count');
this.catImg = document.getElementById('cat-img');
this.cat.addEventListener('click',function() {//给每张图片添加点击事件
controler.addCount();
},false);
this.render();
}, render: function() {
let currentCat = controler.getCurrentCat();
this.catName.textContent = currentCat.name;
this.catCount.textContent = currentCat.clickCount;
this.catImg.src = '../' + currentCat.imgSrc;
}
} var listView = { //列表区域的视图
init: function() {
this.catList = document.getElementById('cat-list');
this.render();
}, render: function() {
let cats = controler.getCats();
let fragment = document.createDocumentFragment('ul');
cats.forEach((item,index) => {
let li = document.createElement('li');
li.textContent = item.name;
li.setAttribute('class','item');
li.addEventListener('click',function() {//给li添加点击事件
controler.setCurrentCat(item);
catView.render();
})
fragment.appendChild(li);
})
this.catList.appendChild(fragment);
fragment = null;
}
}
从上面的视图对象可以知道,视图并不直接从model中获取数据,而是通过一个中间对象controler来间接访问model,也就是说controler对象实现了所有的视图和数据间的逻辑操作。
var controler = {
init: function() {
model.currentCat = model.cats[0];
catView.init();
listView.init();
},
//获取全部的猫
getCats: function() {
return model.cats;
},
//获取当前显示的猫
getCurrentCat: function() {
return model.currentCat;
}, //设置当前被点击的猫
setCurrentCat: function(cat) {
return model.currentCat = cat;
}, addCount: function() {
model.currentCat.clickCount++;
catView.render();
}
}
到这里,我们用数据、视图、逻辑分离的代码组织方式重构了一个小型的项目,从该项目中可以清楚的看到:数据model只负责存储数据,而视图view只负责页面的渲染,而controler负责view和model之间的交互逻辑的实现。
等一下,既然说交互逻辑是放在controler中实现的,而视图只负责渲染页面,那为什么click点击事件会放在视图层呢?
这里要明确一下的就是(仅个人理解):视图并不是侠义上的静态页面,视图指的是静态页面和动态入口(用户交互,如点击事件),所以事件的绑定放在view层是完全可以理解的,view层实现了一个动态的入口,而用户点击后的所有逻辑操作都是在controler层实现的。
记一次数据、逻辑、视图分离的原生JS项目实践的更多相关文章
- ArcGIS API for JavaScript 入门教程[3] 你看得到:数据与视图分离
这篇开始正式讲API. 数据和视图分离不是什么奇怪的事情了,这是一个著名的设计--数据与视图分开. 转载注明出处,博客园/CSDN/B站:秋意正寒. 目录:https://www.cnblogs.co ...
- Javascript模板及其中的数据逻辑分离思想(MVC)
#Javascript模板及其中的数据逻辑分离思想 ##需求描述 项目数据库的题目表描述了70-120道题目,并且是会变化的,要根据数据库中的数据描述,比如,选择还是填空题,是不是重点题,题目总分是多 ...
- ASP.NET MVC 5 学习教程:控制器传递数据给视图
原文 ASP.NET MVC 5 学习教程:控制器传递数据给视图 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字 ...
- Jmeter(七)Jmeter脚本优化(数据与脚本分离)
午休时间再来记一记,嗯..回顾着使用Jmeter的历程,想着日常都会用到的一些功能.一些组件:敲定了本篇的主题----------是的.脚本优化. 说起脚本优化,为什么要优化?又怎么优化?是个永恒的话 ...
- Atitit 数据存储视图的最佳实际best practice attilax总结
Atitit 数据存储视图的最佳实际best practice attilax总结 1.1. 视图优点:可读性的提升1 1.2. 结论 本着可读性优先于性能的原则,面向人类编程优先于面向机器编程,应 ...
- Oracle数据逻辑迁移综合实战篇
本文适合迁移大量表和数据的复杂需求. 如果你的需求只是简单的迁移少量表,可直接参考这两篇文章即可完成需求: Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(上) Oracle简 ...
- 10天学会phpWeChat——第三天:从数据库读取数据到视图
在第二天,我们创建了我们的第一个phpWeChat功能模块,但是比较简单.实际生产环境中,我们不可能有如此简单的需求.更多的情况是数据存储在MySql数据库中,我们开发功能模块的作用就是将这些数据从M ...
- 转载-使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试
JUnit 是被广泛应用的 Java 单元测试框架,但是它没有很好的提供参数化测试的支持,很多测试人员不得不把测试数据写在程序里或者通过其它方法实现数据与代码的分离,在后续的修改和维护上有诸多限制和不 ...
- hadoop笔记之Hive的数据存储(视图)
Hive的数据存储(视图) Hive的数据存储(视图) 视图(view) 视图是一种虚表,是一个逻辑概念:可以跨越多张表 既然视图是一种虚表,那么也就是说用操作表的方式也可以操作视图 但是视图是建立在 ...
随机推荐
- 转载-----Java Longest Palindromic Substring(最长回文字符串)
转载地址:https://www.cnblogs.com/clnchanpin/p/6880322.html 假设一个字符串从左向右写和从右向左写是一样的,这种字符串就叫做palindromic st ...
- lua之base64加密和解密算法。
local function encodeBase64(source_str) local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop ...
- PTA 7-2 深入虎穴 (30 分)
著名的王牌间谍 007 需要执行一次任务,获取敌方的机密情报.已知情报藏在一个地下迷宫里,迷宫只有一个入口,里面有很多条通路,每条路通向一扇门.每一扇门背后或者是一个房间,或者又有很多条路,同样是每条 ...
- ARX添加新的图形对象到当前数据库空间ObjectARX PostCurrentSpace
static Acad::ErrorStatus PostCurrentSpace(AcDbObjectId &objId,AcDbEntity *pEnt) { Acad::ErrorSta ...
- 代理服务器和NAT技术
一.代理服务器 所谓“代理”,就是代而劳之的意思.代理服务器就是代理网络用户去取得网络信息,形象的说:它是网络信息的中转站,使得一个网络终端和另一个网络终端不直接进行相连,代理网络用户去取得信息.主要 ...
- TX 下常用的查询指令
查看Jetson TX2 L4T版本 head -n 1 /etc/nv_tegra_release 查看系统版本 cat /etc/lsb-release 查看系统l内核 uname -a 查看内存 ...
- pip安装本地文件
I do a lot of development without an internet connection1, so being able to install packages into a ...
- 《UltraFast设计法实践》系列目录
最近准备开始潜心学习快速和高效的时序收敛设计了,突然想就把整个学习过程做成一个博客系列吧,虽然想想就很激动(技术狗就这么点出息--),但希望坚持下来. 这篇做个目录或者索引,不断向其中添加学习内容. ...
- python全栈开发_day7_字符编码,以及文件的基本读取
一:字符编码 1)什么是字符编码 将人能识别的字符等高级标识符与计算机所能识别的二进制01进行转化,这之间的交流需要一个媒介,进行两种标识符之间的转化. 字节的存储方式为八个二进制位 2)乱码 存放数 ...
- c++ 两个set合并
set<int> a,b; //合并到a a.insert(b.begin(),b.end());