淺談前端模塊化規範

来源:https://www.cnblogs.com/cmk1018/archive/2019/08/13/11347381.html
-Advertisement-
Play Games

@[toc] 推薦閱讀 "掘金 前端模塊化" "模塊化七日談" 部分內容摘自《移動 Web 前端高效開發實踐》 iKcamp 著 為什麼需要模塊化? JavaScript 發展初期,代碼簡單地堆積在一起,只要能順利地從上往下一次執行即可。但隨著網站越來越複雜,實現網站功能的 JavaScript 代 ...


目錄

@

推薦閱讀

掘金-前端模塊化
模塊化七日談
部分內容摘自《移動 Web 前端高效開發實踐》- iKcamp 著

為什麼需要模塊化?

JavaScript 發展初期,代碼簡單地堆積在一起,只要能順利地從上往下一次執行即可。但隨著網站越來越複雜,實現網站功能的 JavaScript 代碼也越來越龐大,網頁越來越像桌面程式,很多問題開始暴露出來,比如全局變數衝突、函數命名衝突、依賴關係處理等。

1.原始的模塊化寫法

既然模塊是要實現某個功能,那麼可以把實現功能的一組函數放在同一文件中,像下麵這樣

function a1() {
  // ...
}
function b2() {
  // ...
}

函數 a1 和 b2 組成一個模塊,其他文件先載入該模塊,再對函數進行調用。
缺點:容易發生變數命名衝突,“污染”全局變數,模塊成員之間沒有太多必然的聯繫。

2.添加命名空間

使用命名空間來管理模塊,即使用單全局變數的模式。

var module_special = {
  _index: 0,
  a1: function () {
    // ......
  },
  b2: function () {
    // ......
  }
}
 
// 調用
module_special.a1()
module_special.b2()

通常在屬性名前加下劃線表示該屬性為私有屬性,不過這隻是一種開發規範上的約定,這裡實際上該屬性仍然向外暴露。那麼怎樣讓私有屬性不被暴露呢?那就需要下麵的模塊化方式。

3.立即執行函數表達式

立即執行函數表達式簡稱 “IIFE”(Immediately-Invoked Function Expression)

其能夠形成一個獨立的作用域,用 IIFE 作為一個 “容器”,“容器” 內部可以訪問外部的變數,而外部環境不能訪問 “容器” 內部的變數,所以 IIFE 內部定義的變數不會與外部的變數發生衝突。

var module_special = (function () {
  var _index = 0
  var a1 = function () {
    // ......
  }
  var b2 = function () {
    // ......
  }
  return {
    a1: a1,
    b2: b2
  }
})()
 
// 調用
module_special.a1()
module_special.b2()

這種方式既避免了命名衝突,又使得私有變數 _index 不能被外部訪問和修改。jQuery 源碼大量採用了這種方式。

CommonJS、AMD 和 CMD 規範

CommonJS 規範

node.js 應用由模塊組成,採用 CommonJS 規範,通過全局方法 require 來載入模塊

var http = require('http')                            // 引入http模塊
var server = http.createServer(function (req, res) {  // 用http模塊提供的方法創建一個服務
  res.statusCode = 200                                // 返回狀態碼為200
  res.setHeader('Content-Type', 'text/plain')         // 指定請求和響應的HTTP內容類型
  res.end('Hello World\n')                            // 返回的數據
})
server.listen(3000, '127.0.0.1', function () {        // 監聽的埠和主機名
  console.log('Server running at http://127.0.0.1:3000') // 服務啟動成功後控制台列印信息
})

如何編寫一個 CommonJS 規範的模塊?這就需要 Module 對象。
node.js 內部提供一個 Module 構建函數,所有模塊都是 Module 的實例。每個模塊內部,都有一個 Module 對象,代表當前模塊,包含如下屬性:

  • id:模塊的識別符,通常是帶有絕對路徑的模塊文件名
  • filename:模塊的文件名,帶有絕對路徑
  • loaded:返回一個布爾值,表示模塊是否已經完成載入
  • parent:返回一個對象,表示調用該模塊
  • children:返回一個數組,表示該模塊要用到的其他模塊
  • exports:表示模塊對外輸出的值

其中 exports 是編寫模塊的關鍵,其表示當前模塊對外輸出的介面。其他文件載入該模塊,實際讀取的是 module.exports。

// moduleA.js
module.exports = function (params) {
  console.log(params)
}
 
// 假設兩個文件在同一目錄下
var moduleA = require('./moduleA')
moduleA()
 
// 為了方便,node.js為每個模塊提供一個exports變數指向module.exports
// 那麼moduleA也可以這樣編寫
exports.moduleA = function (params) {
  console.log(params)
}

