首頁 >web前端 >js教程 >Javascript模組化的詳細介紹

Javascript模組化的詳細介紹

不言
不言原創
2018-09-05 11:55:341637瀏覽

這篇文章帶給大家的內容是關於Javascript模組化的詳細介紹 ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

前言

隨著Web 技術的蓬勃發展和依賴的基礎設施日益完善,前端領域逐漸從瀏覽器擴展至服務端(Node.js),桌面端(PC、Android 、iOS),甚至於物聯網設備(IoT),其中JavaScript 承載著這些應用程式的核心部分,隨著其規模化和複雜度的成倍增長,其軟體工程體係也隨之建立起來(協同開發、單元測試、需求和缺陷管理等),模組化程式的需求日益迫切。

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 年ty 發布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 中导入模块的属性或者方法是强绑定的,包括基础类型;

UMD

通过一层自执行函数来兼容各种模块化规范的写法,兼容 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 / 图片等文件也能为模块化所使用;

附录:特性一览表

模块化规范 加载方式 加载时机 运行环境 备注
AMD 异步 运行时 浏览器
CMD 异步 运行时 浏览器
CommonJS 同步/异步 运行时 浏览器 / Node
ES Module 同步/异步 编译阶段 浏览器 / Node 通过 import() 实现异步加载

相关推荐:

javascript模块化编程(转载),javascript模块化

JavaScript模块化思想

以上是Javascript模組化的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn