摘要:打包出來的代碼快照如下,注意看注釋中的時序實際上,的處理同相差無幾,只是在定義模塊和引入模塊時會去處理標識,從而兼容其在語法上的差異。
前言
隨著 Web 技術的蓬勃發展和依賴的基礎設施日益完善,前端領域逐漸從瀏覽器擴展至服務端(Node.js),桌面端(PC、Android、iOS),乃至于物聯網設備(IoT),其中 JavaScript 承載著這些應用程序的核心部分,隨著其規?;蛷碗s度的成倍增長,其軟件工程體系也隨之建立起來(協同開發、單元測試、需求和缺陷管理等),模塊化編程的需求日益迫切。
JavaScript 對模塊化編程的支持尚未形成規范,難以堪此重任;一時間,江湖俠士挺身而出,一路披荊斬棘,從刀耕火種過渡到面向未來的模塊化方案;
概念模塊化編程就是通過組合一些__相對獨立可復用的模塊__來進行功能的實現,其最核心的兩部分是__定義模塊__和__引入模塊__;
定義模塊時,每個模塊內部的執行邏輯是不被外部感知的,只是導出(暴露)出部分方法和數據;
引入模塊時,同步 / 異步去加載待引入的代碼,執行并獲取到其暴露的方法和數據;
刀耕火種盡管 JavaScript 語言層面并未提供模塊化的解決方案,但利用其可__面向對象__的語言特性,外加__設計模式__加持,能夠實現一些簡單的模塊化的架構;經典的一個案例是利用單例模式模式去實現模塊化,可以對模塊進行較好的封裝,只暴露部分信息給需要使用模塊的地方;
// Define a module var moduleA = (function ($, doc) { var methodA = function() {}; var dataA = {}; return { methodA: methodA, dataA: dataA ?}; })(jQuery, document); // Use a module var result = moduleA.mehodA();
直觀來看,通過立即執行函數(IIFE)來聲明依賴以及導出數據,這與當下的模塊化方案并無巨大的差異,可本質上卻有千差萬別,無法滿足的一些重要的特性;
定義模塊時,聲明的依賴不是強制自動引入的,即在定義該模塊之前,必須手動引入依賴的模塊代碼;
定義模塊時,其代碼就已經完成執行過程,無法實現按需加載;
跨文件使用模塊時,需要將模塊掛載到全局變量(window)上;
AMD & CMD 二分天下題外話:由于年代久遠,這兩種模塊化方案逐漸淡出歷史舞臺,具體特性不再細聊;
為了解決”刀耕火種”時代存留的需求,AMD 和 CMD 模塊化規范問世,解決了在瀏覽器端的異步模塊化編程的需求,__其最核心的原理是通過動態加載 script 和事件監聽的方式來異步加載模塊;__
AMD 和 CMD 最具代表的兩個作品分別對應 require.js 和 sea.js;其主要區別在于依賴聲明和依賴加載的時機,其中 require.js 默認在聲明時執行, sea.js 推崇懶加載和按需使用;另外值得一提的是,CMD 規范的寫法和 CommonJS 極為相近,只需稍作修改,就能在 CommonJS 中使用。參考下面的 Case 更有助于理解;
// AMD define(["./a","./b"], function (moduleA, moduleB) { ?// 依賴前置 moduleA.mehodA(); console.log(moduleB.dataB); // 導出數據 return {}; }); // CMD define(function (requie, exports, module) { ?// 依賴就近 ?var moduleA = require("./a"); ? moduleA.mehodA(); // 按需加載 ?if (needModuleB) { ? ?var moduleB = requie("./b"); ? ?moduleB.methodB(); ?} // 導出數據 exports = {}; });CommonJS
2009 年 ry 發布 Node.js 的第一個版本,CommonJS 作為其中最核心的特性之一,適用于服務端下的場景;歷年來的考察和時間的洗禮,以及前端工程化對其的充分支持,CommonJS 被廣泛運用于 Node.js 和瀏覽器;
// Core Module const cp = require("child_process"); // Npm Module const axios = require("axios"); // Custom Module const foo = require("./foo"); module.exports = { axios }; exports.foo = foo;規范
module (Object): 模塊本身
exports (*): 模塊的導出部分,即暴露出來的內容
require (Function): 加載模塊的函數,獲得目標模塊的導出值(基礎類型為復制,引用類型為淺拷貝),可以加載內置模塊、npm 模塊和自定義模塊
實現1、模塊定義
默認任意 .node .js .json 文件都是符合規范的模塊;
2、引入模塊
首先從緩存(require.cache)優先讀取模塊,如果未命中緩存,則進行路徑分析,然后按照不同類型的模塊處理:
內置模塊,直接從內存加載;
外部模塊,首先進行文件尋址定位,然后進行編譯和執行,最終得到對應的導出值;
其中在編譯的過程中,Node對獲取的JavaScript文件內容進行了頭尾包裝,結果如下:
(function (exports, require, module, __filename, __dirname) { var circle = require("./circle.js"); console.log("The area of a circle of radius 4 is " + circle.area(4)); });特性總結
同步執行模塊聲明和引入邏輯,分析一些復雜的依賴引用(如循環依賴)時需注意;
緩存機制,性能更優,同時限制了內存占用;
Module 模塊可供改造的靈活度高,可以實現一些定制需求(如熱更新、任意文件類型模塊支持);
ES Module(推薦使用)ES Module 是語言層面的模塊化方案,由 ES 2015 提出,其規范與 CommonJS 比之 ,導出的值都可以看成是一個具備多個屬性或者方法的對象,可以實現互相兼容;但寫法上 ES Module 更簡潔,與 Python 接近;
import fs from "fs"; import color from "color"; import service, { getArticles?} from "../service"; export default service; export const getArticles = getArticles;
主要差異在于:
ES Module 會對靜態代碼分析,即在代碼編譯時進行模塊的加載,在運行時之前就已經確定了依賴關系(可解決循環引用的問題);
ES Module 關鍵字:import export 以及獨有的 default 關鍵字,確定默認的導出值;
ES Module 中導出的值是一個 只讀的值的引用?,無論基礎類型和復雜類型,而在 CommonJS 中 require 的是值的拷貝,其中復雜類型是值的淺拷貝;
// a.js export let a = 1; export function caculate() { a++; }; // b.js import { a, caculate } from "a.js"; console.log(a); // 1 caculate(); console.log(a); // 2 a = 2; // Syntax Error: "a" is read-onlyUMD
通過一層自執行函數來兼容各種模塊化規范的寫法,兼容 AMD / CMD / CommonJS 等模塊化規范,貼上代碼勝過千言萬語,需要特別注意的是 ES Module 由于會對靜態代碼進行分析,故這種運行時的方案無法使用,此時通過 CommonJS 進行兼容;
(function (global, factory) { ?if (typeof exports === "object") { ? ?module.exports = factory(); } else if (typeof define === "function" && define.amd) { ? ?define(factory); ?} else { ? ?this.eventUtil = factory(); ?} })(this, function (exports) { ? // Define Module ?Object.defineProperty(exports, "__esModule", { value: true }); exports.default = 42; });構建工具中的實現
為了在瀏覽器環境中運行模塊化的代碼,需要借助一些模塊化打包的工具進行打包( 以 webpack 為例),定義了項目入口之后,會先快速地進行依賴的分析,然后將所有依賴的模塊轉換成瀏覽器兼容的對應模塊化規范的實現;
模塊化的基礎從上面的介紹中,我們已經對其規范和實現有了一定的了解;在瀏覽器中,要實現 CommonJS 規范,只需要實現 module / exports / require / global 這幾個屬性,由于瀏覽器中是無法訪問文件系統的,因此 require 過程中的文件定位需要改造為加載對應的 JS 片段(webpack 采用的方式為通過函數傳參實現依賴的引入)。具體實現可以參考:tiny-browser-require。
webpack 打包出來的代碼快照如下,注意看注釋中的時序;
(function (modules) { ?// The module cache ?var installedModules = {}; ?// The require function ?function __webpack_require__(moduleId) {} return __webpack_require__(0); // ---> 0 }) ({ ?0: function (module, exports, __webpack_require__) { ? ?// Define module A ? ?var moduleB = __webpack_require__(1); // ---> 1 ?}, ?1: function (module, exports, __webpack_require__) { ? ?// Define module B ? ?exports = {}; // ---> 2 ?} });
實際上,ES Module 的處理同 CommonJS 相差無幾,只是在定義模塊和引入模塊時會去處理 __esModule 標識,從而兼容其在語法上的差異。
異步和擴展1、瀏覽器環境下,網絡資源受到較大的限制,因此打包出來的文件如果體積巨大,對頁面性能的損耗極大,因此需要對構建的目標文件進行拆分,同時模塊也需要支持動態加載;
webpack 提供了兩個方法 require.ensure() 和 import() (推薦使用)進行模塊的動態加載,至于其中的原理,跟上面提及的 AMD & CMD 所見略同,import() 執行后返回一個 Promise 對象,其中所做的工作無非也是動態新增 script 標簽,然后通過 onload / onerror 事件進一步處理。
2、由于 require 函數是完全自定義的,我們可以在模塊化中實現更多的特性,比如通過修改 require.resolve 或 Module._extensions 擴展支持的文件類型,使得 css / .jsx / .vue / 圖片等文件也能為模塊化所使用;
附錄1:特性一覽表模塊化規范 | 加載方式 | 加載時機 | 運行環境 | 備注 |
---|---|---|---|---|
AMD | 異步 | 運行時 | 瀏覽器 | |
CMD | 異步 | 運行時 | 瀏覽器 | 依賴基于靜態分析,require 時已經 module ready |
CommonJS | 同步/異步 | 運行時 | 瀏覽器 / Node | |
ES Module | 同步/異步 | 編譯階段 | 瀏覽器 / Node | 通過 import() 實現異步加載 |
AMD 模塊化規范: https://github.com/amdjs/amdjs-api/wiki/AMD
CMD 模塊定義規范:https://github.com/seajs/seajs/issues/242
webpack 模塊相關文檔: https://webpack.js.org/concepts/modules/
瀏覽器加載 CommonJS 模塊的原理與實現:http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97395.html
某熊的技術之路指北 ? 當我們站在技術之路的原點,未來可能充滿了迷茫,也存在著很多不同的可能;我們可能成為 Web/(大)前端/終端工程師、服務端架構工程師、測試/運維/安全工程師等質量保障、可用性保障相關的工程師、大數據/云計算/虛擬化工程師、算法工程師、產品經理等等某個或者某幾個角色。某熊的技術之路系列文章/書籍/視頻/代碼即是筆者蹣跚行進于這條路上的點滴印記,包含了筆者作為程序員的技術視野、...
摘要:前言之前在做配置時候多次用到路徑相關內容,最近在寫項目的時候,有一個文件需要上傳到阿里云的功能,同時本地服務器也需要保留一個文件備份。如果返回的路徑字符串長度為零,那么他會返回一個,代表當前的文件夾。 showImg(https://segmentfault.com/img/bVbwElJ?w=480&h=204); 前言 之前在做webpack配置時候多次用到路徑相關內容,最近在寫項...
摘要:如果解析失敗則終止運行。如果新對象的并不匹配當前對象的則拋出異常。替換當前對象為新對象。是替換,也就是說會覆蓋到目標元素之前本身所具有的全部樣式繼承的不算。除此以外,下返回的屬性和值全是大寫,為了避免日后出現意外,還是比較好。 一、cssText之起步 那些年,我們是這樣設置樣式的: xxx.style.width = 233px; xxx.style.position = fix...
閱讀 1448·2019-08-29 17:14
閱讀 1650·2019-08-29 12:12
閱讀 730·2019-08-29 11:33
閱讀 3266·2019-08-28 18:27
閱讀 1444·2019-08-26 10:19
閱讀 909·2019-08-23 18:18
閱讀 3529·2019-08-23 16:15
閱讀 2543·2019-08-23 14:14