從前端模組化程式設計切入想聊聊前端的未來(文末有我想問的問題)

Fengmaybe發表於2019-03-08

1. JavaScript模組化簡述?

1.1 為什麼需要模組化

  1. 沒有模組化前的專案,常常在一個JS檔案中會有很多功能的程式碼,這使得檔案很大,分類性不強,自然而然不易維護;
  2. 那麼我們將一個大的JS檔案根據一定的規範拆分成幾個小的檔案的話將會便於管理,可以提高複用性,隨之,可以起到分治的效果;
  3. 一個複雜的專案肯定有很多相似的功能模組,如果每次都需要重新編寫模組肯定既費時又耗力。同樣,某個功能別人已經造好了輪子,我們就調來用用就好,這時就要引用別人編寫模組,引用的前提是要有統一的「開啟姿勢」,如果每個人有各自的寫法,那麼肯定會亂套,所以會引出模組化規範;
  4. 現在常用的JavaScript模組化規範有四種: CommonjsAMD , CMD , ES6模組化 。個人理解,ES6模組化才是主流。

1.2 模組的定義

  • 將一個複雜的程式依據一定的規則(規範)封裝成幾個塊(檔案), 並進行組合在一起
  • 塊的內部資料相對而言是私有的, 只是向外部暴露一些介面(方法)與外部其它模組通訊

所以,我們發現學習或建立模組就是抓住兩點:如何引入模組?如何暴露模組?

1.3 模組化的定義

編碼時是按照模組一個一個編碼的, 整個專案就是一個模組化的專案

1.4 模組化的優勢

  • 方便維護程式碼,更好的分離,按需載入
  • 提高程式碼複用性
  • 降低程式碼耦合度(降偶)
  • 分治思想——模組化不僅僅只是複用,不管你將來是否要複用某段程式碼,你都有充分的理由將其分治為一個模組。(我們在開發中有時候經常會出現一個模組,實則只用到了一次,但還是抽離出來作為單個獨立的模組,這就是分而治之的軟體工程的思想,在前端模組化同樣適用)

2. 模組化的進化史?

2.1 全域性Function模式

module1.js (定義一個模組1)

//資料
let data1 = 'module one data'

//運算元據的函式
function foo() {
  console.log(`foo() ${data1}`)
}
function bar() {
  console.log(`bar() ${data1}`)
}
複製程式碼

module2.js (定義一個模組2)

let data2 = 'module two data';

function foo() {  //與模組1中的函式衝突了
  console.log(`foo() ${data2}`)
}
複製程式碼

test.html (去使用定義好的模組1和模組2)

//同步引入,若函式衝突,則後面覆蓋前面
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  foo()   //foo() module two data
  bar()   //bar() module one data
</script>
複製程式碼

說明:

  • 全域性函式模式: 將不同的功能封裝成不同的全域性函式
  • 問題: Global被汙染了, 很容易引起命名衝突(比如模組中的data1 data2都是全域性變數)

2.2 namespace模式

module1.js (定義一個模組1)

