親愛的觀衆老婆們好~最近在團隊裏組織了一次內容關於React的分享,然然後端有同窗反映未能理解爲什麼前端須要使用框架,框架究竟解決了什麼問題。javascript
回想剛入坑前端的時候,也接觸了一些PHP,當了解到其實PHP能夠生成HTML模板以後,對前端的信仰幾乎崩潰。既而後端就能夠渲染前端的話,前端價值到底在哪?感受頁面就是隨便切一下就行,根本不必用框架,當時着實迷茫了好久。前端
後來對前端領域接觸深了,再通過大神的指點,總算是理解爲什麼前端須要使用框架。根據本身的理解,再結合分享時後端同窗的疑問,因而有了這篇文章。本文相對小白向,只是經過虛擬一個項目說明問題,還望各位大神不吝賜教。java
產品: 來來來,新項目來啦。最近發現用戶喜歡擼貓,咱們來個在線雲擼貓!頁面要求有貓圖,點一下計數加一點當擼了一次!多久能上線??後端
這個簡單啊!
設計模式
厲害的我連jQuery都不用,原生上!bash
<body>
<img src="1.jpg" alt="">
<p>0</p>
<script>
const img = document.querySelector('img');
const p = document.querySelector('p');
let count = 0;
img.addEventListener('click', function() {
count += 1;
p.innerHTML = count;
})
</script>
</body>複製代碼
簡單易懂,對不對!閉包
產品: 線上大受歡迎,然而要改需求了爸爸!如今要兩隻喵,點擊要分開算哦,各自顯示就好。你最棒了,我知道你確定能夠的!app
看在叫了爸爸的份上,仍是給他改一下吧。然而第一個版本多粗糙啊,各類污染全局變量,第二版我要寫得棒棒的!框架
<body>
<section class="catSection">
<img src="cat1.jpg" alt="" class="catImg">
<p class="catCount">0</p>
</section>
<section class="catSection">
<img src="cat2.jpg" alt="" class="catImg">
<p class="catCount">0</p>
</section>
<script>
(function() {
const catSection = document.querySelectorAll('.catSection');
catSection.forEach((section) => {
let count = 0;
const catImg = section.querySelector('.catImg');
const catCount = section.querySelector('.catCount');
catImg.addEventListener('click', () => {
count++;
catCount.innerHTML = count;
})
})
})()
</script>複製代碼
沒有污染全局變量,還能夠擴展需求,後續產品就算要求再多的貓我都能hold住!
函數
產品: 父皇,你聽我說啊,就最後一次,真的最後一次。如今貓兩隻不夠啊,但太多的話展現又很差看。如今需求作一個列表,列表有N只貓,點那隻貓展現那隻貓,每次只展現意志,並且貓要有對應的名字哦,點擊也要分開統計!其實跟之前差很少?就改改就行了。明天就要哦!
我封裝得好好的功能,你跟我說改需求?並且改得面目全非跟我說差很少?
然而,吐槽歸吐槽,需求仍是要作的。並且需求明天就要,代碼寫得再好也可能立刻改功能,代碼仍是實現功能就算了吧。
具體代碼由各位看官本身實現(建議先停下來,動手去實現這個需求),這裏我就再也不上代碼了。極可能咱們此次寫的代碼,就不會太考慮什麼全局變量污染,也不考慮封裝的問題,逐漸趨向於實現功能就行了,由於需求太多了,時間和精力限制了去寫「好」代碼。
固然了,產品經理說需求最後一次改,都是騙人的。下次可能會有點擊到某個值時,自動切換貓,動態添加貓等等的新需求。咱們如今這樣組織代碼的形式,是典型的」意大利麪式「代碼(簡單說,就是各類東西整合在一塊兒,層級不分,難以維護)。這種代碼寫起來直觀,但往後的維護是至關難的。寫出上述例子後,不妨隔兩天再去看看可否輕易理解那一份代碼。本身寫的代碼尚且如此,他人的更不在話下了。
那麼,這種隨着項目愈來愈複雜,邏輯愈來愈多,咱們該怎麼寫代碼呢?
前端其實有一種說法:咱們如今的」新「東西,都是其餘領域玩過的。雖然看起來很氣人,但這也是事實。當現狀不知如何處理時,不妨參考下其餘領域的解決方案。離前端比較近的就是後端了,那麼後端是怎麼管理的呢?最典型的設計就是MVC了。那麼,前端能不能借鑑呢?
說幹就幹,以上面的需求爲例,咱們試着用MVC的方式組織一下代碼,看下和你剛纔寫的有什麼不一樣。
先來M
,也就是Model,數據層,對外提供接口能夠獲取相關的數據。這麼組織的話,是否是蠻好懂的:
const model = (function() {
//相關數據
const _model = {
catLists: [
{
src: '1.jpg',
name: 'cat1',
count: 0
},
{
src: '2.jpg',
name: 'cat2',
count: 0
}
],
targetCatIndex: 0, //目前可被點擊的是哪知喵在catLists中的索引
};
//獲取getCatLists
function getCatLists() {
return _model.catLists;
}
//獲取目標對象
function getTargetCatObj() {
return _model.catLists[_model.targetCatIndex];
}
//修改targetCatIndex
function setTargetCatIndex(name) {
_model.catLists.some((catObj, index) => {
if (catObj.name === name) {
_model.targetCatIndex = index;
return true
}
})
}
//目標對象點擊數+1
function addTargetCatCount() {
const catObj = getTargetCatObj();
catObj.count += 1;
}
return {
getCatLists,
getTargetCatObj,
setTargetCatIndex,
addTargetCatCount
}
})();複製代碼
在自執行函數中,設計了一個對象命名爲_model
,經過閉包存儲它。自執行函數返回一個對象,其中包含四個函數。四個函數執行後,能夠返回或修改_model
中對應的數據。經過註釋看其實仍是挺清楚的。
跟着是V
,也就是view層,負責頁面渲染。這個可能複雜一點,但不想把它弄得太繁瑣,不如就兩個方法吧。就一個初始化的init()
和負責更新視圖的render()
方法就好啦。
先肯定HTML模板:
<ul class="catList"></ul>
<section class="clickArea">
<img src="" class="catImage">
<p class="catCount"></p>
<p class="catName"></p>
</section>複製代碼
再組織一下view層的代碼:
const view = (function() {
//獲取各個須要操做的DOM節點
const img = document.querySelector('.catImage');
const name = document.querySelector('.catName');
const count = document.querySelector('.catCount');
//初始化頁面
function init(catLists, targetObj) {
const list = document.querySelector('.catList');
const fragment = document.createDocumentFragment();
//爲ul添加對應的li
for (let i = 0, len = catLists.length; i < len; i++) {
const li = document.createElement('li');
const name = catLists[i].name;
li.innerHTML = name;
li.addEventListener('click', function() {
//以後會有controller相關的代碼,其實就是換一隻可點擊的喵
controller.changeTargetCat(name);
});
fragment.appendChild(li);
}
list.appendChild(fragment);
img.addEventListener('click', function() {
//以後會有controller相關的代碼,其實就是計數+1
controller.addCount(name);
});
render(targetObj);
}
//從新渲染頁面
function render(targetObj) {
img.src = targetObj.src;
name.innerHTML = targetObj.name;
count.innerHTML = targetObj.count;
}
return {
init,
render
}
})();複製代碼
view層的代碼其實也很簡單的,和model層的套路差很少,經過自執行函數結合閉包存儲以後要操做的節點,對外暴露由兩個方法組成的對象,分別是init
與render
。init
用於初始化話頁面,render
用於從新渲染頁面。裏面調用了controller
,其實就是以後要介紹的controller。
最後是C
層,也就是controller,主要是用於邏輯相關的處理,算是整個設計裏面的大腦。不過因爲這項目比較簡單,因此代碼反而是最簡單的:
const controller = {
addCount(name) {
//經過model的接口增長目標對象的計數
model.addTargetCatCount(name);
controller.renderView();
},
changeTargetCat(name) {
//經過model的接口修改目標索引
model.setTargetCatIndex(name);
controller.renderView();
},
init() {
//經過model的接口獲取相關數據
const { getCatLists, getTargetCatObj } = model;
//傳參並命令view層初始化
view.init(getCatLists(), getTargetCatObj());
},
renderView() {
//傳參並命令view層從新渲染
view.render(model.getTargetCatObj());
}
};複製代碼
controller的設計實際上是比較簡陋的,只是一個包含了四個方法的對象。其中addCount
對應點擊加一的操做,changeTargetCat
對應換貓的操做。上述兩個方法實際上是改變了數據的,而只要數據發生了變化,一概調用renderView
從新渲染。
至此主要代碼已經寫完了,以後調用一下controller.init();
,就能夠開心的擼貓,完成需求了。
若是以前你動手實現了上述需求的話,不妨對比一下咱們設計代碼上的差異。也許你寫的代碼仍是以前那種「麪條式」代碼,但它也是可用的啊,並且還不用這麼多代碼呢,長得也還算能維護的樣子,爲什麼要用這種繁瑣的方式去阻止代碼呢?
然而,按照以前說的,產品可能提更多的需求,下次可能會有點擊到某個值時,自動切換貓,動態添加貓等等的新需求時,你如今的代碼組織形式可否很快地完成需求?當往後修改某些需求時,不當心觸發了潛藏的bug(常常有的狀況),又是否能快速定位出問題並快速改好呢?
「麪條式」代碼常常是數據、視圖與處理邏輯耦合起來的,很容易牽一髮而動全身,當業務至關複雜的時候,開發可能還好說,維護簡直是不可想象的。而你可能已經觀察到了,遵循MVC設計的代碼,雖然繁瑣,但各層是徹底分開的,儘管數據與視圖能夠直接調用對方的接口進行交互,但都是必須經過控制層來作統一處理。數據、視圖與處理邏輯解耦以後,代碼的可閱讀性與可維護性都是一個飛躍。經過犧牲空間(多寫代碼)來換取代碼的可維護性與可拓展性,這是一筆劃算的買賣。
說了半天,好像尚未說出爲什麼前端須要使用框架。然而在複雜的項目中,你贊成我經過MVC組織代碼會比「麪條式」代碼好嗎?若是贊成的話,將我剛纔代碼中不變的部分抽象起來(組件通信、報錯處理等),千方百計提升渲染的性能(使用Virtual Dom),若是認爲前端和其餘不同,數據和視圖仍是能夠進行受控的交互(即MVVM),那麼整合起來,不就是一個框架了嗎?
其實必需要認可,人的腦力是有限的,一款產品的需求多是無限的。當這款產品已經讓你沒法掌握每一個細節,每位參與開發的同窗只能掌握局部細節,而其餘部分只管調用是必然的事實。可是,如何肯定其餘部分可信,調用不會出bug呢?這時候就該使用框架了。框架較大程度上能約束與規範每位開發者的行爲,不按照框架的規定極可能就會報錯,這樣多人協做就有了基本的保證。
可是,不是說使用框架就是最佳實踐。當項目不復雜的時候(好比一次性的活動頁),咱們有足夠能力去掌握項目的細節,那麼使用框架反而不是好的選擇。畢竟再好的框架在性能上都會有損失,而被框架的條條框框約束着,也老是使人不喜的。
簡單地說:腦子不夠,框架來湊;本身組織很差代碼,靠框架來給咱們組織。閱讀至此殊爲不易,感謝各位看官,但願本文對你有一點幫助,謝謝!