Stories

Detail Return Return

前端模塊化演變歷程 - Stories Detail

前端模塊化是指將一個大型的前端應用程序分解為小的、獨立的模塊,每個模塊都有自己的功能和接口,可以被其他模塊使用。

前端模塊化的出現主要是為了解決以下幾個問題:

  • 代碼複用:通過模塊化,可以在多個地方重複使用同一個模塊,而不需要重複編寫相同的代碼。
  • 代碼維護:模塊化後的代碼更加清晰,每個模塊負責的功能明確,便於維護和升級。
  • 依賴管理:模塊化可以很好地處理模塊間的依賴關係,確保模塊使用時其依賴已經被正確加載。
  • 私有化:模塊內部具有私有化內容,對外只提供暴露的通信接口
  • 提高加載效率:模塊化允許按需加載,只有需要的模塊才會被加載,減少了不必要的資源加載,提高了頁面的加載速度。
  • 隔離命名空間:每個模塊都有自己的命名空間,避免了全局變量的污染,減少了命名衝突的可能性。

普通腳本與模塊化的區別:

  • 普通腳本:只有一個index.js文件,所有的業務邏輯都在這個一個js文件中
    在這裏插入圖片描述
  • 模塊化:以一個entry.js作為入口,然後去引用若干個其他模塊
    在這裏插入圖片描述

接下來就用一個簡單的案例來講解模塊化的演變歷程

第一階段:函數調用

對於一個複雜業務,一般都會拆分成多個小任務,每個任務就編寫成一個函數,下面列舉一個簡單的例子:

// 獲取一個隨機的座標
function getCoordinate() {
  return [Math.random() * 100, Math.random() * 100];
}
​
// 把橫縱座標向下整
function handleData(data) {
  return [Math.floor(data[0]), Math.floor(data[1])];
}
​
// 求和
function sum(a, b) {
  return a + b;
}
​
const coordinate = getCoordinate();
const data = handleData(coordinate);
const result = sum(data[0], data[1]);
console.log(result);   // 99

這裏的每個函數就可以看做是不同的模塊,這些方法都是掛在全局window上的

這就會出現一個嚴重的問題,如果引入了其他的庫,其他的庫也在全局定義了同樣的方法,尤其是那種比較通用的方法名。就會導致同名函數相互覆蓋,最終只有一個是可用的。

缺點:容易引發全局命名衝突

第二階段:全局namespace模式

其本質就是通過對象封裝模塊,在window上定義一個全局對象,然後把所有函數都掛到這個對象上

window.__Module = {
  name: "module",
    getCoordinate() {
      return [Math.random() * 100, Math.random() * 100];
    },
    handleData(data) {
      return [Math.floor(data[0]), Math.floor(data[1])];
    },
    sum(a, b) {
      return a + b;
    },
};
​
const module = window.__Module;
const coordinate = module.getCoordinate();
const data = module.handleData(coordinate);
const result = module.sum(data[0], data[1]);

這種方式可以大大的降低命名衝突的概率,以前有很多JS庫都是用這種方式實現的。

缺點:對象裏面的屬性可以被外部修改,缺少了私有屬性的功能

console.log(module.name);  // module
module.name = "new_module";
console.log(module.name);  // new_module

第三階段:IIFE模式+函數作用域+閉包

關注我的公眾號【前端筱園】,不錯過每一篇推送

加入【前端筱園交流羣】,與大家一起交流,共同進步!

IIFE(Immediately Invoked Function Expression),即立即調用函數表達式,是一種在定義後立即被執行的JavaScript函數。

IIFE的主要作用包括:

  • 創建獨立的作用域:IIFE可以創建一個獨立的作用域,防止變量污染全局作用域。通過這種方式,可以在函數內部定義私有變量,而不影響外部環境。
  • 避免變量提升:在JavaScript中,傳統的函數聲明會進行變量提升,即在代碼執行前被提前至作用域頂部。而IIFE由於是表達式,不會被提升,因此可以避免變量提升帶來的問題。
  • 保持代碼封裝性:IIFE有助於保持代碼的封裝性,使得一些只需要在特定作用域內運行的代碼得以隔離,減少全局命名空間的衝突。
  • 模擬塊級作用域:在ES6之前,JavaScript不支持塊級作用域,IIFE常被用來模擬塊級作用域的效果,尤其是在循環和條件語句中需要臨時的變量時非常有用。