let moduleOne = {
  data: 'module one data',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
複製程式碼

module2.js (定義一個模組2)

let moduleTwo = {
  data: 'module two data',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
複製程式碼

test.html (去使用定義好的模組1和模組2)

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  moduleOne.foo()   //foo() module one data
  moduleOne.bar()   //bar() module one data

  moduleTwo.foo()  //foo() module two data
  moduleTwo.bar()  //bar() module two data

  moduleOne.data = 'update data' //能直接修改模組內部的資料
  moduleOne.foo()  //foo() update data
</script>
複製程式碼

說明:

  • namespace模式: 簡單物件封裝
  • 作用: 減少了全域性變數 (如兩個模組的 data 都不是全域性變數了,而是物件的某一個屬性 )
  • 問題: 不安全,可以直接修改模組內部的資料

2.3 IIFE模式

module1.js (定義一個模組1)

(function (window) {
  //資料
  let data = 'IIFE module data'

  //運算元據的函式
  function foo() { //用於暴露的函式
    console.log(`foo() ${data}`)
  }

  function bar() {//用於暴露的函式
    console.log(`bar() ${data}`)
    otherFun() //內部呼叫
  }

  function otherFun() { //內部私有的函式
    console.log('privateFunction go otherFun()')
  }

  //暴露foo函式和bar函式
  window.moduleOne = {foo, bar}
})(window)
複製程式碼

test.html (去使用定義好的模組1)

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
  moduleOne.foo()  //foo() IIFE module data
  moduleOne.bar()  //bar() IIFE module data    privateFunction go otherFun()
  //moduleOne.otherFun()  //報錯,moduleOne.otherFun is not a function
  console.log(moduleOne.data) //undefined 因為我暴露的moduleOne物件中無data
  moduleOne.data = 'xxxx' //不是修改的模組內部的data,而是在moduleOne新增data屬性
  moduleOne.foo() //驗證內部的data沒有改變  還是會輸出 foo() IIFE module data
</script>
複製程式碼

說明:

  • IIFE模式: 匿名函式自呼叫(閉包)
  • IIFE : immediately-invoked function expression(立即呼叫函式表示式)
  • 作用: 資料是私有的, 外部只能通過暴露的方法操作
  • 問題: 如果當前這個模組依賴另一個模組怎麼辦? 見下面IIFE增強版的(模組依賴於jQuery)

2.4 IIFE模式增強

引入jquery到專案中

module1.js (定義一個模組1)

(function (window,$) {
  //資料
  let data = 'IIFE Strong module data'

  //運算元據的函式
  function foo() { //用於暴露的函式
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }

  function bar() {//用於暴露的函式
    console.log(`bar() ${data}`)
    otherFun() //內部呼叫
  }

  function otherFun() { //內部私有的函式
    console.log('privateFunction go otherFun()')
  }

  //暴露foo函式和bar函式
  window.moduleOne = {foo, bar}
})(window,jQuery)
複製程式碼

test.html (去使用定義好的模組1)

<!--引入的js必須有一定順序-->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
  moduleOne.foo()  //foo() IIFE Strong module data  而且頁面背景會變色
</script>
複製程式碼

說明:

  • IIFE模式增強 : 引入依賴

  • 這就是現代模組實現的基石。其實很像了,有引入和暴露兩個方面。

  • 存在的問題:一個頁面需要引入多個JS的問題

    <script type="text/javascript" src="module1.js"></script>
    <script type="text/javascript" src="module2.js"></script>
    <script type="text/javascript" src="module3.js"></script>
    <script type="text/javascript" src="module4.js"></script>
    複製程式碼

    請求過多:一個script標籤就是一次請求
    依賴模糊:看不出來誰依賴著誰?哪些模組是有依賴關係的,很難看出來。
    難以維護:內部依賴關係混亂也就難以維護啦

3. 現代模組化方案?

3.1 CommonJS

CommonJS 是伺服器端模組的規範,Node.js 就是採用了這個規範。但目前也可用於瀏覽器端,需要使用 Browserify 進行提前編譯打包。

CommonJS 模組化的引入方式使用require ; 暴露的方式使用module.exportsexports

COMMONJS

CommonJS基於伺服器端

  1. 下載安裝node.js

  2. 建立專案結構

    |-modules
      |-module1.js
      |-module2.js
      |-module3.js
    |-index.js
    複製程式碼
  3. 模組化編碼 module1.js (定義一個模組1)
    定義一個沒有依賴的模組,此模組用來定義配置常量

    const newsUrl = 'http://localhost:3000/news';
    const commentsUrl = 'http://localhost:3000/comments';
    //通過exports暴露出去
    exports.newsUrl = newsUrl;
    exports.commentsUrl = commentsUrl;
    複製程式碼

    module2.js(定義一個模組2)
    定義一個有依賴的模組(這個模組2又依賴模組1,故需要引入模組1),用來模擬傳送請求獲取資料的一個模組

    //引入依賴
    const m1 = require('./module1');
    
    //定義傳送請求的方法
    function getNews(url) {
      console.log('傳送請求獲取資料,請求地址為:' + url);
      return 'newsData';
    }
    function getComments(url) {
      console.log('傳送請求獲取資料,請求地址為:' + url);
      return 'commentsData';
    }
    
    const newsData = getNews(m1.newsUrl);
    const commentsData = getComments(m1.commentsUrl);
    
    //通過module.exports暴露模組
    module.exports = {
      newsData,
      commentsData
    }
    複製程式碼

    module3.js(定義一個模組3)
    定義一個模組,用來顯示使用者資料

    //定義顯示內容的方法
    function showData(data) {
      console.log('要顯示的資訊:' + data);
    }
    //通過module.exports暴露模組
    module.exports = showData;
    複製程式碼

    index.js (主模組,用來啟動整個專案)
    需要引入所有需要啟動的模組

    const m2 = require('./modules/module2');
    const showData = require('./modules/module3');
    
    showData(m2.newsData);
    showData(m2.commentsData)
    複製程式碼

    結果輸出:

    傳送請求獲取資料,請求地址為:http://localhost:3000/news
    傳送請求獲取資料,請求地址為:http://localhost:3000/comments
    要顯示的資訊:newsData
    要顯示的資訊:commentsData
    複製程式碼
  4. 通過node執行index.js
    執行命令: node index.js

CommonJS基於瀏覽器端

  1. 建立專案結構

    |-dist    //打包生成檔案的目錄
    |-src    //原始碼所在的目錄
      |-module1.js
      |-module2.js
      |-module3.js
      |-index.js //應用主原始檔(只需打包主模組)
    |-index.html //引入dist裡面的打包好的js檔案,[需要在html檔案中引入就是基於瀏覽器端咯]
    複製程式碼
  2. 下載browserify
    全域性安裝下載: npm install browserify -g

  3. 定義模組程式碼 module1.js
    定義一個沒有依賴的模組,此模組用來定義配置常量

    //定義配置常量
    const newsUrl = 'http://localhost:3000/news';
    const commentsUrl = 'http://localhost:3000/comments';
    //暴露出去
    exports.newsUrl = newsUrl;
    exports.commentsUrl = commentsUrl;
    複製程式碼

    module2.js
    定義一個有依賴的模組(依賴模組1),用來模擬傳送請求獲取資料的一個模組

    //引入依賴
    const m1 = require('./module1');
    
    //定義傳送請求的方法
    function getNews(url) {
      console.log('傳送請求獲取資料,請求地址為:' + url);
      return 'newsData';
    }
    function getComments(url) {
      console.log('傳送請求獲取資料,請求地址為:' + url);
      return 'commentsData';
    }
    
    const newsData = getNews(m1.newsUrl);
    const commentsData = getComments(m1.commentsUrl);
    
    //暴露模組
    module.exports = {
      newsData,
      commentsData
    }
    複製程式碼

    module3.js
    定義一個模組,用來顯示使用者資料

    //定義顯示內容的方法
    function showData(data) {
      console.log('要顯示的資訊:' + data);
    }
    //暴露模組
    module.exports = showData;
    複製程式碼

    index.js (應用的主模組JS)
    主模組,用來啟動整個專案。需要引入所有需要啟動的模組。

    const m2 = require('./module2');
    const showData = require('./module3');
    
    showData(m2.newsData);
    showData(m2.commentsData);
    複製程式碼

    打包處理index.js
    執行命令:browserify src/index.js -o dist/bundle.js
    src/index.js 表示就是src目錄下的index主模組
    -o 表示 outfile
    dist/bundle.js 表示打包處理結果生成到dist/bundle.js

    在主頁面index.html中使用引入:
    直接引入主模組就可以了,因為主模組上就有各種依賴,他會自動去解析打包處理。

    <script type="text/javascript" src="dist/bundle.js"></script>
    複製程式碼

    結果輸出:

    傳送請求獲取資料,請求地址為:http://localhost:3000/news
    傳送請求獲取資料,請求地址為:http://localhost:3000/comments
    要顯示的資訊:newsData
    要顯示的資訊:commentsData
    複製程式碼

    如果直接引用未打包處理的index.js 則會報錯:

    引入方式:<script src="src/index.js"></script>
    報錯資訊為:Uncaught ReferenceError: require is not defined-->
    複製程式碼

    我們現在是基於瀏覽器端的使用。只有在node環境下才可以直接使用未打包的index.js引入,因為在node環境下有exportsmodularrequire 這些全域性方法。node函式中是這樣的:function (exports, require, module, filename, dirname) {},所以我們引入一個browserify就會自動配置好這些引數。

徹底說明白module.exportsexports的區別:

nodejs中,module是一個全域性變數,類似於在瀏覽器端的window也是一個全域性變數一樣的道理。

module.exports 初始的時候置為{}, exports也指向這個空物件。

內部的程式碼實現是:

var module = {
  id: 'xxxx', // 我總得知道怎麼去找到他吧
  exports: {}, // exports 就是個空物件
}

var exports = module.exports;  //exports是對module.exports的引用
//也就是exports現在指向的記憶體地址和module.exports指向的記憶體地址是一樣的
複製程式碼

上面的程式碼可以看出我們平常使用的exports是對module.exports的一個引用,兩者都是指向同一個物件。

用一句話來說明就是,模組的require(引入)能看到的只有module.exports這個物件,它是看不到exports物件的,而我們在編寫模組時用到的exports物件實際上只是對module.exports的引用。(exports = module.exports)。

我們可以使用exports.a = ‘xxx’exports.b = function () {} 新增方法或屬性,本質上它也新增在module.exports所指向的物件身上。但是你不能直接exports = { a: 'xxx'} 這樣子的意義就是將exports重新指向新的物件!它和module.exports就不是指向同一個物件,也就這兩者已經失去了關係,而nodejsrequire(引入)能看到的是module.exports指向的物件。

module.exports
故,我們一般都會直接使用module.exports
再舉例說明兩者區別:

function foo() {
  console.log('foo');
}

function bar() {
  console.log('bar');
}
複製程式碼

想要將這兩個函式暴露出去,可以直接使用exports

exports.foo = foo;
exports.bar = bar;
複製程式碼

也可以對module.exports賦值

module.exports = {
  foo: foo,
  bar: bar
}
複製程式碼

但是不能直接對exports賦值

// 錯誤
exports = {
  foo: foo,
  bar: bar
}
複製程式碼

因為這樣做僅僅改變了exports的引用,而不改變module.exports。 好,劇終。這個問題講明白了吧。

總結CommonJS
特點:同步載入,有快取
用法:(抓住引入和暴露)

  • 暴露模組
    exports
    module.exports
  • 引入模組
    require(路徑引數)
    路徑: 自定義模組:路徑必須以./或者 ../開頭
    第三方模組/內建模組/核心模組:路徑直接使用模組名稱

主要是在伺服器端使用的,但是也能在瀏覽器端執行,需要藉助browserify進行編譯。

3.2 AMD

CommonJS規範載入模組是同步的,也就是說,只有載入完成,才能執行後面的操作。由於Node.js主要用於伺服器程式設計,模組檔案一般都已經存在於本地硬碟,所以載入起來比較快,所以同步載入沒有問題。但是如果是瀏覽器端,同步載入很容易阻塞,這時候AMD規範就出來了。AMD規範則是非同步載入模組,允許指定回撥函式。故瀏覽器端一般會使用AMD規範。

AMD 是 RequireJS 在推廣過程中對模組定義的規範化產出 。

AMD

  1. 下載require.js, 並引入

    官網: http://www.requirejs.cn/
    github : https://github.com/requirejs/requirejs
    require.js匯入專案: js/libs/require.js

  2. 建立專案結構

    |-libs
      |-require.js
    |-modules
      |-alerter.js
      |-dataService.js
    |-main.js
    |-index.html
    複製程式碼
  3. 定義require.js的模組程式碼
    dataService.js (定義一個無依賴的模組)

    define(function () {
      let msg = 'hello world lyuya';
      function dataServer() {
        return msg.toUpperCase();
      }
      //暴露這個模組
      return dataServer;
    });
    複製程式碼

    alerter.js (定義一個有依賴的模組)
    定義方法:define(['模組1', '模組2', '模組3'], function (m1, m2,m3) {}) 注意前後一一對應

    //一定要注意一一對應,前面有,後面一定要有,別忘記後面的傳參
    define(['dataServer'],function (dataServer) {
      let msg = dataServer();
      function alerter() {
        alert(msg);
      }
      return alerter;
    });
    複製程式碼
  4. 應用主(入口):main.js (主模組)

    //配置模組的路徑
    requirejs.config({
      baseUrl:'./',  //配置所有引入模組的公共路徑(基本路徑)
      //模組標識名與模組路徑對映
      paths : {
        // 模組名稱(一定要與引入的模組名稱一一對應): 模組的路徑
        dataServer: 'modular/dataServer',  
        //一定不能寫檔案的字尾名,它會自動補全
        alerter: 'modular/alerter',
        //庫/框架自己實現模組化的功能,定義了暴露模組的名稱
        jquery: 'libs/jquery-1.10.1'
      }
    })
    
    //主模組,下面requirejs可以用require代替,require是非同步可快取的
    requirejs(['alerter','jquery'],function (alerter,$) {
      alerter();
      $('body').css('background','pink')
    });
    複製程式碼
  5. 在頁面index.html中使用模組

    <!--src引入requirejs模組去用這個模組解析主模組-->
    
    <script data-main="./main" src="./libs/require.js"></script>
    複製程式碼

總結requireJS
特點:非同步載入,有快取
用法:(抓住引入和暴露)

  • 暴露模組
    在模組內部使用return
  • 定義模組
    define(['模組名'], function (模組暴露內容) {})
    require(['模組名'], function (模組暴露內容) {})
    在模組內部可以使用require定義非同步模組
  • 主模組:
    requirejs.config({}) 配置使用的模組路徑
    requirejs(['模組名'], function (模組暴露內容) {})
  • html檔案引入script標籤
    <script data-main='app.js' src='require.js'></script>

AMD(通用模組定義)主要是在瀏覽器使用的。

3.3 CMD

CMD是根據CommonJS和AMD基礎上提出的。
CMD(通用模組定義)和AMD(非同步模組定)是比較相似的。
RequireJS 遵循的是 AMD(非同步模組定義)規範,SeaJS 遵循的是 CMD (通用模組定義)規範。
seaJS 是國人阿里建立的,代表著海納百川之意。

在這裡插入圖片描述

  1. 下載sea.js, 並引入

    官網: http://seajs.org/
    github : https://github.com/seajs/seajs
    sea.js匯入專案: libs/sea.js

  2. 建立專案結構

    |-libs 
      |-sea.js 
    |-modules 
      |-module1.js 
      |-module2.js 
      |-module3.js 
      |-module4.js 
    |-main.js 
    |-index.html
    複製程式碼
  3. 定義sea.js的模組程式碼
    module1.js

    define(function (require, exports, module) {
        /*
          require: 引入依賴模組
          exports: 暴露模組
          module: 暴露模組
         */
        const msg = 'moduleone';
        function getMsg() {
          console.log('module1 getMsg() ' + msg);
          return msg;
        }
        //暴露模組
        module.exports = getMsg;
      })
    複製程式碼

    module2.js

     define(function (require, exports, module) {
        exports.msg1 = 'lyuya';
        exports.msg2 = 'hello';
      })
    複製程式碼

    module3.js

      define(function (require, exports, module) {
        //同步引入模組
        const getMsg = require('./module1');
    
        let msg = getMsg();
    
        msg = msg.toUpperCase();
    
        module.exports = {
          msg
        }
    
      })
    複製程式碼

    module4.js

      //非同步引入模組
        require.async('./module2', function (m2) {
          console.log(m2.msg1, m2.msg2);
        })
        console.log('module4執行了~~~');
      })
    複製程式碼

    main.js :主(入口)模組

    define(function (require) {
        const m3 = require('./module3');
        require('./module4');
    
        console.log(m3.msg);
      })
    複製程式碼

    index.html:

    <script type="text/javascript" src="libs/sea.js"></script> 
    <script type="text/javascript"> seajs.use('./modules/main') </script>
    複製程式碼

    結果輸出:

     module1 getMsg() moduleone      =====module1.js:12
    
     module4執行了~~~               =====module4.js:9 
    
     MODULEONE                       =====main.js:9
    
     lyuya hello                 =====module4.js:7 
    複製程式碼

