记一次数据、逻辑、视图分离的原生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层实现的。

原文地址:https://www.cnblogs.com/yuliangbin/p/9463114.html

时间: 2024-10-10 04:59:24

记一次数据、逻辑、视图分离的原生JS项目实践的相关文章

数据与视图分离2

1.用简单的方式操作table,现在有一个普通的table,比如要编辑某一行数据 1.1获取选中的row 1.2遍历row的cell 1.3将得到的数据赋值给弹出框 1.4保存,上传给服务器 1.5服务器响应完成,重新给row的cell赋值 这个是比较传统的方式,在1.2这一步比较麻烦,因为每获取一个值就需要首先得到一个cell,显得非常臃肿,它可能是这样的 var obj={}; obj.p1=dom1.value; obj.p2=dom2.value; obj.p3=dom3.value;

10天学会phpWeChat——第三天:从数据库读取数据到视图

在第二天,我们创建了我们的第一个phpWeChat功能模块,但是比较简单.实际生产环境中,我们不可能有如此简单的需求.更多的情况是数据存储在MySql数据库中,我们开发功能模块的作用就是将这些数据从MySql读取并通过视图多样化的呈现给用户. 今天我们进入<10天学会phpWeChat>系列教程的第三天:从数据库读取数据到视图. 一.首先,我们创建一个MySql数据文章表(pw_wechat_hello_article)用来存储要显示给用户的数据. 为了简单明了,这个表我们只保留3个字段: I

转载-使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试

JUnit 是被广泛应用的 Java 单元测试框架,但是它没有很好的提供参数化测试的支持,很多测试人员不得不把测试数据写在程序里或者通过其它方法实现数据与代码的分离,在后续的修改和维护上有诸多限制和不便.Feed4JUnit 是开源的基于 JUnit 的扩展,通过使用 Feed4JUnit 提供的注释,用户可以很方便的把测试数据存放在文件或其它数据源.本文通过介绍及简单示例,使读者了解并能够使用 Feed4JUnit, 方便的实现数据与代码分离的测试. Feed4JUnit 与 JUnit 经常

3 视图分离

在讲解session和kookie的应用之前,我们必须将代码进行适当的分离.如果把页面的html与php写在一起,那么就表示1个文件由2个人负责(网页设计师负责html代码,程序员负责php代码),若这2个人同时更新文件,则会造成混乱,为了方便管理,一般会把视图(前端html)分开放置,并由php加载. 这样我们通过浏览器访问的是php文件,html文件由php文件负责加载. 1 创建文件夹 回到我们的wlvsoft项目,由于现在都是针对后台编写功能,因此在wlvsoft目录下创建admin文件

GUI进化--数据与界面分离

http://blog.csdn.net/doon/article/details/5946862 1.何谓数据和界面分离? GUI,即Graphic User Interface,人机交换界面.连接两端:终端用户--内部逻辑.它关联到两个角色:使用者和开发者. 用户希望看到的是,一个一个的界面元素:窗口.按钮.输入框等等可视和可操作的元素:开发者希望看到的是数据,数字.字符串或者数组.表等数据结构. 传统的GUI,例如Gtk,Qt,Windows,甚至有Android等界面,基本上是把界面和数

Atitit 数据存储视图的最佳实际best practice attilax总结

1.1. 视图优点:可读性的提升1 1.2. 结论  本着可读性优先于性能的原则,面向人类编程优先于面向机器编程,应该优先使用视图2 1.3. 视图的缺点:复杂视图有时可能带来性能下降3 1.1. 视图优点:可读性的提升 视图的主要作用有以下几点:视点集中:使用户只关心它感兴趣的某些特定数据和他们所负责的特定任务简化操作:,若视图本身就是一个复杂查询的结果集,这样在每一次执行相同的查询时,不必重新写这些复杂的查询语句 定制数据:视图能够实现让不同的用户以不同的方式看到不同或相同的数据集 合并分割

命名空间namespace、smarty使用(视图分离,MVC)、smarty模板语法、smarty缓存、MVC模式

一.命名空间:namespace 命名空间 可以理解为逻辑上的使用,为了防止重名 namespace :关键字 加载:require_once();//加载一次 include_once() 申明命名空间注意:名称空间一定是在最顶部 在他上面不能有任何内容,名称空间申明不能写在类的内部 use App\Stu; 引入,导入 名称空间\引入类名 as:取别名 二.smarty使用 smarty是一个使用php写出来的模板引擎,拥有独立简单的模板语法,它实现了逻辑代码与模板的分离,把原本HTML与P

数据绑定和数据网格视图(DataGridView)控件

数据绑定和数据网格视图(DataGridView)控件 数据网格视图控件,不像我们前面看到的控件,它可以显示多个列,但是,数据必须格式化,使数据网格知道要显示哪一列.有两种实现方法:一个是把数据网格视图绑定到数据表(DataTable),另一个是把网格到绑定对象列表,对象有许多属性,不同的属性就成为网格的列. 下面的例子是一种简单的解决方案,绑定到数据集(DataSet): open System open System.Collections.Generic open System.Confi

Object.defineproperty实现数据和视图的联动

Object.defineproperty语法 在一个对象上定义新的属性 var o = {}; // 创建一个新对象 // Example of an object property added with defineProperty with a data property descriptor Object.defineProperty(o, "a", {value : 37, writable : true, enumerable : true, configurable :