javascript
javascript模块化简介
所有原創并不精彩,所有精彩并非原創
歷史
JavaScript 隨著時間的推移所負責的責任越來越重從最開始的添加表單驗證功能之類的腳本到angular 應用開發框架,隨著js任務越來越重就急需模塊化的解決方案。
模塊化的基礎條件就是開辟一片獨立的上下文,那些擁有模塊化功能的語言或通過物理文件組織模塊,或以抽象的 namespace package 組織模塊,而JavaScript 并沒這種能力只能從語法上開辟獨立的上下文,就目前瀏覽器端運行的js來說能開辟獨立上下文的方式只有一種方式 function(閉包)
- 最開始的刀耕火種用閉包各種全局變量組織結構
- AMD UMD commonjs es6
- 現在webpack 支持 AMD commonjs es6 ,不過webpack更多的只是格式上的支持
對比一下各個模塊化方案代碼寫法
仔細觀察一下下面列舉的幾個例子不難發現根上還是閉包
AMD
define(['requrie','exports','module'],function(require, exports, module) {var a = require('a'),b = require('b');exports.A=a}) ; 復制代碼angular
angular.module('myApp', []).controller('Ctl', ['$scope', '$log', function ($scope, $log) {$scope.name = 'leonwgc';$log.log('hello,world'); }]); 復制代碼webpack
(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] = {/******/ i: moduleId,/******/ l: false,/******/ exports: {}/******/ };/******//******/ // Execute the module function/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);/******//******/ // Flag the module as loaded/******/ module.l = true;/******//******/ // Return the exports of the module/******/ return module.exports;/******/ }/******//******/ // Load entry module and return exports/******/ return __webpack_require__(__webpack_require__.s = 81);/******/ })/************************************************************************//******/ ([/* 0 */ /***/ (function(module, exports, __webpack_require__) {/***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { ; ; exports.cla = cla;/***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { ; var e = 2.71828182846; function math(x) {return Math.exp(x); }module.exports = math;/***/ })]); 復制代碼對比完代碼接下來簡要介紹一下AMD,Commonjs ,ES6模塊化的語法于定義
AMD
define(id?, dependencies?, factory);
id
第一個參數,id,是個字符串。它指的是定義中模塊的名字,這個參數是可選的。如果沒有提供該參數,模塊的名字應該默認為模塊加載器請求的指定腳本的名字。如果提供了該參數,模塊名必須是“頂級”的和絕對的(不允許相對名字)。
模塊格式
模塊名用來唯一標識定義中模塊,它們同樣在依賴數組中使用。AMD的模塊名規范是CommonJS模塊名規范的超集。引用如下:
- 模塊名是由一個或多個單詞以正斜杠為分隔符拼接成的字符串
- 單詞須為駝峰形式,或者".",".."
- 模塊名不允許文件擴展名的形式,如".js"
- 模塊名可以為 "相對的" 或 "頂級的"。如果首字符為"."或".."則為"相對的"模塊名
- 頂級的模塊名從根命名空間的概念模塊解析
- 相對的模塊名從 "require" 書寫和調用的模塊解析 上文引用的CommonJS模塊id屬性常被用于JavaScript模塊。
相對模塊名解析示例:
- 如果模塊 "a/b/c" 請求 "../d", 則解析為"a/d"
- 如果模塊 "a/b/c" 請求 "./e", 則解析為"a/b/e"
依賴
第二個參數,dependencies,是個定義中模塊所依賴模塊的數組。依賴模塊必須根據模塊的工廠方法優先級執行,并且執行的結果應該按照依賴數組中的位置順序以參數的形式傳入(定義中模塊的)工廠方法中。
依賴的模塊名如果是相對的,應該解析為相對定義中的模塊。換句話來說,相對名解析為相對于模塊的名字,并非相對于尋找該模塊的名字的路徑。
本規范定義了三種特殊的依賴關鍵字。如果"require","exports", 或 "module"出現在依賴列表中,參數應該按照CommonJS模塊規范自由變量去解析。
依賴參數是可選的,如果忽略此參數,它應該默認為["require", "exports", "module"]。然而,如果工廠方法的形參個數小于3,加載器會選擇以函數指定的參數個數調用工廠方法。
工廠方法
第三個參數,factory,為模塊初始化要執行的函數或對象。如果為函數,它應該只被執行一次。如果是對象,此對象應該為模塊的輸出值。
如果工廠方法返回一個值(對象,函數,或任意強制類型轉換為true的值),應該為設置為模塊的輸出值。
簡單的 CommonJS 轉換
如果依賴性參數被忽略,模塊加載器可以選擇掃描工廠方法中的require語句以獲得依賴性(字面量形為require("module-id"))。第一個參數必須字面量為require從而使此機制正常工作。
在某些情況下,因為腳本大小的限制或函數不支持toString方法(Opera Mobile是已知的不支持函數的toString方法),模塊加載器可以選擇掃描不掃描依賴性。
如果有依賴參數,模塊加載器不應該在工廠方法中掃描依賴性。
Simple Name/Value Pairs
If the module does not have any dependencies, and it is just a collection of name/value pairs, then just pass an object literal to define():
//Inside file my/shirt.js: define({color: "black",size: "unisize" }); 復制代碼Definition Functions
If the module does not have dependencies, but needs to use a function to do some setup work, then define itself, pass a function to define():
//my/shirt.js now does setup work //before returning its module definition. define(function () {//Do setup work herereturn {color: "black",size: "unisize"} }); 復制代碼Definition Functions with Dependencies
If the module has dependencies, the first argument should be an array of dependency names, and the second argument should be a definition function. The function will be called to define the module once all dependencies have loaded. The function should return an object that defines the module. The dependencies will be passed to the definition function as function arguments, listed in the same order as the order in the dependency array:
//my/shirt.js now has some dependencies, a cart and inventory //module in the same directory as shirt.js define(["./cart", "./inventory"], function(cart, inventory) {//return an object to define the "my/shirt" module.return {color: "blue",size: "large",addToCart: function() {inventory.decrement(this);cart.add(this);}}} ); 復制代碼Define a Module with Simplified CommonJS Wrapper
If you wish to reuse some code that was written in the traditional CommonJS module format it may be difficult to re-work to the array of dependencies used above, and you may prefer to have direct alignment of dependency name to the local variable used for that dependency. You can use the simplified CommonJS wrapper for those cases:
define(function(require, exports, module) {var a = require('a'),b = require('b');exports.A=a} );define(['requrie','exports','module'],function(require, exports, module) {var a = require('a'),b = require('b');exports.A=a} ); 復制代碼r.js
babel
commonjs js 規范是 AMD 的子集 看一下demo
- babel-plugin-transform-es2015-modules-amd
- babel-plugin-transform-es2015-modules-commonjs
- babel-plugin-transform-es2015-modules-systemjs
- babel-plugin-transform-es2015-modules-umd
Commonjs
CommonJS API定義很多普通應用程序(主要指非瀏覽器的應用)使用的API,從而填補了這個空白。它的終極目標是提供一個類似Python,Ruby和Java標 準庫。這樣的話,開發者可以使用CommonJS API編寫應用程序,然后這些應用可以運行在不同的JavaScript解釋器和不同的主機環境中。在兼容CommonJS的系統中,你可以使用 JavaScript程序開發:
- 服務器端JavaScript應用程序
- 命令行工具
- 圖形界面應用程序
- 混合應用程序(如,Titanium或Adobe AIR...)
基本語法
var x = 5; var addX = function (value) {return value + x; }; module.exports.x = x; module.exports.addX = addX; // 上面代碼通過module.exports輸出變量x和函數addX。// require方法用于加載模塊。 var example = require('./example.js');console.log(example.x); // 5 console.log(example.addX(1)); // 6 復制代碼module對象
- module.id 模塊的識別符,通常是帶有絕對路徑的模塊文件名。
- module.filename 模塊的文件名,帶有絕對路徑。
- module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
- module.parent 返回一個對象,表示調用該模塊的模塊。
- module.children 返回一個數組,表示該模塊要用到的其他模塊。
- module.exports 表示模塊對外輸出的值。
exports
exports 要注意的問題
exports = function(x) {console.log(x)};復制代碼函數傳參傳入引用的引用
函數傳參基本是兩種類型 值類型和引用類型 最早接觸這個問題是在湯姆大叔的博客中
var liz={age:18}function fun(liz){liz={age:19} }復制代碼(function (exports, require, module, __filename, __dirname) {// exports = module.exports }); 復制代碼ES6
在 ES6 之前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用于服務器,后者用于瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規范,成為瀏覽器和服務器通用的模塊解決方案。
ES6 模塊的設計思想是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。比如,CommonJS 模塊就是對象,輸入時必須查找對象屬性。
// CommonJS模塊 let { stat, exists, readFile } = require('fs');// 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile; 復制代碼上面代碼的實質是整體加載fs模塊(即加載fs的所有方法),生成一個對象(_fs),然后再從這個對象上面讀取 3 個方法。這種加載稱為“運行時加載”,因為只有運行時才能得到這個對象,導致完全沒辦法在編譯時做“靜態優化”。
ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。
// ES6模塊 import { stat, exists, readFile } from 'fs'; 復制代碼上面代碼的實質是從fs模塊加載 3 個方法,其他方法不加載。這種加載稱為“編譯時加載”或者靜態加載,即 ES6 可以在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。當然,這也導致了沒法引用 ES6 模塊本身,因為它不是對象。
由于 ES6 模塊是編譯時加載,使得靜態分析成為可能。有了它,就能進一步拓寬 JavaScript 的語法,比如引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
除了靜態加載帶來的各種好處,ES6 模塊還有以下好處。
- 不再需要UMD模塊格式了,將來服務器和瀏覽器都會支持 ES6 模塊格式。目前,通過各種工具庫,其實已經做到了這一點。
- 將來瀏覽器的新 API 就能用模塊格式提供,不再必須做成全局變量或者navigator對象的屬性。
- 不再需要對象作為命名空間(比如Math對象),未來這些功能可以通過模塊提供。
module 語法
推薦
工作上的體會這種模塊加載的語法,就用最簡單常見的方式就好,千萬不要過多操作
import _default from "xxx" import _default,{a,b,c} from "xxx"export default class xxx{} export class xxx{} export {xx,xxx,xxxx} 復制代碼export 與 import 的復合寫法
如果在一個模塊之中,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起。
export { foo, bar } from 'my_module';// 可以簡單理解為 import { foo, bar } from 'my_module'; export { foo, bar }; 上面代碼中,export和import語句可以結合在一起,寫成一行。但需要注意的是,寫成一行以后,foo和bar實際上并沒有被導入當前模塊,只是相當于對外轉發了這兩個接口,導致當前模塊不能直接使用foo和bar。模塊的接口改名和整體輸出,也可以采用這種寫法。// 接口改名 export { foo as myFoo } from 'my_module';// 整體輸出 export * from 'my_module'; 默認接口的寫法如下。export { default } from 'foo'; 具名接口改為默認接口的寫法如下。export { es6 as default } from './someModule';// 等同于 import { es6 } from './someModule'; export default es6; 同樣地,默認接口也可以改名為具名接口。export { default as es6 } from './someModule'; 下面三種import語句,沒有對應的復合寫法。import * as someIdentifier from "someModule"; import someIdentifier from "someModule"; import someIdentifier, { namedIdentifier } from "someModule"; 為了做到形式的對稱,現在有提案,提出補上這三種復合寫法。export * as someIdentifier from "someModule"; export someIdentifier from "someModule"; export someIdentifier, { namedIdentifier } from "someModule"; 復制代碼模塊的繼承
模塊之間也可以繼承。
假設有一個circleplus模塊,繼承了circle模塊。
// circleplus.jsexport * from 'circle'; export var e = 2.71828182846; export default function(x) {return Math.exp(x); } 上面代碼中的export *,表示再輸出circle模塊的所有屬性和方法。注意,export *命令會忽略circle模塊的default方法。然后,上面代碼又輸出了自定義的e變量和默認方法。這時,也可以將circle的屬性或方法,改名后再輸出。// circleplus.jsexport { area as circleArea } from 'circle'; 上面代碼表示,只輸出circle模塊的area方法,且將其改名為circleArea。加載上面模塊的寫法如下。// main.jsimport * as math from 'circleplus'; import exp from 'circleplus'; console.log(exp(math.e)); 上面代碼中的import exp表示,將circleplus模塊的默認方法加載為exp方法。 復制代碼本文最后推一手我寫的自動生成模塊索引的工具index-creater
轉載于:https://juejin.im/post/5cb0a7e36fb9a0687015c610
總結
以上是生活随笔為你收集整理的javascript模块化简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [译]理解js中的event loop
- 下一篇: [译] 制定良好的路线图:产品负责人的六