註意:不能把值直接賦給 exports,因為這樣等於切斷了 exports 與 module.exports 的聯繫

總結 CommonJS 模塊的特點如下:

  1. 所有模塊都有單獨作用域,不會污染全局作用域
  2. 重覆載入模塊只會載入一次,後面都從緩存讀取
  3. 模塊載入的順序按照代碼中出現的順序
  4. 模塊載入是同步的

AMD 規範與 RequireJS

AMD 和 CMD 規範因為現在用的比較少了(反正我是沒看見過),就簡單介紹下
CommonJS 模塊採用同步載入,適合服務端卻不適合瀏覽器。AMD 規範支持非同步載入模塊,規範中定義了一個全局變數 define 函數,描述如下:
define(id?, dependencies?, factory)
第一個參數 id,為字元串類型,表示模塊標識,為可選參數。若不存在則模塊標識預設定義為在載入器中被請求腳本的標識。如果存在,那麼模塊標識必須為頂層的或者一個絕對的標識。

第二個參數 dependencies,定義當前所依賴模塊的數組。依賴模塊必鬚根據模塊的工廠方法優先順序執行,並且執行的結果按照依賴數組中的位置順序以參數的形式傳入(定義中模塊的)工廠方法中。

第三個參數 factory,為模塊初始化時要執行的函數或對象。如果為函數,只被執行一次。如果是對象,此對象應該為模塊的輸出值。如果工廠方法返回一個值(對象、函數或任意強制類型轉換為 true 的值),應該設置為該模塊的輸出值。
創建一個標準 AMD 模塊

define('alpha', ['require', 'exports', 'beta'], function (require, exports, beta) {
  exports.berb = function () {
    return beta.verb()
    // 或者 return require('beta').verb()
  }
})

創建模塊標識為 “alpha” 的模塊,依賴於內置的 “require” 和 “exports” 模塊和外部標識為 “beta” 的模塊。require 函數取得模塊的引用,從而即使模塊沒有作為參數定義,也能夠被使用。exports 是定義的 alpha 模塊的實體,在其上定義的任何屬性和方法也就是 alpha 模塊的屬性和方法。

RequireJS 庫能夠把 AMD 規範應用到實際瀏覽器 Web 端的開發中,其主要解決了兩個問題:實現 JavaScript 文件的非同步載入,避免網頁失去響應;管理模塊之間的依賴性,便於代碼的編寫和維護。

// AMD Wrapper
define(
  ['types/Employee'],     // 依賴
  function(Employee) {    // 這個回調會在所有依賴都被載入後才執行
    function Programmer() {
      // do something
    }
    Programmer.prototype = new Employee()
    return Programmer // return Constructor
  }
)

我們來比較下 CommonJS 和 AMD 的書寫風格:

// CommonJS
var a = require('./a') // 依賴就近
a.doSomething()

var b = require('./b')
b.doSomething()

// AMD
define(['a', 'b'], function (a, b) { // 依賴前置
  a.doSomething()
  b.doSomething()
})

CMD 規範與 Sea.js

CMD 規範全稱為 Common Module Definition
CMD 是另一種 js 模塊化方案,它與 AMD 很類似,不同點在於:AMD 推崇依賴前置、提前執行,CMD 推崇依賴就近、延遲執行。此規範其實是在 sea.js 推廣過程中產生的。

/** AMD寫法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
     // 等於在最前面聲明並初始化了要用到的所有模塊
    a.doSomething();
    if (false) {
        // 即便沒用到某個模塊 b,但 b 還是提前執行了
        b.doSomething()
    } 
});
 
/** CMD寫法 **/
define(function(require, exports, module) {
    var a = require('./a'); //在需要時申明
    a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }
});
 
/** sea.js **/
// 定義模塊 math.js
define(function(require, exports, module) {
    var $ = require('jquery.js');
    var add = function(a,b){
        return a+b;
    }
    exports.add = add;
});
// 載入模塊
seajs.use(['math.js'], function(math){
    var sum = math.add(1+2);
});

ECMAScript6 標準的模塊支持

ECMAScript5 及之前的版本不支持原生模塊化,需要引入 AMD 規範的 RequireJS 或者 AMD 規範的 Seajs 等第三方庫來實現。

直到 ECMAScript6 才支持原生模塊化,其不但具有 CommonJS 規範和 AMD 規範的優點,而且實現得更加友好,語法較之 CommonJS 更簡潔、支持編譯時載入(靜態載入),迴圈依賴處理得更好。

ES6 模塊功能主要由兩個命令構成:export 和 import,export 命令用於規定模塊的對外介面,import 命令用於輸入其他模塊提供的功能。

