1. 程式人生 > >從前端模組化的概念來理解Webpack

從前端模組化的概念來理解Webpack

為什麼需要模組化?

隨著網站內容越來越複雜,瀏覽器和使用者的互動越來越細膩,網站再也不是簡單的內容呈現,更像是一個複雜的客戶端軟體,其中html/css/js程式碼越來越多,邏輯越來越複雜,越來越不便於管理,多人協作成本加深,為了解決這些問題,才出現了模組化的概念,也就是說模組化更多的是工程方面的產出,為了應對更復雜的網站開發。

>在ES6之前,前端模組的實現本質都是利用JS神器:閉包。 閉包使得函式在呼叫時可以訪問該函式定義時的詞法作用域,通過作用域查詢所有宣告的識別符號(變數),達到不暴露私有作用域。

基礎模組

    function myModule() {
        var
_something = 'xxoo' var _another = [1,2,3]; function dosomething() { console.log(_something); } function doAnother() { console.log(_another.join('!')); } return { dosomething: dosomething, doAnother: doAnother } } var
foo = myModule(); foo.dosomething(); //xxoo foo.doAnother(); //1!2!3

解析:
myModule()只是一個函式,通過呼叫它來建立一個模組例項,不執行的話,內部作用域和閉包都無法建立,其次返回一個物件字面量,返回的物件中含有對內部函式的引用而不是內部資料變數的引用(函式的巢狀才能形成閉包),

從模組中返回一個實際的物件並不是必須的,也可以直接返回一個內部函式,類似jQuery,jQeury和$識別符號就是jQuery模組的公共API,但它們本身都是函式(由於函式也是物件,所以也可以擁有屬性)。

模組模式需要具備兩個必要條件

  • 必須有外部的封閉函式,該函式至少被呼叫一次(每次呼叫都會建立一個新的模組例項)。

  • 封閉函式必須至少返回一個內部函式,這樣內部函式才能在私有作用域中形成閉包,並且可以訪問或者修改私有的狀態。

現代的模組機制

大多數模組依賴載入器本質上都是將這種模組定義封裝進一個友好的API。比如requiresjs,以下程式碼是簡單的實現。

    var MyModules = (function Manager () {
        var modules = {};
        function define(name, deps, impl) {
            for(var i=0; i<deps.length; i++) {
                //依賴變數賦值,找到相應的模組方法
                deps[i] = modules[deps[i]];
            }
            modules[name] = impl.apply(impl, deps);//傳入依賴的模組名作為引數,以便該模組呼叫
        }
        function get(name) {
            return modules[name];
        }
        return {
            define: define,
            get: get,
        }
    })();

這段程式碼的核心是modules[name] = impl.apply(impl, deps)。為了模組的定義引入了包裝函式(可以傳入任何依賴),並且將返回值,也就是模組的API,儲存在一個根據名字來管理的模組列表中。

下面展示瞭如何使用它來定義模組:

    MyModules.define('bar', [], function() {
        function hello(who) {
            return 'i am ' + who;
        }
        return {hello: hello};
    });

    MyModules.define('foo', ['bar'], function(bar) {
        function awesome() {
            console.log(bar.hello('kenny').toUpperCase());
        }
        return {
            awesome: awesome
        }
    })

    var bar = MyModules.get('bar');
    var foo = MyModules.get('foo');

    console.log(bar.hello('kenny')); // i am kenny

    console.log(foo.awesome('kenny'));// I AM KENNY

解讀
MyModules是一個立即執行函式(單例模式),返回物件字面量(含有對內部函式define和get的引用,形成閉包),所以當執行define的時候,define方法可以查詢MyModules的詞法作用域,其次deps[i]=modules[depsi],也形成閉包,當有依賴時,impl通過查詢該引數(dep[i])來獲取API,從而其他方法可以找到該依賴的方法。在私有物件modules裡, get方法共享MyModules的詞法作用域,從而可以獲取define時的模組方法。

可以研究示例程式碼深入理解下閉包的作用,最重要的是要理解模組管理器沒有任何特殊的“魔力”,它們符合前面列出的模組模式的兩個特點: 呼叫了包裝函式定義的包裝函式, 並且將返回值作為該模組的API

