前端技術(shù)淺談
1线召、前端框架發(fā)展
以銅為鑒,可以正衣冠多矮;以人為鑒缓淹,可以明得失;以史為鑒塔逃,可以知興替讯壶。
本文主題:前端開(kāi)發(fā)如何借助對(duì)
HTML
和Javascript
不同的使用方式,來(lái)一步步演進(jìn)前端開(kāi)發(fā)框架湾盗,從而優(yōu)化前端開(kāi)發(fā)過(guò)程伏蚊。
想要全面了解前端技術(shù)發(fā)展,最好的方法就是向后看淹仑,看一路走來(lái)前端開(kāi)發(fā)是如何從服務(wù)端主導(dǎo)的靜態(tài)網(wǎng)站
一步步發(fā)展到現(xiàn)在由客戶端主導(dǎo)的單頁(yè)應(yīng)用
丙挽。只有了解了過(guò)去前端分別在不同的階段解決了怎樣的問(wèn)題,才能更好地看清楚未來(lái)要向哪里去匀借。所以颜阐,我們先簡(jiǎn)單的來(lái)看看企業(yè)信息化產(chǎn)品
在前端技術(shù)發(fā)展過(guò)程中的幾個(gè)重要迭代過(guò)程。
1.1吓肋、靜態(tài)頁(yè)面
1991年凳怨,Tim作為布道者在Internet上廣泛推廣Web的理念,與此同時(shí)是鬼,美國(guó)國(guó)家超算應(yīng)用中心對(duì)此表現(xiàn)出了濃厚的興趣肤舞,并開(kāi)發(fā)了名為Mosaic
的瀏覽器,于1993年4月進(jìn)行了發(fā)布均蜜。此時(shí)的網(wǎng)頁(yè)以HTML為主李剖,是純靜態(tài)的網(wǎng)頁(yè),網(wǎng)頁(yè)是“只讀”的囤耳,信息流只能通過(guò)服務(wù)器到客戶端單向流通篙顺,由此世界進(jìn)入了Web 1.0時(shí)代偶芍。
90年代,流行的一些新聞網(wǎng)站德玫、公司官方宣傳網(wǎng)頁(yè)匪蟀,都屬于這個(gè)范疇。
解決問(wèn)題:信息的展示和傳播過(guò)程宰僧。
1.2材彪、動(dòng)態(tài)頁(yè)面
靜態(tài)頁(yè)面因?yàn)閮?nèi)容是固定的,不能讀取后臺(tái)數(shù)據(jù)庫(kù)中的數(shù)據(jù)琴儿,為了使得Web內(nèi)容更加靈活段化,以PHP
、JSP
凤类、ASP.NET
為代表的動(dòng)態(tài)頁(yè)面技術(shù)相繼誕生穗泵。
隨著動(dòng)態(tài)頁(yè)面技術(shù)的不斷發(fā)展普气,后臺(tái)代碼變得龐大臃腫谜疤,后端邏輯也越來(lái)越復(fù)雜,逐漸難以維護(hù)现诀,此時(shí)夷磕,后端的各種MVC
框架逐漸發(fā)展起來(lái),以JSP
為例仔沿,Struts
坐桩、Spring MVC
等框架層出不窮,這些框架把請(qǐng)求處理邏輯封锉,分層了多層绵跷,比如典型的三層
結(jié)構(gòu),就起源于此成福。
JSP(JavaServer Pages)原理
JSP其實(shí)就是HTML+Java語(yǔ)言組成的一個(gè)html模板文件碾局,通過(guò)JSP Engine 把它最終轉(zhuǎn)換為HTML頁(yè)面。
公司內(nèi)部產(chǎn)品:
軟件公司
SIP
平臺(tái)奴艾,基于ASP.NET
開(kāi)發(fā)净当,采用aspx
模板語(yǔ)法,進(jìn)行動(dòng)態(tài)頁(yè)面渲染蕴潦。并且還采用流行一時(shí)的前端服務(wù)器組件:Express Developer.Net
技術(shù)公司
Supplant 4.x
產(chǎn)品像啼,基于Struts
,采用freemark
模板,進(jìn)行動(dòng)態(tài)頁(yè)面渲染潭苞。
// 現(xiàn)在比較流行的spring 前端模板 Thymeleaf 渲染一個(gè)用戶信息塊
<h2>
<p>Name: <span th:text="${user.name}">Jack</span>.</p>
<p>Age: <span th:text="${user.age}">21</span>.</p>
<p>friend: <span th:text="${user.friend.name}">Rose</span>.</p>
</h2>
從Web誕生至2005年忽冻,一直處于后端重、前端輕的狀態(tài)此疹。這個(gè)階段僧诚,瀏覽器只是負(fù)責(zé)渲染頁(yè)面蜜猾,具體頁(yè)面的一些邏輯大部分都是在后端執(zhí)行,包括按鈕點(diǎn)擊振诬,下拉框數(shù)據(jù)改變等等前端交互事件蹭睡,甚至在ASP.NET WebForm
框架還有服務(wù)器
端控件一說(shuō)。
解決的問(wèn)題:實(shí)現(xiàn)html內(nèi)容動(dòng)態(tài)拼裝的過(guò)程赶么,可以讓服務(wù)器端的數(shù)據(jù)動(dòng)態(tài)的生成到頁(yè)面肩豁,同時(shí)采用頁(yè)面模板,可以復(fù)用很多公共頁(yè)面邏輯辫呻。web產(chǎn)品更加靈活了清钥,涉足的領(lǐng)域也更加廣。
1.3放闺、AJAX
在Web最初發(fā)展的階段(動(dòng)態(tài)頁(yè)面)祟昭,因?yàn)轫?yè)面內(nèi)容和頁(yè)面交互事件都依托于服務(wù)器端,前端頁(yè)面要想獲取后臺(tái)信息需要刷新整個(gè)頁(yè)面
怖侦,這是很糟糕的用戶體驗(yàn)篡悟。
Google分別在2004年和2005年先后發(fā)布了兩款重量級(jí)的Web產(chǎn)品:Gmail
和Google Map
。這兩款Web產(chǎn)品都大量使用AJAX技術(shù)匾寝,不需要刷新頁(yè)面就可以使得前端和服務(wù)器進(jìn)行網(wǎng)絡(luò)通信搬葬,這雖然在當(dāng)今看來(lái)是理所應(yīng)當(dāng)?shù)模窃谑畮啄昵癆JAX卻是一項(xiàng)革命性的技術(shù)艳悔,顛覆了用戶體驗(yàn)急凰。
Asynchronous Javascript And XML
瀏覽器BOM實(shí)現(xiàn)原理 XMLHttpRequst
API展示:
function sendAjax() {
//構(gòu)造表單數(shù)據(jù)
var formData = new FormData();
formData.append('username', 'tom');
formData.append('id', 123456);
//創(chuàng)建xhr對(duì)象
var xhr = new XMLHttpRequest();
//設(shè)置xhr請(qǐng)求的超時(shí)時(shí)間
xhr.timeout = 3000;
//設(shè)置響應(yīng)返回的數(shù)據(jù)格式
xhr.responseType = "text";
//創(chuàng)建一個(gè) post 請(qǐng)求,采用異步
xhr.open('POST', '/server', true);
//注冊(cè)相關(guān)事件回調(diào)處理函數(shù)
xhr.onload = function(e) {
if(this.status == 200||this.status == 304){
alert(this.responseText);
}
};
xhr.ontimeout = function(e) { ... };
xhr.onerror = function(e) { ... };
xhr.upload.onprogress = function(e) { ... };
//發(fā)送數(shù)據(jù)
xhr.send(formData);
}
AJAX使得瀏覽器客戶端可以更方便地向服務(wù)器發(fā)送數(shù)據(jù)信息猜年,這促進(jìn)了Web 2.0的發(fā)展抡锈。
但是有個(gè)問(wèn)題,各個(gè)瀏覽器對(duì)Ajax
和Dom
操作有差異性乔外。為了解決瀏覽器兼容性問(wèn)題床三,Dojo
、jQuery
袁稽、YUI
勿璃、ExtJS
等前端Framework相繼誕生。前端開(kāi)發(fā)人員用這些Framework頻繁發(fā)送AJAX請(qǐng)求到后臺(tái)推汽,在得到數(shù)據(jù)后补疑,再用這些Framework更新DOM樹(shù)。
Jquery Ajax
的封裝:
$.ajax({
//請(qǐng)求方式
type : "POST",
//請(qǐng)求的媒體類(lèi)型
contentType: "application/json;charset=UTF-8",
//請(qǐng)求地址
url : "http://127.0.0.1/admin/list/",
//數(shù)據(jù)歹撒,json字符串
data : JSON.stringify(list),
//請(qǐng)求成功
success : function(result) {
console.log(result);
},
//請(qǐng)求失敗莲组,包含具體的錯(cuò)誤信息
error : function(e){
console.log(e.status);
console.log(e.responseText);
}
});
于此同時(shí),服務(wù)器框架也慢慢的由處理對(duì)html頁(yè)面的請(qǐng)求(Jsp+servlet
)和模板渲染暖夭,轉(zhuǎn)變?yōu)閷?duì)前端Ajax請(qǐng)求的處理(Spring MVC \ Asp.NET MVC
)锹杈,前后端一般通過(guò)xml
或者Json
格式進(jìn)行數(shù)據(jù)傳輸交互撵孤。當(dāng)然后期,Json
已經(jīng)成為前后端數(shù)據(jù)格式的標(biāo)準(zhǔn)竭望。
解決問(wèn)題:Ajax 解決了動(dòng)態(tài)頁(yè)面獲取內(nèi)容或者提交數(shù)據(jù)都需要整頁(yè)刷新的問(wèn)題邪码,可以讓頁(yè)面局部更新,異步提交數(shù)據(jù)咬清,提升了Web的系統(tǒng)用戶體驗(yàn)闭专。同時(shí)后端也出現(xiàn)了,純粹處理數(shù)據(jù)的web服務(wù)技術(shù)來(lái)支撐異步請(qǐng)求的過(guò)程旧烧。另外影钉,頁(yè)面的展示和頁(yè)面的數(shù)據(jù)也開(kāi)始慢慢有種分離的趨勢(shì)。
公司內(nèi)部產(chǎn)品:
軟件公司
SIP4.X
產(chǎn)品掘剪,大量使用Jquery\Easyui等前端組件平委,并且服務(wù)器端也從Asp.Net
升級(jí)為Asp.Net MVC
。每個(gè)頁(yè)面都是純粹的HTML+Javascript
, 開(kāi)發(fā)方式也慢慢的前后端分離夺谁。
1.4廉赔、MV* 架構(gòu)
伴隨著Ajax
出現(xiàn)和流行,大量的交互邏輯都從后端移到了前端予权,前端的代碼越來(lái)越多昂勉,于此同時(shí),javascript
語(yǔ)言本身也重新開(kāi)始迭代扫腺。
1.4.1、ES6的發(fā)展
2015年6月村象,ECMAScript 6.0
發(fā)布笆环。該版本增加了許多新的語(yǔ)法,包括支持let厚者、const躁劣、Arrow function、Class库菲、Module账忘、Promise、Iterator熙宇、Generator鳖擒、Set、Map烫止、async蒋荚、Symbol、Proxy馆蠕、Reflect期升、Decorator
等惊奇。這些語(yǔ)法使得前端開(kāi)發(fā)人員,可以不用Jquery
這種第三方的框架播赁,也能夠很優(yōu)雅的編寫(xiě)前端代碼颂郎。
ECMAScript
發(fā)展很快,以至于許多瀏覽器都只能支持部分ES6中的新特性容为。隨之祖秒,Babel
和TypeScript
逐漸流行起來(lái),編寫(xiě)ES6代碼舟奠,然后用Babel或TypeScript
將其編譯為ES5
等瀏覽器支持的JavaScript竭缝。
同時(shí)隨著HTML5
的流行,前端不再是人們眼中的小玩意沼瘫,以前在C/S中實(shí)現(xiàn)的桌面軟件的功能逐步遷移到了前端抬纸,前端的代碼邏輯逐漸變得復(fù)雜起來(lái)。解決辦法耿戚,和后端代碼一樣:分層
湿故。當(dāng)然,分層一般都是基于比較通用的一些設(shè)計(jì)模式膜蛔,比如MVC坛猪、MVP、MVVM
皂股。伴隨著墅茉,分層的出現(xiàn),一些現(xiàn)在流行的框架也同時(shí)出現(xiàn)了:
隨著這些MV*
框架的出現(xiàn)呜呐,網(wǎng)頁(yè)逐漸由Web Site演變成了Web App就斤,最終導(dǎo)致了復(fù)雜的單頁(yè)應(yīng)用( Single Page Application)的出現(xiàn)。同時(shí)也伴隨出現(xiàn)了類(lèi)似前端路由
蘑辑、虛擬DOM
等一些前端概念洋机。
1.4.2、Node.js發(fā)展
一個(gè)基于 Chrome
V8
引擎的 JavaScript 運(yùn)行時(shí)
Node.js的發(fā)展洋魂,使得Javascript
不在局限于瀏覽器端運(yùn)行绷旗,服務(wù)器軟件、桌面軟件到看到了Javascript
的身影副砍。當(dāng)然對(duì)前端領(lǐng)域而言衔肢,Node.js的產(chǎn)生使得前端開(kāi)發(fā)基于Javascript
實(shí)現(xiàn)工程化得到了可能。后文會(huì)詳細(xì)介紹址晕。
解決問(wèn)題:前端代碼復(fù)雜后膀懈,Dom操作變的頻繁,需要用更合理谨垃、更優(yōu)雅的方式來(lái)完成這個(gè)過(guò)程启搂,分層和用更高層次的設(shè)計(jì)模式來(lái)組織硼控,由此大量前端MV*框架出現(xiàn)。
1.5胳赌、移動(dòng)應(yīng)用興起
隨著iOS
和Android
等智能手機(jī)的廣泛使用牢撼,移動(dòng)瀏覽器也逐步加強(qiáng)了對(duì)HTML5
特性的支持力度。
移動(dòng)瀏覽器的發(fā)展疑苫,導(dǎo)致了流量入口逐漸從PC分流到移動(dòng)平臺(tái)熏版,這是Web發(fā)展的新機(jī)遇。移動(dòng)Web面臨著更大的碎片化和兼容性問(wèn)題捍掺,jQuery Mobile撼短、Sencha Touch、Framework7挺勿、Ionic
等移動(dòng)Web框架也隨之出現(xiàn)缨伊。
相比于原生應(yīng)用(Native App)烟阐,移動(dòng)Web開(kāi)發(fā)成本低砚嘴、跨平臺(tái)枚粘、發(fā)布周期短的優(yōu)勢(shì)愈發(fā)明顯,但是原生應(yīng)用(Native App)的性能和UI體驗(yàn)要遠(yuǎn)勝于移動(dòng)Web蚊丐。移動(dòng)Web與Native App孰優(yōu)孰劣的爭(zhēng)論愈演愈烈熙参,在無(wú)數(shù)開(kāi)發(fā)者的實(shí)踐中,人們發(fā)現(xiàn)兩者不是替代關(guān)系麦备,而是應(yīng)該將兩者結(jié)合起來(lái)孽椰,取長(zhǎng)補(bǔ)短,Hybrid技術(shù)逐漸得到認(rèn)同泥兰。
根據(jù)實(shí)現(xiàn)原理弄屡,Hybrid技術(shù)可以分為兩大類(lèi):
-
將
HTML5
的代碼放到Native App的WebView
(可以理解為移動(dòng)端輕量瀏覽器)組件中運(yùn)行,WebView
為Web提供宿主環(huán)境鞋诗,JavaScript代碼通過(guò)WebView
調(diào)用Native API。最新公司上線一個(gè)
沙特健康碼項(xiàng)目
迈嘹,就是一個(gè)混合的App削彬。其優(yōu)點(diǎn)就是改動(dòng)H5
部分的功能,App不需要重新安裝秀仲。技術(shù)公司信息化產(chǎn)品e-mobile其實(shí)也是個(gè)混合App融痛,比如我們之前的每日健康申報(bào)。
-
將
HTML5
代碼針對(duì)不同平臺(tái)編譯成不同的原生應(yīng)用神僵,實(shí)現(xiàn)了Web開(kāi)發(fā)雁刷,Native部署。這一類(lèi)的典型代表有React Native
保礼。React Native : 使用JavaScript和React編寫(xiě)原生移動(dòng)應(yīng)用沛励。
解決問(wèn)題:移動(dòng)端的興趣责语,讓
H5
在移動(dòng)端發(fā)展迅速,由此也讓混合應(yīng)用成為了趨勢(shì)目派±ず颍混合應(yīng)用解決了更新應(yīng)用內(nèi)容,但是App不需要重新下載更新的問(wèn)題企蹭,同時(shí)也因?yàn)镴S的跨平臺(tái)特性白筹,簡(jiǎn)化了移動(dòng)應(yīng)用的開(kāi)發(fā)過(guò)程。
2谅摄、主流框架和技術(shù)介紹
目前國(guó)內(nèi)徒河,前端單頁(yè)應(yīng)用
開(kāi)發(fā)相對(duì)流行的Javascript
框架主要有兩個(gè)(Jquery不算的話):Vue
和 React
,他們核心都是解決一個(gè)數(shù)據(jù)
和視圖
之間關(guān)聯(lián)的問(wèn)題送漠。
一般我們進(jìn)行表單開(kāi)發(fā)過(guò)程中顽照,往往需要經(jīng)歷兩個(gè)過(guò)程:
1)、頁(yè)面加載完成后螺男,腳本向服務(wù)器異步請(qǐng)求數(shù)據(jù)棒厘,數(shù)據(jù)返回后,通過(guò)腳本給表單設(shè)置初始值下隧。
2)奢人、表單操作完成后,腳本獲取表單的值淆院,組織格式后何乎,向后臺(tái)服務(wù)器提交數(shù)據(jù)。如下圖:
在Jquery
時(shí)代土辩,其為我們提供了操作DOM元素的常用API支救,幫我們封裝了提交和獲取數(shù)據(jù)的Ajax
請(qǐng)求API,使得我們可以輕松實(shí)現(xiàn)上面介紹的兩個(gè)過(guò)程拷淘。
<input type="submit" value="Jquery" id = "id"/>
//頁(yè)面加載請(qǐng)求數(shù)據(jù)并賦值
$(document).ready(function(){
// $.ajax("Get")
$("#id").val("Hello Jquery")
});
//頁(yè)面操作完成各墨,收集數(shù)據(jù),并提交
$("button").click(function(){
var id = $("#id").val();
// $.ajax("Post","/save",{id:id})
});
但是這種DOM操作往往過(guò)于繁瑣启涯,每個(gè)頁(yè)面元素都需要進(jìn)行賦值贬堵、收集數(shù)據(jù)等等一些列重復(fù)的過(guò)程。伴隨著框架設(shè)計(jì)模式的引入结洼,出現(xiàn)了類(lèi)似MVVM這樣的框架黎做,比如Vue
,就是從更高層次解決數(shù)據(jù)
和視圖
雙向同步更新的過(guò)程松忍。
解決問(wèn)題:主流前端架構(gòu)都在解決一個(gè)核心問(wèn)題:數(shù)據(jù)和視圖的關(guān)聯(lián)蒸殿。
2.1、Vue 的MVVM
// View
<template>
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
</div>
</template>
<script>
// Model
var data = {
message:"Hello Vue"
}
// ModelAndView
var app = new Vue({
el: '#app',
data: data,
methods:{
getData(){
//請(qǐng)求數(shù)據(jù)
this.message = 'Hello World'
},
saveDate(){
//發(fā)送數(shù)據(jù) this.message 已經(jīng)是最新值
}
},
mounted(){
this.getData()
}
})
</script>
<style>
#app {
padding: 0px;
margin: 0px;
}
</style>
Vue
底層幫我們實(shí)現(xiàn)了數(shù)據(jù)
和視圖
的雙重綁定,大概過(guò)程:
監(jiān)聽(tīng)數(shù)據(jù)更新視圖:
//遍歷數(shù)據(jù)的每一項(xiàng)宏所,設(shè)置數(shù)據(jù)監(jiān)聽(tīng)
Object.keys(data).forEach(function (key) {
defineReactive(vm, key, data[key]);
});
function defineReactive(obj, key, val) {
// 內(nèi)部監(jiān)聽(tīng)器
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
if (Dep.target) dep.addSub(Dep.target);
return val;
},
set: function (newVal) {
if (newVal === val) return;
val = newVal;
// 觸發(fā)數(shù)據(jù)更新通知酥艳,重新根據(jù)模板的渲染頁(yè)面(過(guò)程相對(duì)復(fù)雜,代碼略)
dep.notify();
},
});
}
操作視圖更新數(shù)據(jù):
SomeInput.addEventListener("input", function(event) {
vm["SomeInput"] = event.target.value;
})
當(dāng)然底層比這個(gè)復(fù)雜很多楣铁,會(huì)采用一些觀察者模式等設(shè)計(jì)模式玖雁,而且Vue
將模板編譯成虛擬 DOM
渲染函數(shù)Render
。并且通過(guò)算法能夠智能地計(jì)算出最少需要重新渲染多少組件盖腕,并把 DOM 操作次數(shù)減到最少赫冬。
2.2、React 的MVC
React
同樣也是來(lái)解決數(shù)據(jù)
和視圖
之間關(guān)聯(lián)的框架溃列,不過(guò)其并不像Vue
一樣采用雙向的數(shù)據(jù)綁定劲厌,而只處理數(shù)據(jù)到視圖的更新,也就是單向過(guò)程听隐。它的處理更加簡(jiǎn)潔补鼻,所有數(shù)據(jù)的更新統(tǒng)一走一個(gè)API:setState
。
// root 容器
<body>
<div id="root"></div>
</body>
// react
import React, { PureComponent } from 'react'
import { render } from "react-dom";
class SimpleDemo extends PureComponent {
constructor(props) {
super(props)
this.state = {
message:'react'
}
}
componentDidMount(){
//通過(guò)ajax請(qǐng)求獲取message數(shù)據(jù)
this.setState({message:'hello'})
}
onChangeHandle(event){
this.setState({message:event.target.value})
}
// 每次 state更新雅任,從新執(zhí)行該渲染函數(shù)
render() {
return (
<div>
<span>{this.state.message}</span>
<input onChange={this.onChangeHandle} type='text' ></input>
</div>
)
}
}
render(<SimpleDemo />, document.getElementById("root"));
數(shù)據(jù)和視圖的變更過(guò)程:
2.3风范、多頁(yè)和單頁(yè)應(yīng)用 Spa&Mpa
2.3.1、MPA
多頁(yè)面
多頁(yè)面應(yīng)用:每次頁(yè)面跳轉(zhuǎn)沪么,后臺(tái)都會(huì)返回一個(gè)新的html文檔硼婿,就是多頁(yè)面應(yīng)用。
在以往傳統(tǒng)開(kāi)發(fā)的應(yīng)用(網(wǎng)站)大多都是多頁(yè)面應(yīng)用禽车,路由由后端來(lái)寫(xiě)寇漫。
首屏?xí)r間快?訪問(wèn)頁(yè)面殉摔,服務(wù)器只需要返回一個(gè)html文件州胳,這個(gè)過(guò)程就經(jīng)歷了一個(gè)HTTP請(qǐng)求,請(qǐng)求響應(yīng)回來(lái)逸月,頁(yè)面就能被展示出來(lái)栓撞。
SEO(搜索引擎排名)效果好?搜索引擎能識(shí)別html的內(nèi)容碗硬,根據(jù)內(nèi)容進(jìn)行排名腐缤。
頁(yè)面切換慢:每一次切換頁(yè)面都需要發(fā)起一個(gè)HTTP請(qǐng)求,假設(shè)網(wǎng)絡(luò)較慢就會(huì)出現(xiàn)卡頓情況肛响。
2.3.2、SPA
單頁(yè)面
單頁(yè)應(yīng)用:用一個(gè)頁(yè)面搞定整個(gè)應(yīng)用的所有功能惜索。刷新頁(yè)面會(huì)請(qǐng)求一個(gè)html文件特笋,切換頁(yè)面的時(shí)候,并不會(huì)發(fā)起新的請(qǐng)求一個(gè)html文件,只是頁(yè)面內(nèi)容發(fā)生了變化猎物。
路由原理:JS監(jiān)聽(tīng)URL變化虎囚,當(dāng)URL發(fā)生變化后,使用JS動(dòng)態(tài)把當(dāng)前的頁(yè)面內(nèi)容清除掉蔫磨,再把下一個(gè)頁(yè)面的內(nèi)容掛載到頁(yè)面上淘讥。此時(shí)的路由就不是后端來(lái)做了,而是前端來(lái)做堤如,判斷頁(yè)面到底顯示哪一個(gè)組件蒲列,再把以前的組件清除掉使用新的組件。就不會(huì)每一次跳轉(zhuǎn)都請(qǐng)求html文件搀罢。
首屏?xí)r間慢蝗岖?請(qǐng)求html
還有js的請(qǐng)求。js的內(nèi)容相對(duì)比多頁(yè)應(yīng)用多榔至,頁(yè)面元素由前端腳本動(dòng)態(tài)組裝抵赢。
頁(yè)面切換快?頁(yè)面跳轉(zhuǎn)不需要去做HTML文件的請(qǐng)求唧取,節(jié)約HTTP請(qǐng)求發(fā)送的時(shí)延铅鲤。
SEO
差?搜索引擎只認(rèn)識(shí)HTML內(nèi)容不認(rèn)識(shí)js內(nèi)容枫弟。單頁(yè)應(yīng)用的渲染都是靠JavaScript渲染出來(lái)的邢享。搜索引擎不好識(shí)別排名。
公司未來(lái)平臺(tái)產(chǎn)品
Supfusion
一個(gè)模塊是一個(gè)單頁(yè)應(yīng)用
解決問(wèn)題:
單頁(yè)應(yīng)用
把前端開(kāi)發(fā)過(guò)程組織的更像是一個(gè)桌面軟件
開(kāi)發(fā)媒区,因?yàn)閷?duì)于后端來(lái)說(shuō)驼仪,就是一個(gè)html頁(yè)面,內(nèi)部所有的功能都是通過(guò)前端來(lái)組織袜漩,在這個(gè)時(shí)候绪爸,廣義上的一個(gè)頁(yè)面,對(duì)于單頁(yè)應(yīng)用
來(lái)說(shuō)就是一個(gè)組件宙攻,而之前頁(yè)面的概念對(duì)于用戶來(lái)說(shuō)奠货,最好是通過(guò)瀏覽器的地址來(lái)區(qū)分,于是乎座掘,前端的靜態(tài)路由
的概念出現(xiàn)了递惋,核心還是根據(jù)URL來(lái)區(qū)分如何加載前端組件。
示例:公司郵箱展示 Link
2.4溢陪、靜態(tài)路由
在現(xiàn)代前端開(kāi)發(fā)中萍虽,路由是非常重要的一環(huán)。但路由到底是什么呢形真?有些說(shuō):路由就是指隨著瀏覽器地址欄的變化杉编,展示給用戶的頁(yè)面也變化的過(guò)程。這是從路由的用途上來(lái)解釋路由是什么的,還有一種說(shuō)法是:路由就是URL到函數(shù)的映射邓馒。這是從路由的實(shí)現(xiàn)原理上來(lái)解釋路由是什么的嘶朱。這兩種說(shuō)法都很有道理,但我個(gè)人認(rèn)為還是第二種比較切合自己對(duì)路由的理解吧光酣。
而路由本身也經(jīng)歷了不同的發(fā)展階段:
后端路由
前端路由
后端路由又可稱(chēng)之為服務(wù)器端路由疏遏,因?yàn)閷?duì)于服務(wù)器來(lái)說(shuō),當(dāng)接收到客戶端發(fā)來(lái)的HTTP請(qǐng)求救军,就會(huì)根據(jù)所請(qǐng)求的相應(yīng)URL财异,來(lái)找到相應(yīng)的映射函數(shù)(MVC
部分的Controller
),然后執(zhí)行該函數(shù)缤言,并將函數(shù)的返回值(html/json/xml
)發(fā)送給客戶端宝当。對(duì)于最簡(jiǎn)單的靜態(tài)資源服務(wù)器,可以認(rèn)為胆萧,所有URL的映射函數(shù)就是一個(gè)文件讀取操作庆揩。對(duì)于動(dòng)態(tài)資源,映射函數(shù)可能是一個(gè)數(shù)據(jù)庫(kù)讀取操作跌穗,也可能是進(jìn)行一些數(shù)據(jù)的處理订晌,等等。然后根據(jù)這些讀取的數(shù)據(jù)蚌吸,在服務(wù)器端就使用相應(yīng)的模板來(lái)對(duì)頁(yè)面進(jìn)行渲染后锈拨,再返回渲染完畢的頁(yè)面。
SPA的出現(xiàn)羹唠,也伴隨著前端路由的出現(xiàn)奕枢。對(duì)于前端路由來(lái)說(shuō),路由的映射函數(shù)通常是進(jìn)行一些DOM的顯示和隱藏操作佩微。這樣缝彬,當(dāng)訪問(wèn)不同的路徑的時(shí)候,會(huì)顯示不同的頁(yè)面組件哺眯。前端路由主要有以下兩種實(shí)現(xiàn)方案:
hash
history
我們常用的諸如react-router
等前端框架的路由控制都是基于前端路由進(jìn)行開(kāi)發(fā)的谷浅,因此將前端路由進(jìn)行一個(gè)了解還是很有必要的。下面就兩種方式奶卓,分別介紹下基本的原理一疯。
2.4.1、hash 的歷史
最開(kāi)始的網(wǎng)頁(yè)是多頁(yè)面的夺姑,后來(lái)出現(xiàn)了 Ajax 之后墩邀,才慢慢有了 SPA。然而盏浙,那時(shí)候的 SPA 有兩個(gè)弊端:
- 用戶在使用的過(guò)程中磕蒲,url 不會(huì)發(fā)生任何改變留潦。當(dāng)用戶操作了幾步之后,一不小心刷新了頁(yè)面辣往,又會(huì)回到最開(kāi)始的狀態(tài)。那個(gè)年代這種僅僅靠Ajax更新內(nèi)容的方式叫
局部刷新
殖卑。 - 由于缺乏 url站削,不方便搜索引擎進(jìn)行收錄。怎么辦呢孵稽?
hash
https://developer.mozilla.org/en-US/docs/Web/API/URL/href#Examples
通過(guò)哈希字符串:
#Exaples
许起,定位到頁(yè)面具體元素。
url 上的 hash 本意是用來(lái)作錨點(diǎn)的菩鲜,方便用戶在一個(gè)很長(zhǎng)的文檔里進(jìn)行上下的導(dǎo)航园细,用來(lái)做 SPA 的路由控制并非它的本意。然而接校,hash 滿足這么一種特性:改變 url 的同時(shí)猛频,不刷新頁(yè)面,再加上瀏覽器也提供 onhashchange 這樣的事件監(jiān)聽(tīng)蛛勉,因此鹿寻,hash 能用來(lái)做路由控制。后來(lái)诽凌,這種模式大行其道毡熏,onhashchange
也就被寫(xiě)進(jìn)了 HTML5
規(guī)范當(dāng)中去了。
下面舉個(gè)例子侣诵,演示“通過(guò)改變 hash 值痢法,對(duì)頁(yè)面進(jìn)行局部刷新”:
<ul>
<li><a href="#/">turn white</a></li>
<li><a href="#/blue">turn blue</a></li>
<li><a href="#/green">turn green</a></li>
</ul>
function Router() {
this.routes = {};
this.currentUrl = '';
}
Router.prototype.route = function (path, callback) {
this.routes[path] = callback || function () {};
};
Router.prototype.refresh = function () {
console.log('觸發(fā)一次 hashchange,hash 值為', location.hash);
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();
};
Router.prototype.init = function () {
window.addEventListener('load', this.refresh.bind(this), false);
window.addEventListener('hashchange', this.refresh.bind(this), false);
};
window.Router = new Router();
window.Router.init();
var content = document.querySelector('body');
// change Page anything
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route('/', function () {
changeBgColor('white');
});
Router.route('/blue', function () {
changeBgColor('blue');
});
Router.route('/green', function () {
changeBgColor('green');
});
通過(guò) hash 的改變來(lái)對(duì)頁(yè)面進(jìn)行局部刷新杜顺。尤其需要注意的是:在第一次進(jìn)入頁(yè)面的時(shí)候财搁,如果 url 上已經(jīng)帶有 hash,那么也會(huì)觸發(fā)一次onhashchange
事件哑舒,這保證了一開(kāi)始的 hash 就能被識(shí)別妇拯。
問(wèn)題:雖然 hash 解決了 SPA 路由控制的問(wèn)題,但是它又引入了新的問(wèn)題 :url 上會(huì)有一個(gè) # 號(hào)洗鸵,很不美觀越锈,解決方案:拋棄 hash,使用 history
2.4.2膘滨、history 的演進(jìn)
很早以前甘凭,瀏覽器便實(shí)現(xiàn)了 history。然而火邓,早期的 history 只能用于多頁(yè)面進(jìn)行跳轉(zhuǎn)丹弱,比如:
// 這部分可參考紅寶書(shū) P215
history.go(-1); // 后退一頁(yè)
history.go(2); // 前進(jìn)兩頁(yè)
history.forward(); // 前進(jìn)一頁(yè)
history.back(); // 后退一頁(yè)
在 HTML5
規(guī)范中德撬,history 新增了以下幾個(gè) API
history.pushState(); // 添加新的狀態(tài)到歷史狀態(tài)棧
history.replaceState(); // 用新的狀態(tài)代替當(dāng)前狀態(tài)
history.state // 返回當(dāng)前狀態(tài)對(duì)象
通過(guò)history.pushState
或者history.replaceState
,也能做到:改變 url 的同時(shí)躲胳,不會(huì)刷新頁(yè)面蜓洪。所以 history 也具備實(shí)現(xiàn)路由控制的潛力。然而坯苹,還缺一點(diǎn):hash 的改變會(huì)觸發(fā) onhashchange
事件隆檀,history 的改變會(huì)觸發(fā)什么事件呢? 很遺憾粹湃,沒(méi)有恐仑。
怎么辦呢?雖然我們無(wú)法監(jiān)聽(tīng)到 history 的改變事件为鳄,然而裳仆,如果我們能羅列出所有可能改變 history 的途徑,然后在這些途徑一一進(jìn)行攔截孤钦,不也一樣相當(dāng)于監(jiān)聽(tīng)了 history 的改變嗎歧斟?
對(duì)于一個(gè)應(yīng)用而言,url 的改變只能由以下 3 種途徑引起:
1司训、點(diǎn)擊瀏覽器的前進(jìn)或者后退按鈕构捡;
2、點(diǎn)擊 a 標(biāo)簽壳猜;
3勾徽、在 JS 代碼中直接修改路由。
第 2 和第 3 種途徑可以看成是一種统扳,因?yàn)?a 標(biāo)簽的默認(rèn)事件可以被禁止喘帚,進(jìn)而調(diào)用 JS 方法。關(guān)鍵是第 1 種咒钟,HTML5
規(guī)范中新增了一個(gè) onpopstate 事件吹由,通過(guò)它便可以監(jiān)聽(tīng)到前進(jìn)或者后退按鈕的點(diǎn)擊。
要特別注意的是:調(diào)用history.pushState
和history.replaceState
并不會(huì)觸發(fā) onpopstate
事件朱嘴。
window.history.pushState(state, title, url)
window.history.replaceState(state, title, url)
// 與 pushState 基本相同倾鲫,但她是修改當(dāng)前歷史記錄,而 pushState 是創(chuàng)建新的歷史記錄
window.addEventListener("popstate", function() {
// 監(jiān)聽(tīng)瀏覽器前進(jìn)后退事件萍嬉,pushState 與 replaceState 方法不會(huì)觸發(fā)
});
window.history.back() // 后退
window.history.forward() // 前進(jìn)
window.history.go(1) // 前進(jìn)一步乌昔,-2為后退兩步,window.history.lengthk可以查看當(dāng)前歷史堆棧中頁(yè)面的數(shù)量
總結(jié):經(jīng)過(guò)上面的分析壤追,history 是可以用來(lái)進(jìn)行路由控制的磕道,只不過(guò)需要從 3 方面進(jìn)行著手。當(dāng)然后端需要支持行冰,任何前端的404
請(qǐng)求都引導(dǎo)index.html
的機(jī)制溺蕉,因?yàn)榍岸藷o(wú)法控制用戶手工刷新當(dāng)前頁(yè)面伶丐。
React路由簡(jiǎn)單示例
// 開(kāi)源Route項(xiàng)目截取的實(shí)現(xiàn)邏輯
let instances = []; // 用來(lái)存儲(chǔ)頁(yè)面中的 Router
const register = (comp) => instances.push(comp);
const unRegister = (comp) => instances.splice(instances.indexOf(comp), 1);
const historyPush = (path) => {
window.history.pushState({}, null, path);
instances.forEach(instance => instance.forceUpdate())
};
window.addEventListener('popstate', () => {
// 遍歷所有 Route,強(qiáng)制重新渲染所有 Route
instances.forEach(instance => instance.forceUpdate());
});
export class Route extends Component {
static propTypes = {
path: PropTypes.string,
component: PropTypes.func,
exact: PropTypes.bool
};
componentWillMount() {register(this);}
render() {
const {path, component, exact} = this.props;
const match = matchPath(window.location.pathname, {path, exact});
// Route 跟當(dāng)前 url 不匹配疯特,就返回 null
if (!match) return null;
if (component) {
return React.createElement(component);
}
}
componentWillUnMount() {unRegister(this);}
}
總結(jié):前端路由解決了
單頁(yè)應(yīng)用
內(nèi)部頁(yè)面的跳轉(zhuǎn)問(wèn)題哗魂,同時(shí)有組織了路由和組件的關(guān)聯(lián)關(guān)系。
2.5辙芍、虛擬DOM
從Vue
和React
的數(shù)據(jù)更新到視圖更新啡彬,兩個(gè)框架都采用了虛擬Dom這樣一種機(jī)制,主要為了差異化更新真實(shí)Dom故硅,避免因?yàn)镈OM更新過(guò)多或者頻繁引起頁(yè)面渲染性能問(wèn)題。
“計(jì)算機(jī)科學(xué)領(lǐng)域的任何問(wèn)題都可以通過(guò)增加一個(gè)間接的中間層來(lái)解決”
“Any problem in computer science can be solved by anther layer of indirection.”
一個(gè)使用虛擬Dom的簡(jiǎn)單場(chǎng)景纵搁,如果用jquery
展示一個(gè)表格:
<div id="container"></div>
<button id="btn-change">change</button>
<script type="text/javascript" src="./jquery.js"></script>
<script type="text/javascript">
var data = [
{
name: '張三',
age: '20',
address: '北京'
},
{
name: '李四',
age: '21',
address: '上海'
},
{
name: '王五',
age: '22',
address: '廣州'
}
]
// 渲染函數(shù)
function render(data) {
var $container = $('#container')
// 清空容器吃衅,重要!L谟徘层!
$container.html('')
// 拼接 table
var $table = $('<table>')
$table.append($('<tr><td>name</td><td>age</td><td>address</td>/tr>'))
data.forEach(function (item) {
$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td>/tr>'))
})
// 渲染到頁(yè)面
$container.append($table)
}
$('#btn-change').click(function () {
data[1].age = 30
data[2].address = '深圳'
// re-render 再次渲染,整個(gè)表格會(huì)全部重新渲染
render(data)
})
// 頁(yè)面加載完立刻執(zhí)行(初次渲染)
render(data)
</script>
如果使用虛擬DOM:
// VDom 的 snabbdom庫(kù)
var vnode = h('ul#list',{},[
h('li.item',{},'item1'),
h('li.item',{},'item2')
])
var container = document.getElementById('container');
patch(container,vnode);
//模擬改變
var btnChange = document.getElementById('btnChange');
btnChange.addEventListener('click',function(){
var newVnode = h('ul#list',{},[
h('li.item',{},'item1'),
h('li.item',{},'item222')
])
patch(vnode,newVnode); //vnode和newVnode對(duì)比利职,僅僅更新需要更新的部分
})
2.5.1趣效、Vue虛擬DOM原理
Vue.js
通過(guò)編譯將template 模板轉(zhuǎn)換成渲染函數(shù)(render ) ,執(zhí)行渲染函數(shù)就可以得到一個(gè)虛擬節(jié)點(diǎn)樹(shù)猪贪。在對(duì) Model 進(jìn)行操作的時(shí)候跷敬,會(huì)觸發(fā)對(duì)應(yīng)
Dep
中的 Watcher 對(duì)象。Watcher 對(duì)象會(huì)調(diào)用對(duì)應(yīng)的 update 來(lái)修改視圖热押。這個(gè)過(guò)程主要是將新舊虛擬節(jié)點(diǎn)進(jìn)行差異對(duì)比西傀,然后根據(jù)對(duì)比結(jié)果進(jìn)行DOM操作來(lái)更新視圖。
簡(jiǎn)單點(diǎn)講桶癣,在Vue
的底層實(shí)現(xiàn)上拥褂,Vue
將模板編譯成虛擬DOM渲染函數(shù)。結(jié)合Vue
自帶的響應(yīng)系統(tǒng)牙寞,在狀態(tài)改變時(shí)饺鹃,Vue
能夠智能地計(jì)算出重新渲染組件的最小代價(jià)并應(yīng)到DOM操作上。
上圖幾個(gè)概念加以解釋:
渲染函數(shù)
:渲染函數(shù)是用來(lái)生成Virtual DOM的间雀。Vue推薦使用模板來(lái)構(gòu)建我們的應(yīng)用界面悔详,在底層實(shí)現(xiàn)中Vue會(huì)將模板編譯成渲染函數(shù),當(dāng)然我們也可以不寫(xiě)模板雷蹂,直接寫(xiě)渲染函數(shù)伟端,以獲得更好的控制。
VNode 虛擬節(jié)點(diǎn)
:它可以代表一個(gè)真實(shí)的 DOM節(jié)點(diǎn)匪煌。通過(guò) createElement 方法能將 VNode 渲染成 DOM節(jié)點(diǎn)责蝠。簡(jiǎn)單地說(shuō)党巾,vnode可以理解成節(jié)點(diǎn)描述對(duì)象,它描述了應(yīng)該怎樣去創(chuàng)建真實(shí)的DOM節(jié)點(diǎn)霜医。
patch(也叫做patching算法)
:虛擬DOM最核心的部分齿拂,它可以將vnode渲染成真實(shí)的DOM,這個(gè)過(guò)程是對(duì)比新舊虛擬節(jié)點(diǎn)之間有哪些不同肴敛,然后根據(jù)對(duì)比結(jié)果找出需要更新的的節(jié)點(diǎn)進(jìn)行更新署海。這點(diǎn)我們從單詞含義就可以看出, patch本身就有補(bǔ)丁医男、修補(bǔ)的意思砸狞,其實(shí)際作用是在現(xiàn)有DOM上進(jìn)行修改來(lái)實(shí)現(xiàn)更新視圖的目的。Vue的Virtual DOM Patching算法是基于Snabbdom
的實(shí)現(xiàn)镀梭,并在些基礎(chǔ)上作了很多的調(diào)整和改進(jìn)刀森。
Virtual DOM 是什么?
Virtual DOM 其實(shí)就是一棵以 JavaScript 對(duì)象( VNode 節(jié)點(diǎn))作為基礎(chǔ)的樹(shù)报账,用對(duì)象屬性來(lái)描述節(jié)點(diǎn)研底,實(shí)際上它只是一層對(duì)真實(shí) DOM 的抽象。最終可以通過(guò)一系列操作使這棵樹(shù)映射到真實(shí)環(huán)境上透罢。
簡(jiǎn)單來(lái)說(shuō)榜晦,可以把Virtual DOM 理解為一個(gè)簡(jiǎn)單的JS對(duì)象,并且最少包含標(biāo)簽名( tag)羽圃、屬性(attrs)和子元素對(duì)象( children)三個(gè)屬性乾胶。不同的框架對(duì)這三個(gè)屬性的命名會(huì)有點(diǎn)差別。
對(duì)于虛擬DOM统屈,咱們來(lái)看一個(gè)簡(jiǎn)單的實(shí)例胚吁,就是下圖所示的這個(gè),詳細(xì)的闡述了模板 → 渲染函數(shù) → 虛擬DOM樹(shù) → 真實(shí)DOM的一個(gè)過(guò)程:
Virtual DOM 作用是什么愁憔?
虛擬DOM的最終目標(biāo)是將虛擬節(jié)點(diǎn)渲染到視圖上腕扶。但是如果直接使用虛擬節(jié)點(diǎn)覆蓋舊節(jié)點(diǎn)的話,會(huì)有很多不必要的DOM操作吨掌。例如半抱,一個(gè)ul
標(biāo)簽下很多個(gè)li
標(biāo)簽,其中只有一個(gè)li
有變化膜宋,這種情況下如果使用新的ul
去替代舊的ul
,因?yàn)檫@些不必要的DOM操作而造成了性能上的浪費(fèi)窿侈。
為了避免不必要的DOM操作,虛擬DOM在虛擬節(jié)點(diǎn)映射到視圖的過(guò)程中秋茫,將虛擬節(jié)點(diǎn)與上一次渲染視圖所使用的舊虛擬節(jié)點(diǎn)(oldVnode
)做對(duì)比史简,找出真正需要更新的節(jié)點(diǎn)來(lái)進(jìn)行DOM操作,從而避免操作其他無(wú)需改動(dòng)的DOM肛著。
其實(shí)虛擬DOM在Vue.js主要做了兩件事:
提供與真實(shí)DOM節(jié)點(diǎn)所對(duì)應(yīng)的虛擬節(jié)點(diǎn)vnode
將虛擬節(jié)點(diǎn)vnode和舊虛擬節(jié)點(diǎn)oldVnode進(jìn)行對(duì)比圆兵,然后更新視圖
為何需要Virtual DOM跺讯?
具備跨平臺(tái)的優(yōu)勢(shì)
由于 Virtual DOM 是以 JavaScript 對(duì)象為基礎(chǔ)而不依賴真實(shí)平臺(tái)環(huán)境,所以使它具有了跨平臺(tái)的能力殉农,比如說(shuō)瀏覽器平臺(tái)刀脏、Weex、Node 等超凳。操作 DOM 慢愈污,js運(yùn)行效率高。我們可以將DOM對(duì)比操作放在JS層轮傍,提高效率暂雹。
因?yàn)镈OM操作的執(zhí)行速度遠(yuǎn)不如Javascript的運(yùn)算速度快,因此创夜,把大量的DOM操作搬運(yùn)到Javascript中擎析,運(yùn)用patching算法來(lái)計(jì)算出真正需要更新的節(jié)點(diǎn),最大限度地減少DOM操作挥下,從而顯著提高性能。
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個(gè)緩存桨醋∨镂粒可以類(lèi)比 CPU 和硬盤(pán),既然硬盤(pán)這么慢喜最,我們就在它們之間加個(gè)緩存:既然 DOM 這么慢偎蘸,我們就在它們 JS 和 DOM 之間加個(gè)緩存。CPU(JS)只操作內(nèi)存(Virtual DOM)瞬内,最后的時(shí)候再把變更寫(xiě)入硬盤(pán)(DOM)
- 提升渲染性能
Virtual DOM的優(yōu)勢(shì)不在于單次的操作迷雪,而是在大量、頻繁的數(shù)據(jù)更新下虫蝶,能夠?qū)σ晥D進(jìn)行合理章咧、高效的更新。
為了實(shí)現(xiàn)高效的DOM操作能真,一套高效的虛擬DOM diff
算法顯得很有必要赁严。我們通過(guò)patch 的核心----diff
算法,找出本次DOM需要更新的節(jié)點(diǎn)來(lái)更新粉铐,其他的不更新疼约。比如修改某個(gè)model 100次,從1加到100蝙泼,那么有了Virtual DOM的緩存之后程剥,只會(huì)把最后一次修改patch到view上。那diff
算法的實(shí)現(xiàn)過(guò)程是怎樣的汤踏?
React Diff
算法基于三個(gè)策略
Web UI 中 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少织鲸,可以忽略不計(jì)舔腾。
擁有相同類(lèi)的兩個(gè)組件將會(huì)生成相似的樹(shù)形結(jié)構(gòu),擁有不同類(lèi)的兩個(gè)組件將會(huì)生成不同的樹(shù)形結(jié)
對(duì)于同一層級(jí)的一組子節(jié)點(diǎn)昙沦,它們可以通過(guò)唯一 id 進(jìn)行區(qū)分琢唾。
Link : React Diff 算法
總結(jié):虛擬DOM解決了
javascript
頻繁更新Dom的性能問(wèn)題,在React內(nèi)部盾饮,虛擬DOM甚至可以合并同一個(gè)節(jié)點(diǎn)的多次更新采桃,更加減少操作DOM的次數(shù),另外React還可以利用瀏覽器每個(gè)刷新貞的空閑時(shí)間更新DOM丘损,來(lái)解決腳本和UI渲染之間的資源競(jìng)爭(zhēng)普办。
3、前端構(gòu)建演進(jìn)
通過(guò)以上介紹徘钥,單頁(yè)面引用導(dǎo)致了大量
頁(yè)面
現(xiàn)在已經(jīng)成為了一個(gè)一個(gè)組件
衔蹲,靜態(tài)路由來(lái)組織組件的呈現(xiàn)和隱藏,虛擬DOM形成了組件和真實(shí)頁(yè)面的橋梁呈础。當(dāng)單頁(yè)應(yīng)用的內(nèi)容更加龐大以后舆驶,如何組織這些組件,已經(jīng)成為了一個(gè)難題而钞。于是乎沙廉,前端出現(xiàn)了一個(gè)概念三化
,也就是下文的模塊化
臼节、工程化
撬陵、組件化
。
示例:
supplant-app-template
3.1网缝、模塊化
在模塊化概念出現(xiàn)以前巨税,我們的代碼是這樣的:
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>項(xiàng)目領(lǐng)用臺(tái)賬</title>
<script type="text/javascript" src="/bap/struts/res/jquery.js"></script>
<script type="text/javascript" src="/bap/struts/res/zh_cn/core.js"></script>
<script type="text/javascript" src="/bap/struts/res/zh_cn/main.js"></script>
<script type="text/javascript" src="/bap/struts/supdatagrid/supdatagrid.js"></script>
</head>
<body>
....
</body>
</html>
頁(yè)面引用了一堆js
文件,這些文件內(nèi)部可能有依賴關(guān)系粉臊,也有可能同時(shí)定義的全局對(duì)象草添,一不小心,就容易相互覆蓋维费。
// core.js
window.some = {}
//main.js
window.some = [1,2,3]
//main.js比core.js 后加載果元,最后window.some就是數(shù)組[1,2,3]
為了實(shí)現(xiàn)像服務(wù)器端語(yǔ)言一樣,按需的使用各個(gè)代碼塊犀盟,前端語(yǔ)言也引入了模塊化
的概念而晒。
一個(gè)模塊就是一個(gè)實(shí)現(xiàn)特定功能的文件,有了模塊我們就可以更方便的使用別人的代碼阅畴,要用什么功能就加載什么模塊倡怎。
模塊化開(kāi)發(fā)的好處:
避免變量污染,命名沖突
通過(guò)代碼分離混驰,提高代碼復(fù)用率
提高維護(hù)性
依賴關(guān)系的管理
為了實(shí)現(xiàn)模塊化屎蜓,出現(xiàn)了用于JavaScript模塊管理的兩大流行規(guī)范:commonJS
和AMD
寇损。
前者定義的是模塊的同步加載猖辫,主要用于Node.js。同步加載在前端會(huì)導(dǎo)致整個(gè)頁(yè)面等待蠢挡,對(duì)前端并不適用嫩与,便出現(xiàn)了AMD
涕刚。AMD
采用異步加載方式晓避,通過(guò)RequireJS
等工具適用于前端簇捍。
以RequireJS
為例,這是一種在線“編譯”模塊的方案俏拱,相當(dāng)于在瀏覽器中先加載一個(gè)AMD
解釋器暑塑,使瀏覽器認(rèn)識(shí)define、export锅必、module等相關(guān)命令事格,來(lái)實(shí)現(xiàn)模塊化。后來(lái)ES6提供了對(duì)模塊化的原生支持搞隐,它的目標(biāo)是創(chuàng)建一種CommonJS
和AMD
使用者都愿意接受的方式驹愚,即擁有簡(jiǎn)潔的語(yǔ)法,又支持異步加載和配置模塊加載劣纲。
AMD示例
:
//<script type="text/javascript" data-main="js/script/main" src="js/lib/require.js"></script>
require(['jquery'], function ($){
alert($);
});
為了讓模塊化開(kāi)發(fā)的代碼么鹤,可以打包在一起并且在瀏覽器上運(yùn)行,出現(xiàn)了一些打包模塊的構(gòu)建工具味廊,webpack
是一個(gè)預(yù)編譯模塊的方案。在發(fā)布前預(yù)編譯好棠耕,不需要在瀏覽器中加載解釋器余佛。另外,直接寫(xiě)AMD
或ES6
的模塊化代碼窍荧,它都能編譯成瀏覽器識(shí)別的JavaScript代碼辉巡。甚至CommonJS
規(guī)范的模塊化,webpack
也可以轉(zhuǎn)換成瀏覽器使用的形式蕊退。
Link : Webpack
npm包和npm倉(cāng)庫(kù)
伴隨著模塊化的程度不斷加深郊楣,前端各種各樣的第三方模塊層出不窮,前端也出現(xiàn)了類(lèi)似java
領(lǐng)域的Maven倉(cāng)庫(kù)一樣的包管理庫(kù)NPM瓤荔。已經(jīng)有相當(dāng)豐富的第三方開(kāi)源腳本庫(kù)上傳到了NPM倉(cāng)庫(kù)內(nèi)净蚤,這也是前端發(fā)展如此快速的原因之一。
總結(jié):模塊化來(lái)解決腳本如何拆分和組合输硝,讓單頁(yè)面的組件被復(fù)用成為了可能今瀑。
3.2、工程化
將前端項(xiàng)目當(dāng)成一項(xiàng)系統(tǒng)工程進(jìn)行分析、組織和構(gòu)建從而達(dá)到項(xiàng)目結(jié)構(gòu)清晰橘荠、分工明確屿附、團(tuán)隊(duì)配合默契、開(kāi)發(fā)效率提高的目的哥童。
還記得我在最早期寫(xiě)前端代碼時(shí),往往一個(gè)頁(yè)面就是一個(gè)文件搞定,HTML/CSS/JS全部寫(xiě)在一起,后來(lái)知道應(yīng)該把結(jié)構(gòu)挺份、樣式和動(dòng)作分離,我想這是我接觸到最早的前端工程化的思想了,所謂前端工程化我認(rèn)為就是將前端項(xiàng)目當(dāng)成一項(xiàng)系統(tǒng)工程進(jìn)行分析、組織和構(gòu)建從而達(dá)到項(xiàng)目結(jié)構(gòu)清晰贮懈、分工明確匀泊、團(tuán)隊(duì)配合默契、開(kāi)發(fā)效率提高的目的错邦。
工程化是一種思想而不是某種技術(shù)(當(dāng)然為了實(shí)現(xiàn)工程化我們會(huì)用一些技術(shù)),這樣說(shuō)還不夠具體,舉個(gè)例子來(lái)說(shuō):
要蓋一棟大樓,假如我們不進(jìn)行工程化的考量那就是一上來(lái)掂起瓦刀探赫、磚塊就開(kāi)干,直到把大樓壘起來(lái),這樣做往往意味著中間會(huì)出現(xiàn)錯(cuò)誤,要推倒重來(lái)或是蓋好以后結(jié)構(gòu)有問(wèn)題但又不知道出現(xiàn)在哪誰(shuí)的責(zé)任甚至?xí)谀骋惶燹Z然倒塌,那我們?nèi)绻霉こ袒乃枷肴プ?就會(huì)先畫(huà)圖紙、確定結(jié)構(gòu)撬呢、確定用料和預(yù)算以及工期,另外需要用到什么工種多少人等等,我們會(huì)先打地基再建框架再填充墻體這樣最后建立起來(lái)的高樓才是穩(wěn)固的合規(guī)的,什么地方出了問(wèn)題我們也能找到源頭和負(fù)責(zé)人伦吠。
前面我說(shuō)接觸最早的工程化思維就是“結(jié)構(gòu)、樣式和動(dòng)作分離”,在只有若干個(gè)頁(yè)面的小型項(xiàng)目我們只需要用這些簡(jiǎn)單的做法就能把項(xiàng)目很好的組織起來(lái),但是在一個(gè)大型web項(xiàng)目中往往有更加復(fù)雜的結(jié)構(gòu)和非常多的頁(yè)面需要很多人甚至是多個(gè)團(tuán)隊(duì)配合才能把項(xiàng)目做完,我們需要有更加嚴(yán)謹(jǐn)和復(fù)雜的工程化思維去組織結(jié)構(gòu)魂拦。從更高層面的項(xiàng)目組織來(lái)看我們要做項(xiàng)目的各種規(guī)范毛仪、技術(shù)選型、項(xiàng)目構(gòu)建優(yōu)化等等,在代碼層面我們還需要用到JS/CSS模塊機(jī)芯勘、UI組件化等開(kāi)發(fā)方式箱靴。
再用一句通俗的話來(lái)概括前端工程化:前端工程化就是用做工程的思維看待和開(kāi)發(fā)自己的項(xiàng)目,而不再是直接擼起袖子一個(gè)頁(yè)面一個(gè)頁(yè)面開(kāi)寫(xiě)。
package.json
基于現(xiàn)在流行的前端工程荷愕,都是由npm包管理的衡怀,內(nèi)部有個(gè)package.json,系統(tǒng)的管理著每個(gè)項(xiàng)目的依賴安疗、打包過(guò)程抛杨、版本等等信息,這也類(lèi)似java
里面的pom.xml
荐类。
總結(jié):工程化讓復(fù)雜單頁(yè)面應(yīng)用的開(kāi)發(fā)怖现、構(gòu)建和部署成為了一套體系。
3.3玉罐、組件化
頁(yè)面上的每個(gè)獨(dú)立的屈嗤、可視/可交互區(qū)域視為一個(gè)組件,每個(gè)組件對(duì)應(yīng)一個(gè)工程目錄吊输,組件所需的各種資源都在這個(gè)目錄下就近維護(hù)饶号;
由于組件具有獨(dú)立性,因此組件與組件之間可以自由組合季蚂;
頁(yè)面不過(guò)是組件的容器讨韭,負(fù)責(zé)組合組件形成功能完整的界面脂信;
組件化將頁(yè)面視為一個(gè)容器,頁(yè)面上各個(gè)獨(dú)立部分例如:頭部、導(dǎo)航透硝、焦點(diǎn)圖狰闪、側(cè)邊欄、底部等視為獨(dú)立組件,不同的頁(yè)面根據(jù)內(nèi)容的需要,去盛放相關(guān)組件即可組成完整的頁(yè)面濒生。
模塊化和組件化一個(gè)最直接的好處就是復(fù)用埋泵,同時(shí)我們也應(yīng)該有一個(gè)理念,模塊化和組件化除了復(fù)用之外還有就是分治罪治,我們能夠在不影響其他代碼的情況下按需修改某一獨(dú)立的模塊或是組件丽声,因此很多地方我們及時(shí)沒(méi)有很強(qiáng)烈的復(fù)用需要也可以根據(jù)分治需求進(jìn)行模塊化或組件化開(kāi)發(fā)。
總結(jié):組件化已經(jīng)成為設(shè)計(jì)一個(gè)復(fù)雜前端應(yīng)用必備的過(guò)程觉义。
4雁社、內(nèi)部前端技術(shù)棧
SIP
Jquery 、Easyui
Supplant PC
React晒骇、Ant Design React霉撵、Jquery
Supplant Mobile
Vue、Element UI洪囤、原生webView
Supfusion
React徒坡、Ant Design React
Heath-Code Mobile
React、Ant Design Mobile瘤缩、原生webView
Heath-Code PC
React喇完、Ant Design React