// 函數作用域+閉包
function fun() {
    let name = "module";
    return {
    get() {
        return name;
    },
    set(newName) {
        name = newName;
    },
    };
}
​
console.log(name);  // undefind
const Name = fun();
console.log(Name.get());  // module

如果要改變函數內屬性的值,只有通過暴露出來的方法進行修改,否則無法修改,這就符合了模塊化的標準

Name.set("new_module");
console.log(Name.get());  // new_module

接下來就使用閉包進行模塊化的改造,創建一個自執行的閉包函數。

(() => {
    let name = "module";
    function getCoordinate() {
        return [Math.random() * 100, Math.random() * 100];
    }
    
    function handleData(data) {
        return [Math.floor(data[0]), Math.floor(data[1])];
    }
    
    function sum(a, b) {
        return a + b;
    }
    
    function getName() {
        return name;
    }
    function setName(newName) {
        name = newName;
    }
    window.__Module = {
        name,
        getCoordinate,
        handleData,
        sum,
        getName,
        setName,
    };
})();
​
const module = window.__Module;
const coordinate = module.getCoordinate();
const data = module.handleData(coordinate);
const result = module.sum(data[0], data[1]);
console.log(result);    // 125
console.log(module.name);    // module
module.name = "new_module";
console.log(module.name);     // new_module

在上面的代碼中,對 module.name 的值進行修改,然後打印發現結果為修改後的值,這與之前提到的私有行相矛盾:

module.name = "new_module";
console.log(module.name);     // new_module

這個問題本質上是函數作用域與對象屬性的區別,在閉包方法中,name 屬性添加到了返回結果中,這裏其實是對name的一個拷貝,而不是函數內部的name。

只有通過getName才能拿到內部屬性name的值,也只有通過 setName 才能改變內部屬性 name的值。

console.log(module.getName());    // module
module.setName("new_module")
console.log(module.getName());    // new_module

缺點:無法解決模塊間相互依賴的問題

第四階段:IIFE模式增強,支持傳入自定義依賴

將模塊進行拆分,不同的模塊放在不同的自執行函數中

((global) => {
    function handleData(data) {
        return [Math.floor(data[0]), Math.floor(data[1])];
    }
    function sum(a, b) {
      return a + b;
    }
    global.__Module_utils = {
        handleData,
        sum,
    };
})(window);
​
iief_entry.js

((global, module) => {
    function getCoordinate() {
        return [Math.random() * 100, Math.random() * 100];
    }
    global.__Module = {
        getCoordinate,
        handleData: module.handleData,
        sum: module.sum,
    };
})(window, window.__Module_utils);
​
const module = window.__Module;
const coordinate = module.getCoordinate();
const data = module.handleData(coordinate);
const result = module.sum(data[0], data[1]);
console.log(result);


缺點:

  • 傳入了多個參數依賴,代碼閲讀變得困難
  • 大規模的模塊開發會非常的麻煩,很容易出錯
  • 無特定語法支持,代碼簡陋

經過這四個階段的演變,前端模塊化的標準逐步形成,和前面提到的自執行函數原理非常接近,下期講解CommonJS規範(關注我的公眾號,不錯過推送哦)。

寫在最後

歡迎到我的個人網站(www.dengzhanyong.com)

關注我的公眾號【前端筱園】,不錯過每一篇推送

加入【前端筱園交流羣】,與大家一起交流,共同進步!

user avatar tianmiaogongzuoshi_5ca47d59bef41 Avatar smalike Avatar freeman_tian Avatar jingdongkeji Avatar dirackeeko Avatar chongdianqishi Avatar razyliang Avatar longlong688 Avatar huichangkudelingdai Avatar Dream-new Avatar xiaoxxuejishu Avatar febobo Avatar
Favorites 172 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.