換句話說,模組就是模組,即使在它們外層加上一個友好的包裝工具也不會發生任何變化。

現代的模組機制

ES6為模組增加了一級語法支援。在通過模組系統載入時,ES6會將檔案當作獨立的模組來處理,每個模組可以匯入其他模組或特定的API成員,同樣也可以匯出自己的API成員。

該方案最大的特點就是靜態化(API不會在執行時被改變),靜態化的優勢在於可以在編譯的時候確定模組的依賴關係以及輸入輸出的變數。如果PAI引用不存在,編譯器會在編譯時就丟擲‘早期’錯誤,而不會等到執行時期再動態解析

ES6的模組必須被定義在獨立的檔案中(一個檔案一個模組)。

import "jquery";
export function doStuff() {}

webpack的模組有什麼特點

  • 1:可以相容多模組風格,無痛遷移老專案。

  • 2:一切皆模組,js/css/圖片/字型都是模組。

  • 3:靜態解析,按需打包,動態載入。

webpack並不強制你使用某種模組化方案,而是通過相容所有模組化方案讓你無痛接入專案,當然這也是webpack牛逼的地方。

有了webpack,你可以隨意選擇你喜歡的模組化方案,至於怎麼處理模組之間的依賴關係及如何按需打包,webpack會幫你處理好的。

webpack對模組程式碼做了什麼?

一行alert(‘hello world’)程式碼,經過webpack打包後,會生成如下50行程式碼;

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};

/******/     // The require function
/******/     function __webpack_require__(moduleId) {

/******/         // Check if module is in cache
/******/         if(installedModules[moduleId])
/******/             return installedModules[moduleId].exports;

/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             exports: {},
/******/             id: moduleId,
/******/             loaded: false
/******/         };

/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/         // Flag the module as loaded
/******/         module.loaded = true;

/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }


/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;

/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules;

/******/     // __webpack_public_path__
/******/     __webpack_require__.p = "";

/******/     // Load entry module and return exports
/******/     return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {

    alert('hello world');

/***/ }
/******/ ]);

Runtime & 模組

上半部分是一個函式定義,也就是Runtime,作用是保證模組順序載入和執行。
下半部分是我們的JS程式碼,包裹了一個函式,也就是模組。執行的時候模組是作為Runtime的引數被傳進去的。

(function(modules) {
    // Runtime
})([
    // 模組陣列
])

特點

  • 模組不再暴露在全域性作用域,模組的全域性變數也不再是全域性作用域。

  • 模組被引入的時候只是執行程式碼而無法將模組賦值。因為非模組化規範的程式碼沒有通過AMD的return或者CommonJs的exports/this匯出模組本身。

AMD模組

define([], function() {
    alert('hello world!');
});

經過webpack打包,會生成如下核心程式碼:

function(module, exports, __webpack_require__) {
    var __WEBPACK_AMD_DEFINE_ARRAY__, // AMD依賴列表
        __WEBPACK_AMD_DEFINE_RESULT__; // AMD factory函式的返回值,即模組內容

    __WEBPACK_AMD_DEFINE_ARRAY__ = [];

    // 執行factory函式,獲取返回值作為模組內容
    // 函式體使用.apply呼叫,函式體中this為exports
    // 形參則分別對應依賴列表中的各個依賴模組
    __WEBPACK_AMD_DEFINE_RESULT__ = function() {
        alert('hello world!');
    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__);

    // 如果模組內容不為空,則通過module.exports返回
    // 如果為空,則不處理,在Runtime中宣告為{}
    if (__WEBPACK_AMD_DEFINE_RESULT__ !== undefined) {
        module.exports = __WEBPACK_AMD_DEFINE_RESULT__;
    }
}

CommonJs

var me = {
    sayHello:function(){
        alert('hello world!');
    }
};
module.exports = me;

經過webpack打包,會生成如下核心程式碼:

function(module, exports) {
    var me = {
        sayHello: function() {
            alert('hello world!');
        }
    };

    module.exports = me;
}

模組化是拆分,那傳輸怎麼辦?