總結seaJS

特點:非同步載入,有快取
用法:

  • 定義模組
    define(function (require, exports, module) {})

  • 引入模組
    同步載入require()
    非同步載入require.async(['模組名'], function (模組暴露內容) {})

  • 暴露模組
    exports
    module.exports

  • html檔案引入script標籤
    <script src='sea.js'></script>
    <script>seajs.use('app.js')</script>

seajsrequirejs一樣主要在瀏覽器中使用。其實這兩個一般都很少使用。用的比較多的是commonjs 和馬上要介紹的es6模組化

3.4 ES6模組化⭐⭐

ES6模組化的出現,給前端更大的方便。旨在成為瀏覽器和伺服器通用的模組解決方案,但還是主要專門針對瀏覽器端。其模組功能主要由兩個命令構成:exportimport。現在很多專案都在使用ES6模組化規範。

ES6模組化

  1. 定義package.json檔案

  2. 安裝babel-cli, babel-preset-es2015browserify

    npm install babel-cli browserify -g
    npm install babel-preset-es2015 --save-dev
    preset 預設(將es6轉換成es5的所有外掛打包)

  3. 定義.babelrc檔案

    {
     "presets": ["es2015"]
    }
    複製程式碼
  4. 編碼
    module1.js
    分別暴露 後面需要完整的定義(變數或函式定義)

    export function foo() {
       console.log('module1 foo()');
     }
     export function bar() {
       console.log('module1 bar()');
     }
     export const DATA_ARR = [1, 3, 5, 1]
    複製程式碼

    module2.js
    統一暴露 暴露的是一個物件,要暴露的資料新增為物件的屬性/方法

    let data = 'module2 data'
     
     function fun1() {
       console.log('module2 fun1() ' + data);
     }
     
     function fun2() {
       console.log('module2 fun2() ' + data);
     }
     
     export {fun1, fun2}
    複製程式碼

    module3.js
    靜默暴露 只能暴露一個內容,預設暴露的本質:定義了default變數,將後面的值賦值給default變數,暴露出去

    export default {
       name: 'Tom',
       setName: function (name) {
         this.name = name
       }
     }
    複製程式碼

    app.js 主模組用import引入模組

     import {foo, bar} from './module1'
     import {DATA_ARR} from './module1'
     import {fun1, fun2} from './module2'
     import person from './module3'
    
     import $ from 'jquery'  //引入第三方jQuery模組 npm install jquery@1 --save
    
     $('body').css('background', 'red')
    
     foo()
     bar()
     console.log(DATA_ARR);
     fun1()
     fun2()
    
     person.setName('JACK')
     console.log(person.name);
    複製程式碼

    輸出結果:

     module1 foo()
     module1 bar()
     [1, 3, 5, 1]
     module2 fun1() 
     module2 fun2() 
     JACK
    複製程式碼
  5. 編譯
    使用Babel將ES6編譯為ES5程式碼(但包含CommonJS語法):babel src -d build
    使用Browserify編譯js:browserify build/app.js -o dist/bundle.js

  6. 在頁面index.html中引入測試
    <script type="text/javascript" src="lib/bundle.js"></script>

