1. 程式人生 > >記一次數據、邏輯、視圖分離的原生JS項目實踐

記一次數據、邏輯、視圖分離的原生JS項目實踐

style item listview tro 過程 加載 完成 tar ive

一切的開始源於這篇文章:一句話理解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項目實踐