模組化是工程化的需求,是為了更好的管理程式碼,最後上線的程式碼並不應該是這樣的,假設我們用兩個極端的方式去載入程式碼:

  • N個模組N個請求。

  • 所有模組打包成一個檔案,一個請求。

顯然,這兩種都不是最優方案,第一種請求數量過多,第二種請求檔案過大。
理論上,最優方案是:按需打包,即將該頁面需要的所有模組打包成一個檔案,保證請求最少,且請求的程式碼都是需要的。

在webpack之前的構建工具裡,都實現不了這個“最優方案”,因為它們不知道模組之前的依賴關係,自然就不能按需打包了。

而webpack出現之後,它的程式碼分片功能讓webpack擁有了按需打包的特性,從而鶴立雞群。當然,webpack還有很多其他優秀的特性。

wepback按需打包及拆分可以參考這篇文章

參考:

  • 書籍《你不知道的javascript上卷》

相關推薦

前端模組概念理解Webpack

為什麼需要模組化? 隨著網站內容越來越複雜,瀏覽器和使用者的互動越來越細膩,網站再也不是簡單的內容呈現,更像是一個複雜的客戶端軟體,其中html/css/js程式碼越來越多,邏輯越來越複雜,越來越不便於管理,多人協作成本加深,為了解決這些問題,才出現了模組化的

前端模組開發規範的終結者Webpack詳解(純乾貨,不套路)

可謂集CommonJS、AMD、ES6等多種特性於一身,靈活、易用、高擴充套件性、效能優越。 核心配置 以下是webpack的幾個核心配置節: 節點 說明 entry 指定要打包的檔案

前端模組理解

轉至http://www.cnblogs.com/lvdabao/p/js-modules-develop.html 在JavaScript發展初期就是為了實現簡單的頁面互動邏輯,寥寥數語即可;如今CPU、瀏覽器效能得到了極大的提升,很多頁面邏輯遷移到了客戶端(表單驗證等)

前端模組,元件和,工程化的理解

前端工程化 前端工程化我認為就是將前端專案當成一項系統工程進行分析、組織和構建從而達到項 目結構清晰、分工明確、團隊配合默契、開發效率提高的目的。 工程化是一種思想而不是某種技術(當然為了實現工程化我們會用一些技術) 在一個大型web專案中往往有更加複雜的結構和非常多的頁面需要很多人甚至

前端模組-5分鐘快速入門RequireJs

各位開發專案的時候引用JS都會遇到以下的情景 產生AMD規範的背景 因為使用各種外掛,或者團隊協同合作,產生多個js檔案, 假如使用的JQ外掛,則必須先引用jquery庫才能夠正常執行, JS檔案之間強依賴關係,讓我們不敢動檔案的引入順序。 並且,在渲染頁面的過程中,同步讀取JS檔案會

函式的引數傳遞理解python中“一切都是物件”

From Python 初學者: a = 2 其中2為物件,a可理解為貼在物件上的標籤。 物件由不可變物件和可變物件,不可變物件有數字、字串、元祖,可變物件有列表,字典,集合。 def f(x): x *= 2 a = 1 f(a) print(a) #1 b =

WEB 前端模組都有什麼?

前言 說到前端模組化,你第一時間能想到的是什麼?Webpack?ES6 Module?還有嗎?我們一起來看一下下圖。 相信大夥兒對上圖的單詞都不陌生,可能用過、看過或者是隻是聽過。那你能不能用一張圖梳理清楚上述所有詞彙之間的關係呢?我們日常編寫程式碼的時候,又和他們之間的誰誰誰有關係呢?

WEB 前端模組,讀文筆記

文章連結 WEB 前端模組化都有什麼? 知識點 根據平臺劃分 瀏覽器 AMD、CMD 存在網路瓶頸,使用非同步載入 非瀏覽器 CommonJS 直接操作 IO,同步載入 瀏覽器 AMD 依賴前置 requirejs CMD 就近依賴 seajs AMD 與 CMD 都是在頁面初始化時載入

前端模組(AMD和CMD、CommonJs)

