前端模塊化小總結—commonJs,AMD,CMD, ES6 的Module
隨著前端快速發展,需要使用javascript處理越來越多的事情,不在局限頁面的交互,項目的需求越來越多,更多的邏輯需要在前端完成,這時需要一種新的模式 --模塊化編程
模塊化的理解:模塊化是一種處理復雜系統分解為更好的可管理模塊的方式。簡單來說就是解耦,簡化開發,一個模塊就是實現特定功能的文件,可以更方便地使用別人的代碼,想要什麽功能,就加載什麽模塊。模塊開發需要遵循一定的規範
CommonJS規範
CommonJS就是一個JavaScript模塊化的規範,是用在服務器端的node的模塊規範,前端的webpack也是對CommonJS原生支持的。
特點:1. 模塊輸出的是一個值的拷貝, 模塊是運行時加載,同步加載
2.CommonJS 模塊的頂層this
指向當前模塊
有兩個API :
require : 加載所要依賴的其他模塊
module.exports 或者exports :對外暴露的接口
示例:新建兩個模塊文件A.js 和 B.js
A.js //寫法一 module.exports = { a:1, b:2 } //寫法二 // module.exports.a=1; // module.exports.b = 2;
//寫法三 // exports.a=1; // exports.b = 2
// 三種寫法結果是一樣,對外暴露的接口的結果是一致的
B.js
console.log(require(‘./B.js‘));//{a:1,b:2}
註意
(1). exports 與module.exports 的區別:exports 是對 module.exports 的引用,不能直接給exports 賦值,直接賦值無效,結果是一個空對象,module.exports 可以直接賦值:如示例
// module.exports = 123; //123
// module.exports = function () { //[Function] // console.log(123) // }
// exports = 123; //{} // exports = function(){//{} // console.log(123) // }
(2). 一個文件不能寫多個module.exports ,如果寫多個,對外暴露的接口是最後一個module.exports
(3). 模塊如果沒有指定使用module.exports 或者exports 對外暴露接口時,在其他文件就引用該模塊,得到的是一個空對象{}
AMD規範
AMD 即 Asynchronous Module Definition,中文名是“異步模塊定義”的意思。它是一個在瀏覽器端模塊化開發的規範,AMD 是 RequireJS 在推廣過程中對模塊定義的規範化產出,所以AMD規範的實現,就是的require.js了
特點 :異步加載,不阻塞頁面的加載,能並行加載多個模塊,但是不能按需加載,必須提前加載所需依賴
Amd 的規範中定義了兩個重要的api
define(id?,[]?,callbakc): //定義聲明模塊,參數id 模塊id標識(可選),參數二是一個數組(可選),依賴其他模塊,最後是回調函數 require([module],callback):// 加載模塊,參數一,是數組,指定加載的模塊,參數二回調函數,模塊加載完成後執行
還有一個配置屬性API:
require.config({ baseUrl: //基本路徑 paths:// 對象,對外加載的模塊名稱 : 鍵值關系,鍵:自定義模塊名稱,值 :文件名或者文件路徑(不要寫文件後綴.js),可以是字符串,數組(如果第一個加載失敗,會加載第二個) shim://對象,配置非AMD 模式的文件,每個模塊要定義(1)exports:值(指在js文件暴露出來的全局變量,如:window.a)(2)deps: 數組,表明該模塊的依賴性 })
註意:paths 的設置加載優化與shim 中配置(AMD模式優先非AMD模式)
以下是例子,加載不同情況的(非AMD 模式,AMD 模式)的例子
示例1 :加載AMD模式模塊
//首先,引入requiresJS //requirejs.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <!--data-main :設置data-main屬性,設置加載主文件路徑,默認設置該目錄為根目錄 如:../js/index.js ,默認把 ../js 作為模塊文件的根模塊 --> <script src="../js/require.js" data-main="../js/index.js" defer async="async" type="text/javascript"></script> </head> </html> //Math.js // 定義了AMD 模式的模塊,並且引入了jquery 模塊 (這裏的jquery屬於AMD模式) define([‘jquery‘],function(_){ //define([‘moudel1‘,‘moudel2‘],callback()) //如果一個模塊不依賴其他模塊,直接使用define()函數寫, //如果一個模塊依賴其他模塊,define()第一個參數是數組 //回調函數的參數對應第一個參數數組的值,按順序 console.log(_);//ƒ (a,b){return new m.fn.init(a,b)} return { add:function(x,y){ return x+y; }, } }) // index.js (主入口) //配置好加載路徑 require.config({ baseUrl:"../js", paths:{ "index":"index", // 主入口文件 index: "jquery":"jquery.min",// jquery 庫,符合AMD模式(1.7 版本以上符合AMD) "maths":"Math", //自定義AMD 模式的模塊 } }) //加載maths.js require(["maths"],function(_math){ console.log( _math); // {add: ƒ } })
示例2:加載非AMD 模式的模塊,依賴非AMD模塊 ,第三方插件(如jquery .lazyload 插件)
這裏用到了shim屬性配置(加載非AMD 模式)
//index.js (主入口) //配置好加載路徑 require.config({ baseUrl:"../js", paths:{ "index":"index", // 主入口文件 index: "jquery":"jquery.min",//jquery 庫(1.7 版本以上符合AMD) "jquery.lazyload":[ // 非AMD 模式 依賴jquery 庫的第三方插件 "http://apps.bdimg.com/libs/jquery-lazyload/1.9.5/jquery.lazyload.min",
"jquery.lazyload"
]
//這裏加載了百度靜態資源cdn和本地的資源(如果第一個加載失敗,會加載本地)}, shim:{ "jquery.lazyload":{ deps:["jquery"]//配置 jquery 依賴 } } }) //加載jquery 和jquery.lazyload require(["jquery","jquery.lazyload"],function($){ $("img.lazy").lazyload({effect: "fadeIn"}); })
示例3 加載非AMD 模式 (閉包形式)的模塊,有依賴(非AMD模式),自定義(與示例2,差不多)
指定了config 配置的 shim 中exports屬性,有返回值
//util.js, //非閉包,暴露了兩個全局變量 utils ,obj var utils = {}; var obj = {}; utils.add = function(v1,v2){ return v1+v2; }; obj.reduce = function(x,y){ return x-y; } //test1.js //非AMD 模式的 (閉包形式),一般插件寫法,使用wondow 對外暴露了objTest 變量 (function(window,$,obj,utils){ console.log($);//ƒ (a,b){return new m.fn.init(a,b)} console.log(utils);//{add: ƒ} console.log(obj);//{reduce: ƒ} window.objTest = {};//window等於對外暴露接口 })(window,jQuery,obj,utils)// 只能寫接口$或者 jQuery(不能寫AMD 的模塊名稱jquery,會報錯undefined) //index.js (主入口) //配置好加載路徑 require.config({ baseUrl:"../js", paths:{ "index":"index", // 主入口文件 index: "jquery":"jquery.min",//jquery 庫(1.7 版本以上符合AMD) }, shim:{ "test1":{// 非AMD 模式的(閉包形式) deps:["util"],//設置依賴util:實際是,加載 了 "../js/util.js" 依賴該js 中的所有全局變量 exports:"objTest" , //在test1.js 文件中,使用了window.objTest 對外暴露了接口 }, "util":{ // 非AMD 模式 (非閉包) deps:["jquery"],//設置依賴 } } }) //加載 test1.js require(["test1"],function(_){ console.log(_);//返回的是一個對象 {},因為在配置中設置了exports console.log("load finshing...") });
//加載 util.js require(["util"],function(_){ console.log(_);//返回的是一個undefind,沒有配置exports console.log("load finshing...") });
分析示例3:在 test1.js 中,閉包函數,一共傳了四個值,分別是 window,jQuery,obj,utils ,
window 就不用說了,jQuery 從哪裏傳來呢?index.js 主文件中的shim 中test1 模塊的依賴只有util (等於加載了util.js 文件,依賴了該文件中的所有全局變量,obj 和utils); 回歸jQuery 從哪裏加載進來,別忘paths 的配置加載優先shim 配置,所以先加載了paths 中的jquery 模塊,即使jquery 也是支持AMD ,但是也是暴露了window.jQuery 全局變量,所以後於jquery 模塊的加載,無論是AMD 還是非AMD 都可以得到該全局變量jQuery
註意 :非AMD 模塊中依賴AMD 模塊,是不可以直接在deps屬性中設置AMD的模塊名作為依賴,這樣是直接報錯的
CMD規範
CMD規範是阿裏的玉伯提出來的,實現js庫為sea.js。 它和requirejs非常類似,即一個js文件就是一個模塊,但是CMD的加載方式更加優秀,是通過按需加載的方式,而不是必須在模塊開始就加載所有的依賴。
玉伯說過能夠親眼看到seajs死掉也是一種幸福(說明了什麽,你懂的)
seajs.config({ //設置別名,方便調用 alias: { ‘jquery‘: ‘ http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js‘ } }); define(function(require, exports, module) { //引用jQuery模塊 var $ = require(‘jquery‘); }); // 加載多個模塊,在加載完成時,執行回調 seajs.use([‘./a‘, ‘./b‘], function(a, b) { a.doSomething(); b.doSomething(); });
es6 的module規範
ES6 在語言標準的層面上,實現了模塊功能,而且非常簡單,ES6到來,完全可以取代 CommonJS 和 AMD規範,成為瀏覽器和服務器通用的模塊解決方案。由vue,Angular React這些mvvm 模式的框架發展,讓前端的編程變得模塊化,組件化。
特點:1. ES6 模塊之中,頂層的this
指向undefined
,即不應該在頂層代碼使用this
。
2. 自動采用嚴格模式"use strict"。須遵循嚴格模式的要求
3. ES6 模塊的設計思想是盡量的靜態化,編譯時加載”或者靜態加載,編譯時輸出接口
4. ES6 模塊export
、
命令可以出現在模塊的任何位置,但是必須處於模塊頂層。如果處於塊級作用域內,就會報錯import
5.ES6 模塊輸出的是值的引用
模塊功能主要由兩個命令構成:export 和
import
。
export:
用於規定模塊的對外接口,
import:
用於輸入其他模塊提供的功能。
1.export 命令
export
命令除了輸出變量,還可以輸出函數或類(class),不同的數據類型
export.js
//變量
export var m = 1; //函數 export function fn(x, y) { return x * y; }; //類class export class class1{} //export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名 function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamV3 };
2.import命令
使用export
命令定義了模塊的對外接口以後,其他 JS 文件就可以通過import
命令加載這個模塊。
main.js
//靜態加載,只加載export.js 文件中三個變量,其他不加載 import {m, fn, streamV1} from ‘./export.js‘; //import命令要使用as關鍵字,將輸入的變量重命名。 import {fn as fn1} from ‘./export.js‘; //整體加載模塊 improt * as all from ‘./export.js‘
3.export default 命令
本質上,export default
就是輸出一個叫做default
的變量或方法
// export-default.js export default function foo() { console.log(‘foo‘); } // 或者寫成 function foo() { console.log(‘foo‘); }
//foo函數的函數名foo,在模塊外部是無效的。加載的時候,視同匿名函數加載 export default foo;
//import-default.js import myfoo from ‘./export-default.js‘;
比較一下默認輸出和正常輸出
// 第一組 export default function crc32() { // 輸出 // ... } import crc32 from ‘crc32‘; // 輸入 // 第二組 export function crc32() { // 輸出 // ... }; import {crc32} from ‘crc32‘; // 輸入
分析:上面代碼的兩組寫法,
第一組是使用export default
時,對應的import
語句不需要使用大括號;
第二組是不使用export default
時,對應的import
語句需要使用大括號。
以下的寫法是有效的
// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同於 // export default add; // app.js import { default as foo } from ‘modules‘; // 等同於 // import foo from ‘modules‘;
瀏覽器加載規則:
1、瀏覽器加載 ES6 模塊,也使用<script>
標簽,但是要加入type="module"
屬性。
瀏覽器對於帶有type="module"
的<script>
,都是異步加載,不會造成堵塞瀏覽器,
<script type="module" src="./foo.js"></script>
2.ES6 模塊也允許內嵌在網頁中,語法行為與加載外部腳本完全一致。
<script type="module"> import utils from "./utils.js"; // other code </script>
es6模塊推薦參考:http://es6.ruanyifeng.com/#docs/module
完結。。。謝謝
前端模塊化小總結—commonJs,AMD,CMD, ES6 的Module