export

在 ES6 中,一個模塊也是一個獨立的文件,具有獨立的作用域,通過 export 命令輸出內部變數

let name = 'bus'
let color = 'green'
let weight = '20噸噸噸'
export {name, color, weight}
 
// export命令除了輸出變數,還可以輸出函數或類
export function run() {
  console.log('Bus is running')
}
// 可以使用 as 關鍵字對輸出的變數、函數、類重命名
let name = 'bus'
let color = 'green'
let weight = '20噸噸噸'
function run() { console.log('Bus is running') }
export {
  name as busName,
  color as busColor,
  weight as busWeight,
  run as busRun
}

import

import 命令用於導入模塊

import { name, color, weight, run } from './car'
 
// 導入一個模塊的時候也可以用 as 關鍵字對模塊進行重命名
import {name as busName } from './car'
 
// 通過星號 '*' 整體載入某個文件
import * as car from './car'
console.log(car.name)   // bus
console.log(car.color)  // green

export default 命令

從前面的例子可以看出,使用 import 命令載入模塊時需要知道變數名或者函數名,或者整個文件,否則無法載入。為了方便,可以使用 export default 命令為模塊指定預設輸出,載入該模塊時,可以使用 import 命令為其指定任意名字。

// 定義模塊 math.js
let basicNum = 0
let add = function(a, b) {
  return a+b
}
export default { basicNum, add }
 
// 引入
import math from './math'
function test() {
  console.log(math.add(99 + math.basicNum))
}

附:阮一峰《ES6標準入門》
import 命令是靜態載入而不是動態載入的,如果 import 命令要取代 require 方法,就要能實現動態載入。
有一個提案:建議引入 import() 函數,完成動態載入,import 命令能夠接收什麼參數,import() 函數命令就能接受什麼參數。

關於上面所說的提案,現在配置 webpack 使用 babel 轉譯應該能實現了(Vue 的路由懶載入,Webpack 的 splitChunk 都有用到)。
現在前端框架基本上使用 ES6 的模塊化語法,node.js 仍然保持 require 導入,兩者最主要的區別是:

  • require 是運行時載入,import 是編譯時載入

即下麵的條件載入時不可能實現的

if (x === 2) {
    import MyModual from './myModual'
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原型鏈 對象 對象: 1,函數對象:有function創造出來的函數 2,普通對象:除開函數對象之外的對象,都是普通對象 即普通對象obj是構造函數Object的一個實例,因此: obj.__proto__ === Object.prototype ` 但凡通過new Function()創建 的對 ...
  • 一、swich case判斷語句eg // 只有exp和值1或值2類型相同時,才能執行,否則會跳到default關鍵字處,執行對應代碼段; 註:default關鍵字:規定不存在 case 匹配時所運行的代碼。 二、if else if 註:if中條件可以是有多個,用&&或||隔開; 三、if els ...
  • 迴圈語句分類{ for while do ( ) while } 一、for迴圈語句和for迴圈的嵌套 for迴圈格式eg: 表達式“i=1”共運行1次,在迴圈之前運行; 表達式“i<=100”是判斷能否滿足執行迴圈體的條件,如果滿足,迴圈多少次就執行多少次,不滿足時跳出迴圈體; 表達式“i++”進 ...
  • 問題描述: 使用vue-cli創建的項目,開發地址是localhost:8080,由於後臺開發不同的模塊,導致每個模塊請求的ip和埠號不一致 例如:http://192.168.10.22:8081 或者 http://192.168.10.30:9999等 解決問題: 在vue.config.j ...
  • 一、for迴圈 1.單個for迴圈: for(初始值;條件;增量){ 語句 } 初始值:無條件的執行第一個表達式 條件:是判斷是否能執行迴圈體的條件 增量:做增量的操作 //迴圈輸出1~100之間數字的和 var sum=0; for(var i=1;i<=100;i++){ sum= sum+i; ...
  • github 獲取更多資源 https://github.com/ChenMingK/WebKnowledges Notes 線上閱讀:https://www.kancloud.cn/chenmk/web knowledges/1080520 垃圾回收機制 對垃圾回收演算法而言,其核心思想就是如何判斷 ...
  • Introduction 技術棧:react + redux + react router + express + Nginx 練習點: redux 連接 react router 路由跳轉 scss 樣式書寫 容器組件與展示組件的設計 express 腳手架項目結構設計 用戶信息持久化(cooki ...
  • 上面這張圖出自 "冴羽的博客" ,這張圖已經能很好地解釋原型與原型鏈了,其涉及到的屬性如下: : 每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共用的屬性和方法,如果使用這個函數生成了實例,那麼稱這個對象為所有實例的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...