[內(nèi)部]前端技術(shù)淺談

前端技術(shù)淺談

content.png

1线召、前端框架發(fā)展

前端技術(shù)演進(jìn).png

以銅為鑒,可以正衣冠多矮;以人為鑒缓淹,可以明得失;以史為鑒塔逃,可以知興替讯壶。

本文主題:前端開(kāi)發(fā)如何借助對(duì)HTMLJavascript不同的使用方式,來(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)容更加靈活段化,以PHPJSP凤类、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.png

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)品:GmailGoogle 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ì)AjaxDom操作有差異性乔外。為了解決瀏覽器兼容性問(wèn)題床三,DojojQuery袁稽、YUI勿璃、ExtJS等前端Framework相繼誕生。前端開(kāi)發(fā)人員用這些Framework頻繁發(fā)送AJAX請(qǐng)求到后臺(tái)推汽,在得到數(shù)據(jù)后补疑,再用這些Framework更新DOM樹(shù)。

前端庫(kù)時(shí)代.png

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中的新特性容为。隨之祖秒,BabelTypeScript逐漸流行起來(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)了:

MVVM.png

隨著這些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等一些前端概念洋机。

三劍客.jpg

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)用興起

隨著iOSAndroid等智能手機(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)。

健康碼.jpg
  • 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不算的話):VueReact,他們核心都是解決一個(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ù)。如下圖:

表單常用過(guò)程.png

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

vue-mvvm.png
// 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ò)程:

React.png

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ě)寇漫。


MPA.jpg

首屏?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文件搀罢。


SPA.jpg

首屏?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');
});

hash-demo.gif

通過(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.pushStatehistory.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);}
}
React路由.png

總結(jié):前端路由解決了單頁(yè)應(yīng)用內(nèi)部頁(yè)面的跳轉(zhuǎn)問(wèn)題哗魂,同時(shí)有組織了路由和組件的關(guān)聯(lián)關(guān)系。

2.5辙芍、虛擬DOM

VueReact的數(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操作上。

vDom.png

上圖幾個(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ò)程:

vdom2.png
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ò)程是怎樣的汤踏?

diff.png

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ū)分琢唾。

diff2.png

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ī)范:commonJSAMD寇损。

前者定義的是模塊的同步加載猖辫,主要用于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)建一種CommonJSAMD使用者都愿意接受的方式驹愚,即擁有簡(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ě)AMDES6的模塊化代碼窍荧,它都能編譯成瀏覽器識(shí)別的JavaScript代碼辉巡。甚至CommonJS規(guī)范的模塊化,webpack也可以轉(zhuǎn)換成瀏覽器使用的形式蕊退。

Link : Webpack

webpack.png

npm包和npm倉(cāng)庫(kù)

npm2.png

伴隨著模塊化的程度不斷加深郊楣,前端各種各樣的第三方模塊層出不窮,前端也出現(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ā)展如此快速的原因之一。

npm.png
npms.png

總結(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ā)方式箱靴。

分工.png

再用一句通俗的話來(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è)面濒生。

組件化.jpg

模塊化和組件化一個(gè)最直接的好處就是復(fù)用埋泵,同時(shí)我們也應(yīng)該有一個(gè)理念,模塊化和組件化除了復(fù)用之外還有就是分治罪治,我們能夠在不影響其他代碼的情況下按需修改某一獨(dú)立的模塊或是組件丽声,因此很多地方我們及時(shí)沒(méi)有很強(qiáng)烈的復(fù)用需要也可以根據(jù)分治需求進(jìn)行模塊化或組件化開(kāi)發(fā)。

Link :React 組件設(shè)計(jì)哲學(xué)

總結(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

5、前端技術(shù)書(shū)籍推薦

book.jpg

6剥啤、溝通&交流

timg.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锦溪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子府怯,更是在濱河造成了極大的恐慌海洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件富腊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡域帐,警方通過(guò)查閱死者的電腦和手機(jī)赘被,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肖揣,“玉大人民假,你說(shuō)我怎么就攤上這事×牛” “怎么了羊异?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我野舶,道長(zhǎng)易迹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任平道,我火速辦了婚禮睹欲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘一屋。我一直安慰自己窘疮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布冀墨。 她就那樣靜靜地躺著闸衫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诽嘉。 梳的紋絲不亂的頭發(fā)上蔚出,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音含懊,去河邊找鬼身冬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛岔乔,可吹牛的內(nèi)容都是我干的酥筝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雏门,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘿歌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起茁影,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宙帝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后募闲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體步脓,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年浩螺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了靴患。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡要出,死狀恐怖鸳君,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情患蹂,我是刑警寧澤或颊,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布砸紊,位于F島的核電站,受9級(jí)特大地震影響囱挑,放射性物質(zhì)發(fā)生泄漏醉顽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一看铆、第九天 我趴在偏房一處隱蔽的房頂上張望徽鼎。 院中可真熱鬧,春花似錦弹惦、人聲如沸否淤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)石抡。三九已至,卻和暖如春助泽,著一層夾襖步出監(jiān)牢的瞬間啰扛,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工嗡贺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隐解,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓诫睬,卻偏偏與公主長(zhǎng)得像煞茫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摄凡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345