知識點1:AMD/CMD/CommonJs是JS模組化開發的標準,目前對應的實現是RequireJs/SeaJs/nodeJs. 知識點2:CommonJs主要針對服務端,AMD/CMD主要針對瀏覽器端,所以最容易混淆的是AMD/CMD。(順便提一下,針對伺服器端和針對瀏覽器端有什麼本質的區別呢?伺服器端一

前端模組開發規範之AMD(可不是處理器哦!)

首先強調下,我們這裡提到的AMD可不是計算機的處理器哦! 繼CommonJS之後,雙出現了一種非同步載入模組的方法。就是AMD,全稱為:Asynchronous module definition。 它的使用方法依然很簡單。 定義一個模組: define('user', ['

前端模組開發規範之ES6

直接上程式碼啦! 匯入 import { getList } from './userService' 或者 import userService from './userService' 匯出 export default { userService }

前端模組開發規範之CommonJS

CommonJS是前端模組化發展過程中出現的第一個規範。其使用方式也是相當簡便的。 以下是匯入和匯出的兩個關鍵片斷。 1、匯入 const user = require('./user'); 2、匯出 module.exports = user.getList;

前端模組的作用

相信很多人都用過 seajs、 requirejs 等這些模組載入器,他們都是十分便捷的工程管理工具,簡化了程式碼的結構,更重要的是消除了各種檔案依賴和命名衝突問題,並利用 AMD / CMD 規範統一了格式。然而你瞭解模組化的作用嗎?下面主要講述模組化能解決哪

關於前端模組

在嘗試重構前任前端的程式碼的時候遇到的問題,史前前端轉變不久,對類、模組化、元件化概念很模糊,在看了一些資料之後初步梳理了一下 1、目前個人所理解的類就是一個物件,複雜的類包含多個方法屬性、或者屬性就是一個類。 2、在網上收集的其他資料,發現了之前自己的一個誤區,之前以為

前端模組詳解(完整版)

前言 在JavaScript發展初期就是為了實現簡單的頁面互動邏輯,寥寥數語即可;如今CPU、瀏覽器效能得到了極大的提升,很多頁面邏輯遷移到了客戶端(表單驗證等),隨著web2.0時代的到來,Ajax技術得到廣泛應用,jQuery等前端庫層出不窮,前端程式碼日益膨脹,此時在JS方面就會考慮使用模組化規範去管

HTML5前端教程分享:前端模組開發

1. 命名衝突 首先從一個簡單的習慣開始。 由於以前一直做 JavaEE 開發的緣故,在 JavaScript 開發中,我已經習慣將專案中的一些通用功能抽象出來,形成一個個的獨立函式,以便於實現程式碼複用,如: function css(element, attr

[前端] 模組那些事02

模組化那些事02 轉自:https://www.cnblogs.com/huiguo/p/7967189.html 所謂的模組化程式設計就是封裝細節,提供使用介面,彼此之間互不影響,每個模組都是相互獨立,實現某一特定的功能。如果其他模組想呼叫的時候,可以暴露我們所希望對外的公開的方法

[前端] 模組那些事01

模組化那些事01 轉自:https://www.cnblogs.com/huiguo/p/7967168.html 前端這幾年發展太快了,我學習的速度都跟不上這速度了。在JavaScript發展初期就是為了實現簡單的頁面互動邏輯,隨著前端的業務邏輯越來越複雜,程式碼也越來越多,Jav

通過seajs研究前端模組-seajs學習心得

本篇文章主要是我在學習seajs過程中,模仿seajs實現過程中的一些心得和體會。我在網上通過學習視訊學習前端模組化時,當時的老師正好使用seajs來講解前端的CommonJS模組化規範並講解了seajs的原始碼,教我們seajs是如何CommonJS模組化規範來實現瀏覽器端的js模組化,學完之後,

前端模組的簡單綜述(一)

剛學前端的時候,曾有一段時間很迷糊,不知道為啥突然從html檔案、js檔案和css檔案三件套,變成需要打包在伺服器才能用了。這種不明白感,隨著使用vue,weex等框架逐步熟練之後,降低不少。但依然不知道,這一路究竟發生了什麼。   故在此,梳理整個前端模組的發展過程,為自