閱讀原文javascript
模塊一般是指編程語言所提供的代碼組織機制,利用此機制可將程序拆解爲獨立且通用的代碼單元。css
模塊化主要是解決代碼分割、做用域隔離、模塊之間的依賴管理以及發佈到生產環境時的自動化打包與處理等多個方面。html
javascript應用日益複雜,模塊化已經成爲一個迫切需求。可是做爲一個模塊化方案,它至少要解決以下問題:前端
最原始的方式就是,每一個文件就是一個模塊,而後使用script的方式進行引入。vue
可是此方式有如下問題:java
爲了解決做用域污染的問題,就產生了當即執行函數 + 模塊對象模式:jquery
// app1.js
var app = {};
複製代碼
// app2.js
(function(){
app.a = function(a, b) {
// code
}
})();
複製代碼
// app3.js
(function(app){
var temp = [ 1, 2];
var a = app.a(temp)
})(app);
複製代碼
具體的能夠查閱阮一峯老師的博客Javascript模塊化編程(一):模塊的寫法webpack
在ES6以前,js沒有塊級做用域,因此採用此方式創建一個函數做用域。可是在ES6以後,可使用塊級做用域。git
因爲使用了IIFE,因此減小了全局做用域污染,但並非完全消除,由於還定義了一個appa模塊對象呢。github
因此這也僅僅只是減小了做用域污染,仍是會有其餘缺點。
後來,有人試圖將javascript引入服務端,因爲服務端編程相對比較複雜,就急需一種模塊化的方案,因此就誕生了commonjs,有require + module.exports實現模塊的加載和導出。
CommonJS採用同步的方式加載模塊,主要使用場景爲服務端編程。由於服務器通常都是本地加載,速度較快。
後來,隨着前端業務的日漸複雜,瀏覽器端也須要模塊化,可是commonjs是同步加載的,這意味着加載模塊時,瀏覽器會凍結,什麼都幹不了,這在瀏覽器確定是不行的,這就誕生了AMD和CMD規範,分別以requirejs和seajs爲表明。
這兩貨都採用異步方式加載模塊。
AMD(Asynchronous Module Defination)異步模塊加載機制。
define(
[module_id,] // 模塊名字,若是缺省則爲匿名模塊
[dependenciesArray,] // 模塊依賴
definition function | object // 模塊內容,能夠爲函數或者對象 ); 複製代碼
CMD(Common Module Defination)通用模塊加載機制
// 方式一
define(function(require, exports, module) {
// 模塊代碼
var a = require('a')
});
// 方式二
define( 'module', ['module1', 'module2'], function( require, exports, module ){
// 模塊代碼
} );
複製代碼
儘管以上方案解決了上面說的問題,可是也帶來了一些新問題:
因爲上述這些緣由,有些人想在瀏覽器使用 CommonJS 規範,但 CommonJS 語法主要是針對服務端且是同步的,因此就產生了Browserify,它是一個 模塊打包器(module bundler),能夠打包commonjs規範的模塊到瀏覽器中使用。
UMD(Universal Module Definition) 統一模塊定義。
AMD 與 CommonJS 雖然師出同源,但仍是分道揚鑣,關注於代碼異步加載與最小化入口模塊的開發者將目光投注於 AMD;而隨着 Node.js 以及 Browserify 的流行,愈來愈多的開發者也接受了 CommonJS 規範。使人扼腕嘆息的是,符合 AMD 規範的模塊並不能直接運行於 CommonJS 模塊規範的環境中,符合 CommonJS 規範的模塊也不能由 AMD 進行異步加載。
並且有這麼多種規範,若是咱們要發佈一個模塊供其餘人用,咱們不可能爲每種規範發佈一個版本,就算你蛋疼這樣作了,別人使用的時候還得下載對應版本,因此如今須要一種方案來兼容這些規範。
實現的方式就是在代碼前面作下判斷,根據不一樣的規範使用對應的加載方式。
// 以vue爲例
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () {
// vue code ...
})
複製代碼
因爲目前ES6瀏覽器支持還不夠好,因此不少第三方庫都採用了這種方式。
ES6引入了ESModule規範,主要經過export + import來實現,最終一統江湖。但是現實很骨感,一些瀏覽器並不支持(IE,說的就是你),因此還不能直接在瀏覽器中直接使用。
在頁面上加載一個AMD/CMD模塊格式解釋器。這樣瀏覽器就認識了define, exports,module這些東西,也就實現了模塊化。
SystemJS 是一個通用的模塊加載器,它能在瀏覽器或者 NodeJS 上動態加載模塊,而且支持 CommonJS、AMD、全局模塊對象和 ES6 模塊。經過使用插件,它不只能夠加載 JavaScript,還能夠加載 CoffeeScript 和 TypeScript。配合jspm也是不錯的搭配。
相比於第一種方案,這個方案更加智能。因爲是預編譯的,不須要在瀏覽器中加載解釋器。你在本地直接寫JS,無論是AMD/CMD/ES6風格的模塊化,它都能認識,而且編譯成瀏覽器認識的JS。
注意: browerify只支持Commonjs模塊,如需兼容AMD模塊,則須要plugin轉換
前身爲ServerJS。 咱們能夠理解爲代碼會被以下內建輔助函數包裹:
(function (exports, require, module, __filename, __dirname) {
// ...
// Your code
// ...
});
複製代碼
經過require加載模塊。
const a = require('a')
複製代碼
經過exports
和module.exports
進行模塊導出。
exports
:exports
是module.exports
的一個引用,一個模塊可使用屢次,可是不能直接對exports
從新賦值,只能經過以下方式使用exports.a = function(){
// code...
}
複製代碼
module.exports
:一個模塊只能使用一次module.exports = function(){
// code...
}
複製代碼
在引入requirejs的script標籤上添加data-main
屬性定義入口文件,該文件會在requirejs加載完後當即執行。
若是baseUrl未單獨配置,則默認爲引入require的文件的路徑。
<script src="./assets/lib/requirejs/require.js" data-main="./assets/lib/requirejs/config"></script>
複製代碼
requirejs.config({
// 爲模塊加上query參數,解決瀏覽器緩存,只在開發環境使用
urlArgs: 'yn-course=' + (new Date()).getTime(),
// 配置全部模塊加載的初始路徑,全部模塊都是基於此路徑加載
baseUrl: './',
// 映射一些快捷路徑,至關於別名
paths: {
'~': 'assets',
'@': 'components',
'vue': 'assets/lib/vue/vue',
'vueRouter': 'assets/lib/vue-router/vue-router',
"jquery" : ["http://libs.baidu.com/jquery/2.0.3/jquery"]
},
// 對於匹配的模塊前綴,使用一個不一樣的模塊ID來加載該模塊
map: {
'layer': {
'jquery': 'http://libs.baidu.com/jquery/2.0.3/jquery'
}
},
// 從CommonJS包(package)中加載模塊
packages:{},
// 加載上下文
context:{},
// 超時,默認7S
waitSeconds: 7,
// 定義應用依賴的模塊,在啓動後會加載此數組中的模塊
deps: [],
// 在deps加載完畢後執行的函數
callback:function(){},
// 用來加載非AMD規範的模塊,以瀏覽器全局變量注入,此處僅做映射,須要在須要時手動載入
shim: {
// 'backbone': {
// deps: ['underscore', 'jquery'], // 模塊依賴
// exports: 'Backbone' // 導出的名稱
// }
},
// 全局配置信息,可在模塊中經過module.config()訪問
config:{
color:'red'
},
// 若是設置爲true,則當一個腳本不是經過define()定義且不具有可供檢查的shim導出字串值時,就會拋出錯誤
enforceDefine:false,
// 若是設置爲true,則使用document.createElementNS()去建立script元素
xhtm: false,
//指定RequireJS將script標籤插入document時所用的type=""值
scriptType:'text/javascript'
});
複製代碼
默認requirejs會根據baseUrl+paths配置去查找模塊,可是以下狀況例外:
設置baseURl的方式有以下三種:
map配置對於大型項目很重要:若有兩類模塊須要使用不一樣版本的"foo",但它們之間仍須要必定的協同。 在那些基於上下文的多版本實現中很難作到這一點。並且,paths配置僅用於爲模塊ID設置root paths,而不是爲了將一個模塊ID映射到另外一個。
requirejs.config({
map: {
'some/newmodule': {
'foo': 'foo1.2'
},
'some/oldmodule': {
'foo': 'foo1.0'
}
}
});
複製代碼
經過define來定義模塊,推薦依賴前置原則,固然也可使用require動態按需加載。
define(
[module_id,] // 模塊名字,若是缺省則爲匿名模塊
[dependencies,] // 模塊依賴
definition function | object // 模塊內容,能夠爲函數或者對象 ); 複製代碼
// 若是僅僅返回一個鍵值對,能夠採用以下格式,相似JSONP
define({
color: "black",
size: "unisize"
})
//若是沒有依賴
define(function () {
return {
color: "black",
size: "unisize"
}
})
// 有依賴
define(["./a", "./b"], function(a, b) {
})
// 具名模塊
define("name",
["c", "d"],
function(cart, inventory) {
//此處定義foo/title object
}
)
複製代碼
如要在define()內部使用諸如require("./a/b")
相對路徑,記得將"require"自己做爲一個依賴注入到模塊中:
define(["require", "./a/b"], function(require) {
var mod = require("./a/b");
});
複製代碼
或者使用以下方式:
define(function(require) {
var mod = require("./a/b");
})
複製代碼
require加載的全部模塊都是單例的,每一個模塊都有一個惟一的標識,這個標識是模塊的名字或者模塊的相對路徑(如匿名模塊)。
模塊的惟一性與它們的訪問路徑無關,即便是地址徹底相同的一份JS文件,若是引用的方式與模塊的配置方式不一致,依舊會產生多個模塊。
// User.js
define([], function() {
return {
username : 'yiifaa',
age : 20
};
});
複製代碼
require(['user/User'], function(user) {
// 修改了User模塊的內容
user.username = 'yiifee';
// em/User以baseUrl定義的模塊進行訪問
// 'user/User'以path定義的模塊進行訪問
require(['em/User', 'user/User'], function(u1, u2) {
// 輸出的結果徹底不相同,u1爲yiifaa,u2爲修改後的內容yiifee
console.log(u1, u2);
})
})
複製代碼
requirejs推薦依賴前置,在define或者require模塊的時候,能夠將須要依賴的模塊做爲第一個參數,以數組的方式聲明,而後在回調函數中,依賴會以參數的形式注入到該函數上,參數列表須要和依賴數組中位置一一對應。
define(["./a", "./b"], function(a, b) {
})
複製代碼
在requirejs中,有3中方式進行模塊導出:
define(function(require, exports, module) {
return {
a : 'a'
}
});
複製代碼
define(function(require, exports, module) {
module.exports = {
a : 'a'
}
});
複製代碼
define(function(require, exports, module) {
exports.a = 'a'
});
複製代碼
requirejs提供了兩個全局變量require
、requirejs
供咱們加載模塊,這兩者是徹底等價的。
// 此處require 和 define 函數僅僅是一個參數(模塊標識)的差別,
// 通常require用於沒有返回的模塊,如應用頂層模塊
require(
[dependencies,] // 模塊依賴
definition function // 模塊內容 ); 複製代碼
require是內置模塊,不用在配置中定義,直接進行引用便可。
define(['require'], function(require) {
var $ = require('jquery');
})
複製代碼
requirejs支持異步(require([module])
)和同步(require(module)
)兩種方式加載,即require參數爲數組即爲異步加載,反之爲同步。
在requirejs中,執行同步加載必須知足兩點要求:
define(function(require, exports, module) { })
中能夠同步加載模塊// 假定這裏引用的資源有數十個,回調函數的參數一定很是多
define(['jquery'], function() {
return function(el) {
// 這就是傳說中的同步調用
var $ = require('jquery');
$(el).html('Hello, World!');
}
})
複製代碼
define(['jquery', 'prototype'], function() {
var export = {};
export.jquery = function(el) {
// 這就是傳說中的同步調用
var $ = require('jquery');
$(el).html('Hello, World!');
}
export.proto = function(el) {
// 這就是傳說中的同步調用
var $ = require('prototype');
$(el).html('Hello, World!');
}
return export;
})
複製代碼
define([],function())
:依賴數組中的模塊會異步加載,全部模塊加載完成後混執行回調函數require([])
:傳入數組格式即表示須要異步加載require === requirejs //=> true
複製代碼
require.toUrl("./a.css")
: 獲取模塊url只要頁面不刷新,被requirejs加載的模塊只會執行一次,後面會一直緩存在內存中,即時從新引入模塊也不會再進行初始化。 咱們能夠經過undef
卸載已加載的模塊。
require.undef("moduleName") // moduleName是模塊標識
複製代碼
Module name has not been loaded yet for context: _
:此錯誤表示執行時模塊還未加載成功,通常爲異步加載所致,改爲同步加載便可。
//C模塊
define([],function(){
// 定義一個類
function DemoClass()
{
var count = 0;
this.say = function(){
count++;
return count;
};
}
return function(){
//每次都返回一個新對象
return new DemoClass();
};
});
// A模塊
require(['C'],
function(module) {
cosole.log(module().say());//1
});
// B模塊
require(['C'],
function(module) {
cosole.log(module().say());//1
});
複製代碼
Sea.js 追求簡單、天然的代碼書寫和組織方式,具備如下核心特性:
經過exports + require實現模塊的加載與導出。
<script src="assets/lib/seajs/sea.js"></script>
<script src="assets/lib/seajs/seajs.config.js"></script>
<script type="text/javascript">
seajs.use('app');
</script>
複製代碼
//seajs配置
seajs.config({
//1.頂級標識始終相對 base 基礎路徑解析。
//2.絕對路徑和根路徑始終相對當前頁面解析。
//3.require 和 require.async 中的相對路徑相對當前模塊路徑來解析。
//4.seajs.use 中的相對路徑始終相對當前頁面來解析。
// Sea.js 的基礎路徑 在解析頂級標識時,會相對 base 路徑來解析 base 的默認值爲 sea.js 的訪問路徑的父級
base: './',
// 路徑配置 當目錄比較深,或須要跨目錄調用模塊時,可使用 paths 來簡化書寫
paths: {
gallery: "https://a.alipayobjects.com/gallery"
/*
var underscore = require('gallery/underscore');
//=> 加載的是 https://a.alipayobjects.com/gallery/underscore.js
*/
},
// 別名配置 當模塊標識很長時,可使用 alias 來簡化(至關於 base 設置的目錄爲基礎)
//Sea.js 在解析模塊標識時, 除非在路徑中有問號(?)或最後一個字符是井號(#),不然都會自動添加 JS 擴展名(.js)。若是不想自動添加擴展名,能夠在路徑末尾加上井號(#)。
alias: {
'seajs-css': '~/lib/seajs/plugins/seajs-css',
'seajs-text': '~/lib/seajs/plugins/seajs-text',
'$': '~/lib/zepto/zepto'
},
// 變量配置 有些場景下,模塊路徑在運行時才能肯定,這時可使用 vars 變量來配置
vars: {
//locale: "zh-cn"
/*
var lang = require('./i18n/{locale}.js');
//=> 加載的是 path/to/i18n/zh-cn.js
*/
},
// 映射配置 該配置可對模塊路徑進行映射修改,可用於路徑轉換、在線調試等
map: [
//[".js", "-debug.js"]
/*
var a = require('./a');
//=> 加載的是 ./js/a-debug.js
*/
],
// 預加載項 在普通模塊加載前,提早加載並初始化好指定模塊 preload 中的配置,須要等到 use 時才加載
preload: ['seajs-css','seajs-text'],
// 調試模式 值爲 true 時,加載器不會刪除動態插入的 script 標籤。插件也能夠根據 debug 配置,來決策 log 等信息的輸出
debug: true,
// 文件編碼 獲取模塊文件時,<script> 或 <link> 標籤的 charset 屬性。 默認是 utf-8 還能夠是一個函數
charset: 'utf-8'
});
複製代碼
用來在頁面中加載一個或多個模塊。seajs.use 理論上只用於加載啓動,不該該出如今 define 中的模塊代碼裏。在模塊代碼裏須要異步加載其餘模塊時,推薦使用 require.async 方法。
// 加載一個模塊
seajs.use('./a');
// 加載一個模塊,在加載完成時,執行回調
seajs.use('./a', function(a) {
a.doSomething();
});
// 加載多個模塊,在加載完成時,執行回調
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.doSomething();
});
複製代碼
// 方式一
define(function(require, exports, module) {
// 模塊代碼
var a = require('a')
});
// 方式二,此方法嚴格來講不屬於CMD規範
define( 'module', ['module1', 'module2'], function( require, exports, module ){
// 模塊代碼
});
// 若是模塊內容僅是對象或者字符串
define({ "foo": "bar" });
define('I am a template. My name is {{name}}.');
複製代碼
require 是一個方法,接受 模塊標識做爲惟一參數,用來獲取其餘模塊提供的接口。
此方式,require 的參數值 必須 是字符串直接量。
var a = require('./a');
複製代碼
require.async
方法用來在模塊內部異步加載模塊,並在加載完成後執行指定回調。callback 參數可選。 此時,參數值能夠是動態的,以實現動態加載。
define(function(require, exports, module) {
// 異步加載一個模塊,在加載完成時,執行回調
require.async('./b', function(b) {
b.doSomething();
});
// 異步加載多個模塊,在加載完成時,執行回調
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
複製代碼
require.resolve
使用模塊系統內部的路徑解析機制來解析並返回模塊絕對路徑。
define(function(require, exports) {
console.log(require.resolve('./b'));
// ==> http://example.com/path/to/b.js
});
複製代碼
exports 是一個對象,用來向外提供模塊接口,也可使用return或者module.exports來進行導出
define(function(require, exports) {
// 對外提供 foo 屬性
exports.foo = 'bar';
// return
return {
foo: 'bar',
doSomething: function() {}
};
// module.exports
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
複製代碼
module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。
文檔:官方文檔
在 ES6 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6 的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict";
。
嚴格模式主要有如下限制:
定義模塊的對外接口。 一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。 如下是幾種用法:
//------輸出變量------
export var firstName = 'Michael';
export var lastName = 'Jackson';
//等價於
var firstName = 'Michael';
export {firstName}; //推薦,能清除知道輸出了哪些變量
//------輸出函數或類------
export function multiply(x, y) {
return x * y;
};
//------輸出並as重命名------
var v1 = 'Michael';
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2
};
//------輸出default------
export default function () { ... }
複製代碼
注意:export default
在一個模塊中只能有一個。
使用export命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import命令加載這個模塊。 如下是幾種用法,必須和上面的export對應:
//------加載變量、函數或類------
import {firstName, lastName} from './profile.js';
//------加載並as重命名------
import { lastName as surname } from './profile.js';
//------加載有default輸出的模塊------
import v1 from './profile.js';
//------執行所加載的模塊------
import 'lodash';
//------加載模塊全部輸出------
import * as surname from './profile.js';
複製代碼
若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。
export { foo, bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
export { foo, bar };
複製代碼