如今的前端領域, 隨着JS框架, UI框架和各類庫的豐富, 前端架構也變得十分的重要. 若是一個大型項目沒有合理的前端架構設計, 那麼前端代碼可能由於不一樣的開發人員隨意的引入各類庫和UI框架, 致使代碼量變得異常臃腫, 最終結果多是代碼變得沒法維護, 頁面性能低下,不得已只能推翻重構. 因此咱們須要在項目開始前, 一樣的須要對前端代碼進行架構, 一旦前端架構師設計出全部前端開發人員都要遵循的檢驗機制, 創建起系統設計的規範, 那麼項目就擁有了能夠衡量代碼質量的標準, 前端開發人員也能享受到更高效的工做流. 因此, 前端架構的定義能夠用如下一句話來總結:javascript
前端架構是一系列工具和流程的集合, 旨在提高前端代碼的質量, 並實現高效, 可持續的工做流.css
本系列的前端架構文章, 將分別圍繞前端架構的四個核心展開, 分別是代碼, 流程, 測試, 文檔.html
歸根到底, 全部的網站都是由一堆文本文件和資源文件組成的. 當咱們面對製做網站所產生的大量代碼時, 就會發現爲代碼和資源設定一個指望是多麼重要. 在代碼部分, 咱們會專一於若是實現系統架構中的HTML, CSS, JavaScript.前端
如今早已過了FTP上傳文件的時代, 那麼如今重要的是思考怎麼用工具和流程構建一個高效且避免出錯的工做流. 工做流變得愈來愈複雜, 那些用於它們的工具也一樣如此. 這些工具在提升生產力, 加快效率和保持代碼一致性上帶來了驚人的效果, 但也伴隨着過分工程化和抽象化的風險. 因此, 現有的工做流是須要改變的.java
要構建一個可擴展和可持續優化的系統, 必須保證新代碼和老代碼可以很好的兼容. 咱們的代碼不會獨立存在, 它們都是大型系統中的一部分. 建立覆蓋面普遍的測試方案, 能確保老代碼還能正常運做.git
通常而言, 若是不是團隊中的重要成員要離開, 咱們幾乎都不會意識到文檔的重要性. 等到那個時候, 你們將不得不停下手頭的工做, 優先編寫全部的文檔. 做爲前端機構師, 你要善於在項目開發的同時編寫良好的文檔.github
在前端的架構中, HTML做爲頁面的基礎是十分重要的. 若是初始的HTML寫得很爛, 將要寫出不少沒必要要的CSS和JavaScript來彌補. 反之, 若是若是初始的HTML寫得足夠好, 就能寫出根據可擴展性和可維護的CSS和JavsScript.數組
首先咱們來看一些初級的前端工程師可能寫出的HTML代碼:前端工程師
<div id="header" class="clearfix"> <div id="header-screen" class="clearfix"> <div id="header-inner" class="container-12 clearfix"> <div id="nav-header" role="navigation"> <div class="region" region-navigation> <div class="block block-system block-menu"> <div class="block-inner"> <div class="content"> <ul class="menu"> <li class="first leaf"> <a href="#">菜單1</a> </li> <li class="second leaf"> <a href="#">菜單2</a> </li> </ul> </div> </div> </div> </div> </div> </div> </div>
這類"div
亂燉"的代碼, 是不少初級的前端爲應付切頁面的工做寫出來的. 只是單純爲了還原psd圖, 而徹底你不考慮HTML的可讀性和可維護性.架構
隨後, 在HTML5以後, 標籤的語義化受到了你們的重視, 採用語義化的標籤, 不只增長了代碼的可讀性, 也有利於SEO. HTML語義化標籤的使用,這也是在前端架構中須要考慮到的,下面咱們來看一下使用語言化標籤寫的這段代碼:
<header> <section> <nav> <ul> <li> <a href="#"> 菜單1 </a> </li> <li> <a href="#"> 菜單2 </a> </li> </ul> </nav> </section> </header>
可是若是咱們的頁面的菜單有數10項的時候, 就會額外添加<li><a href="#">菜單N</a></li>
, 這類重複的工做量徹底能夠交給Mustache
這類模板引擎來解決, 已Vue中的模板引擎語法來寫HTML, 會減小不少的工做量 :
<template> <header> <section> <nav> <ul> <li v-for="(item, index) in navList" :key="index"> <a href="#"> {item} </a> </li> </ul> </nav> </section> </header> </template> <script> export default { data() { navList:['菜單1','菜單2','菜單3','菜單4','菜單5','菜單6','菜單7','菜單8','菜單9','菜單10'] } } </script>
你也可使用Handlebars, Jade, artTemplate各類模板引擎到你的項目中, 固然這些都是須要取決於前端架構師前期的所選擇的技術選型. 作爲前端架構師, 須要評估HTML產生的過程, 你對內容的順序, 使用的元素和CSS類名有多大的控制權? 這些元素在未來改動起來會有多大難度? 模板的易用性? 你能夠經過系統作出更改, 仍是須要手動處理? 經過回答這些問題, 可能會顛覆你本身構建HTML和CSS的方法.
構建CSS如今有不少成熟的方法, 例如使用新的命名空間, 擴充數據屬性或在JavaScript裏面定義CSS. 這些方法你能夠從BootStrap, ElementUI這類UI框架中找到影子. 下面, 介紹3種比較經常使用的方法.
1.OOCSS方法(Object-Oriented CSS 面向對象的CSS)
<div class="toggle simple"> <div class="toggle-control open"> <h1 class="toggle-title">標題</h1> </div> <div class="toggle-details open"> 詳細內容 </div> </div>
上面這段代碼就展現瞭如何使用OOCSS方法建立一個可切換的HTML代碼, OOCSS有兩個主要的原則:
分離結構和外觀
這裏的toggle
用來控制結構, simple
用來控制外觀,這就是分離結構和外觀的表現. 這樣能夠實現外觀的複用, 例如當前的simple
皮膚使用直角, 而complex
皮膚可能使用圓角, 還加了陰影.
分離容器和內容
這裏使用toggle-title
就是分離容器和內容的表現, 不管toggle-title
的容器是用的<h1>
仍是<h2>
或者是<div>
, 一旦加上了toggle-title
這個類名, 那麼該容器均已該類名所定義的樣式呈現內容.
2.SMACSS方法(Scalable and Modular Architecture for CSS 模塊化架構的可擴展的CSS)
<div class="toggle toggle-simple"> <div class="toggle-control is-active"> <h2 class="toggle-title">標題2</h2> </div> <div class="toggle-detail is-active"> 詳細內容 </div> </div>
上面的這段代碼基本展現瞭如何使用SMACSS方法,在我我的的理解中, OOCSS更多的實際上是提供了一種CSS構建思想, 該思想要求將結構和外觀分離, 將容器和內容分離. 可是並無提供一套完整的CSS構建規範, 而SMACSS是提供了一套樣式系統, 該樣式系統有5個具體類別:
基礎
//base.css body, form { margin: 0; padding: 0; } a { color: #039; } a:hover { color: #03F; }
在基礎代碼中, 應該規定的是頁面中的一些通用樣式,例如將body
的margin
和padding
設置爲0 , 設置a
標籤的顏色等. 相似於某些人經常使用的initial.css
文件.
佈局
//layout.css #header, #article, #footer { width: 960px; margin: auto; } #article { border: solid #CCC; border-width: 1px 0 0; }
這裏的佈局指的是頁面中一些通用的佈局組件, 例如頭部, 側邊欄, 主體和底部這些. 這些佈局組件會在多個頁面通用, 因此最好把其放入到一個css文件中. 方便複用. 在SMACSS中, 推薦將佈局容器的頂級標籤設置爲id
, 這樣確保了每一個頁面中擁有惟一持有該樣式的佈局容器, 也方便其css和js選擇器的使用. 固然, 你也可使用一個惟一的類名替代id
.
模塊
//module.css //module1 .module1 > h2 { padding: 5px; } .module1 span { padding: 5px; } //module2 .module2 > h2 { padding: 10px; } .module2 span { padding: 10px; }
模塊是指頁面中能夠單獨分離並提取出來複用的部分, 例如導航條, 側邊欄, 對話框或一些widget等. 因此, 模塊禁止使用id
, 而應該採用類名的方式.
狀態
<div id="header" class="is-collapsed"> <form> <div class="msg is-error"> There is an error! </div> <label for="searchbox" class="is-hidden">Search</label> <input type="search" id="searchbox"> </form> </div>
State 負責定義元素不一樣的狀態下,所呈現的樣式. 上面的一段代碼中,已is-
開頭的就是表示狀態的類名, is-collapsed
, is-error
等類名不會單獨使用, 而是和前面的佈局和模塊一塊兒使用. 下面的代碼, 就是在tab欄模塊和狀態一塊兒使用:
//state.css .tab { background-color: purple; color: white; } .is-tab-active { background-color: white; color: black; }
主題
// module-name.css .mod { border: 1px solid; } //theme.css .mod { border-color: blue; }
這裏的主題理解爲皮膚更加合適, 已上面的代碼爲例, 在module-name.css
中定義了邊框除顏色以外的樣式, 在theme.css
文件中定義了該邊框的顏色, 這樣的好處就是, 若是定義其餘顏色的類名去覆蓋這些有顏色的樣式, 那麼就能夠經過類名去切換皮膚的顏色. 達到更換主題的效果.
更多關於SMACSS的方法, 請參考: https://smacss.com/book
3.BEM方法(Block Element Modifier 塊元素修飾符)
<div class="toggle toggle--simple"> <div class="toggle__control toggle__control--active"> <h2 class="toggle__title">標題3</h2> </div> <div class="toggle__details toggle__details--active"> ... </div> ... </div>
BEM是由Yandex提出的給一個CSS命名方法, 該方法要求使用一個CSS類名, 儘量使用如下三者組成:
塊名
這裏的塊名不少初學者會覺得是inline-block
中的塊, 其實這裏的塊名指的是一個獨立的模塊或組件. 例如一個<header>
能夠用作一個模塊, <header>
中的<nav>
能夠用做一個模塊. 模塊之間是能夠相互嵌套的. 上面的示例代碼中 ,toggle
就是一個獨立的模塊
元素
元素是指沒法用在其餘塊名中的部分, 在BEM方法中, 元素跟在塊名後面使用__
鏈接, 之因此約定使用雙下劃線是由於方便在塊名中使用單下劃線命名. 上面示例代碼中的toggle__control
, toggle__title
就是塊名+元素的命名方式.
修飾符
修飾符與SMACSS中的狀態相似, 在BEM方法中, 修飾符須要跟在元素後面使用--
鏈接. 有的人會以爲這種寫法會使得代碼冗餘, SMACSS使用is-active
一樣能夠表示一樣的做用, 爲何上面的代碼要使用toggle__details--active
呢? 其實, 若是單獨看open
和is-active
這兩個名字, 咱們並不知道它們的含義是什麼, 可是當看到一個toggle__details--active
的類名, 咱們就知道它是表示: 這個元素的名稱是details
, 位置在toggle
組件裏, 狀態爲active
.
1.框架的選擇
這裏我不想陷入Angular, React, Vue三大框架之爭. 我是一個Vue的開發者, 我深知MVVM框架給咱們開發者帶了極大的便利, 不用再以jQuery不停的操做DOM的形式去開發, 而是隻關注數據的改變, 以數據去驅動DOM的改變. 這可以把更多的時間放入到業務邏輯的處理上.
就目前三大框架的生態系統來看, 大部分業務三大框架實現起來其實並無什麼大的差異,框架的選擇更多的取決於項目中團隊人員的偏好和學習成本. 好比Vue的學習成本就相比於Angular要小太多. 雖然我是一個Vue的開發者, 但我不得不說在React中使用JSX的語法讓寫代碼變得很愉快.
這裏我還想說的是: 其實你極可能不須要任何的框架!
有不少成功的網站只不過是採用了一些模板語法, 加上少許手動建立的Sass文件和幾十個Javascript函數建立而成. 當項目的規模足夠龐大, 須要犧牲代碼文件體積大小去換取框架所帶來的開發效率的提升時, 再考慮評估引入哪類JS框架和UI框架, 不然不要輕易放棄精簡方案.
2.選擇一套JavaScript代碼規範
每一個人寫代碼的方式是不一樣的, 有些人可能喜歡用==
, 但有的喜歡用===
; 有的人可能習慣給每一個變量使用var
去聲明, 但有的喜歡使用一個var
加逗號運算符去同時聲明多個變量. 這些代碼習慣可能並不會對程序運行形成影響. 可是在大型業務中, 面臨多個開發者共同開發時, 若是沒有一套代碼規範, 那麼就會出現代碼難以維護, 難以閱讀的狀況. 爲了讓新加入的團隊成員也可以快速熟悉相關的代碼, 而且讓代碼能夠維護, 一套Javascript代碼規範不管是開發大型項目和小型項目, 都是必須的.
若是公司沒有代碼定製本身的代碼規範, 可使用大公司所制定的代碼規範, 這裏向你們推薦如下三個代碼規範:
JavaScript Standard Style Guide
下面截取部分airbnb的ES5規範, 來對比一下使用了規範和未使用規範的區別:
//bad var items = new Array() //good var items = [];
slice
var len = items.length; var itemsCopy = []; var i; // bad for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good itemsCopy = items.slice();
slice
將類數組對象轉換成數組function trigger() { var args = Array.prototype.slice.call(arguments); }
''
包裹字符串//bad var name = "LITANGHUI" //good var name = 'LITANGHUI'
join
鏈接而不是使用鏈接符。尤爲是 IE 下var items; var messages; var length; var i; messages = [{ state: 'success', message: 'This one worked.' }, { state: 'success', message: 'This one worked as well.' }, { state: 'error', message: 'This one did not work.' }]; length = messages.length; // bad function inbox(messages) { items = '<ul>'; for (i = 0; i < length; i++) { items += '<li>' + messages[i].message + '</li>'; } return items + '</ul>'; } // good function inbox(messages) { items = []; for (i = 0; i < length; i++) { // use direct assignment in this case because we're micro-optimizing. items[i] = '<li>' + messages[i].message + '</li>'; } return '<ul>' + items.join('') + '</ul>'; }
===
和 !==
而不是 ==
和 !=
// bad if (name !== '') { // ...stuff... } // good if (name) { // ...stuff... } // bad if (collection.length > 0) { // ...stuff... } // good if (collection.length) { // ...stuff... }
// bad function () { ∙∙∙∙var name; } // bad function () { ∙var name; } // good function () { ∙∙var name; }
// bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog' }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog' });
// bad var x=y+5; // good var x = y + 5;