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