總結ES6
特點:動態引入(按需載入),沒有快取
用法:(抓住引入和暴露)

  • 引入模組使用import
    • 對於統一暴露/分別暴露
      import {模組暴露的內容} from '模組路徑';import * as m1 from './module1'
      這兩者暴露的本質是物件,接收的時候只能以物件的解構賦值的方式來接收值
    • 對於預設暴露
      直接使用import 模組暴露的內容 from '模組路徑'
      預設暴露,暴露任意資料型別,暴露什麼資料型別,接收什麼資料型別
  • 暴露模組使用export
    • 分別暴露 (基本不用)
    • 統一暴露 (暴露多個內容)
    • 預設暴露 (暴露單個內容)

主要是用在瀏覽器,伺服器端也使用。但是現在瀏覽器和伺服器均不支援ES6的模組化語法,所以要藉助工具來編譯執行

  • babel 將ES6 - ES5 (ES6的模組化語法 編譯成commonjs
  • browserifycommonjs語法編譯成能讓瀏覽器識別的語法

3. 模組化的擴充套件閱讀

前端模組化開發那點歷史
Javascript模組化程式設計@阮一峰
知乎專欄 | AMD和CMD的區別

4. 本片我想說的話 ? ?

既然說到模組化,其實我更想說說模組化與元件化。這兩個概念在前端領域已經十分普遍。

先有模組化後有元件化。元件化是建立在模組化思想上的一次演進,一個變種。所以,我們會在軟體工程體系中看過一句話:模組化是元件化的基石。

元件化和模組化的思想都是分而治之的思想。但還是有細小的區分,他們的側重點有所不同。

元件化更加傾向於UI層面上,是一個可以獨立展示內容的「積木」,比如一個頁面的頭部元件,包含結構HTML、樣式CSS、邏輯JS、以及靜態資源圖片組合一個集合體。一個頁面是由眾多元件組成的,就像由眾多「積木」搭成的「城堡」一樣; 模組化更加傾向於功能或者資料的封裝,一般由幾個元件或1個元件構成的帶有一定功能的集合體;

引用一下@張雲龍「?大神」對元件化的理解:

元件化的理解JS
就如上圖的這個title元件,包含了結構HTML、樣式CSS、邏輯JS、以及靜態資源圖片,往往元件的組成就是以上四個方面。這個header資料夾我們可以拿到其他專案中使用,它具有可以獨立展示內容的特點。

結合前面提到的模組化開發,整個前端專案可以劃分為這麼幾種開發概念:

前端模組化開發
那麼它們之間的關係如下圖所示,一個應用由多個下圖的頁面組成。一個頁面由多個元件組合。元件中可依賴JS模組。

元件與模組之間的區別
所以,前端開發現在不僅僅只是別人說的「畫畫頁面實現點效果」的職位,它是實現軟體的圖形使用者介面(Graphical User Interface,簡稱GUI),是一名軟體工程師。現在前端開發都是基於模組化和元件化的開發,可以說算是工程化的專案了。從單頁面(SPA)的應用就可以看出JavaScript大大改善了Web應用的使用者體驗。從谷歌提出PWA(Progressive Web Apps)就可以看出前端在領域的成長。

不僅僅如此,多終端也已經成為時下以及未來的一個必然趨勢,移動端、PC端、觸控式螢幕、智慧裝置、物聯網等等,相信前端在跨端的領域下肯定會有更好的解決方案。

但是,如果從整個軟體工程來看,我們就會意識到一個慘痛的事實:前端工程師在整個系統工程中的地位太低了。前端是處於系統軟體的上游(使用者入口),因此沒有其他系統會來調取前端系統的服務。而後端它在軟體開發中處於下游,後端一方面要為前端提供介面服務,一方面要向中後臺以及資料層索取服務,對接層次會更多,地位也就更高了。由此導致,感覺每次需求評估前端往往是最後一道坎,因為上游依託下游,就只能是下游先行了,整體上就會感覺前端對業務的參與度太低了。

甚至,2019了。現在還是有很多團隊會把前端開發歸類為產品或設計崗位底下,嗯,我不好說什麼,唉···。

你在的公司前端的組織架構是腫麼樣吶??? ??

前端未來一定不會差,就像在人工智慧和大資料領域下,不止於前端,前端完全可以融合和細化下去。

引用一位螞蟻夥伴的話來說:前兩年的前端主要矛盾是日益爆發的前端新技術同前端程式猿學不動之間的矛盾,而現在主要矛盾發生了變化,變成了前端日益增長的工程地位訴求同前端工程侷限性之間的矛盾。(這人考研政治絕對高分!)

相學長|為前端工程之崛起而程式設計

在這樣新的矛盾下,我們就要化被動為主動,改接受為影響。

好啦,好好學習吧,做一個π型人。打鐵還需自身硬。Confidence~?

今天是2019年3月8日,農曆二月二(龍抬頭),星期五,陰陰天氣。我在深圳祝福各位女同胞唷節日快樂永遠美麗,祝福男同胞單身幸福~biubiu???????

去泡個澡,今晚早點休息,明天還要去北京大學深圳醫院。

從前端模組化程式設計切入想聊聊前端的未來(文末有我想問的問題)
此文件作者:呂涯
CSDN主頁:https://blog.csdn.net/LY_code
掘金主頁:https://juejin.im/user/5b220d93e51d4558e03cb948
若有錯誤,及時提出,一起學習,共同進步。謝謝。 ???

相關文章