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

  • 相关阅读:
    寒假学习第六天
    寒假学习第五天
    寒假学习第四天
    spark生态体系了解学习(六)
    spark生态体系了解学习(五)
    spark生态体系了解学习(四)
    spark生态体系了解学习(三)
    spark生态体系了解学习(二)
    spark生态体系了解学习(一)
    共享
  • 原文地址:https://www.cnblogs.com/yuliangbin/p/9463114.html
Copyright © 2020-2023  润新知