前陣子一直忙着找實習,發現已經有一段時間沒寫博客了,面試不少時候會被問到模塊化,今天就讓咱們一塊兒來總結下把html
1、什麼是模塊化java
在js出現的時候,js通常只是用來實現一些簡單的交互,後來js開始獲得重視,用來實現愈來愈複雜的功能,而爲了維護的方便,咱們也把不一樣功能的js抽取出來當作一個js文件,可是當項目變的複雜的時候,一個html頁面可能須要加載好多個js文件,而這個時候就會出現各類命名衝突,若是js也能夠像java同樣,把不一樣功能的文件放在不一樣的package中,須要引用某個函數或功能的時候,import下相關的包,這樣能夠很好的解決命名衝突等各類問題,可是js中沒有模塊的概念,又怎麼實現模塊化呢jquery
模塊化開發是一種管理方式,是一種生產方式,一種解決問題的方案,一個模塊就是實現特定功能的文件,有了模塊,咱們就能夠更方便地使用別人的代碼,想要什麼功能,就加載什麼模塊,可是模塊開發須要遵循必定的規範,不然就都亂套了,所以,纔有了後來你們熟悉的AMD規範,CMD規範es6
接下來,咱們就一塊兒學習下AMD,CMD和es6中的模塊化吧面試
2、AMD數組
AMD 即Asynchronous Module Definition,中文名是「異步模塊定義」的意思,它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行,全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行
瀏覽器
通常來講,AMD是 RequireJS 在推廣過程當中對模塊定義的規範化的產出,由於平時在開發中比較經常使用的是require.js進行模塊的定義和加載,通常是使用define來定義模塊,使用require來加載模塊服務器
一、定義模塊異步
AMD規範只定義了一個函數define,它是全局變量,咱們能夠用它來定義一個模塊模塊化
define(id?, dependencies?, factory);
其中,id是定義中模塊的名字,這個參數是可選的,若是沒有提供該參數,模塊的名字應該默認爲模塊加載器請求的指定腳本的名字,若是提供了該參數,模塊名必須是「頂級」的和絕對的
dependencies是定義的模塊中所依賴模塊的數組,依賴模塊必須根據模塊的工廠方法優先級執行,而且執行的結果應該按照依賴數組中的位置順序以參數的形式傳入(定義中模塊的)工廠方法中
factory是模塊初始化要執行的函數或對象,若是爲函數,它應該只被執行一次,若是是對象,此對象應該爲模塊的輸出值
下面來看一個定義模塊的例子
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //Or: return require("beta").verb(); } });
上面的代碼定義了一個alpha的模塊,這個模塊依賴require,exports,beta,所以須要先加載它們,再執行後面的factory
二、加載模塊
require.js中採用require()語句加載模塊,在定義好了模塊後,咱們可使用require進行模塊的加載
require([module], callback);
require要傳入兩個參數,第一個參數[module],是一個數組,裏面的成員就是要加載的模塊,第二個參數callback,則是加載成功以後的回調函數
下面咱們來看一個例子
require([increment'], function (increment) {
increment.add(1);
});
上面的代碼中,好比咱們如今已經定義了一個模塊,名字爲increment,裏面有一個add方法,咱們如今須要用到裏面的方法,只要像上面同樣將模塊加載進來,而後調用方法就能夠了
三、requirejs使用例子
在使用require.js時,能夠經過define()定義模塊,這時候裏面的模塊的方法和變量外部是沒法訪問到的,只有經過return,而後再加載這個模塊,才能夠進行訪問
define('math',['jquery'], function ($) {//引入jQuery模塊 return { add: function(x,y){ return x + y; } }; });
上面的代碼定義了一個math模塊,返回了一個add方法,要使用這個模塊的方法,咱們須要向下面這樣進行訪問
require(['jquery','math'], function ($,math) { console.log(math.add(10,100));//110 });
經過require,咱們加載了math模塊,這樣就可使用math模塊裏面的add方法了
3、CMD
define(factory);
define接受factory參數,factory能夠是一個函數,也能夠是一個對象或字符串
factory爲對象、字符串時,表示模塊的接口就是該對象、字符串,好比能夠以下定義一個 JSON 數據模塊
define({ "foo": "bar" });
也能夠經過字符串定義模板模塊
define('I am a template. My name is {{name}}.');
factory爲函數時,表示是模塊的構造方法,執行該構造方法,能夠獲得模塊向外提供的接口,factory方法在執行時,默認會傳入三個參數:require,exports和 module
define(function(require, exports, module) { // 模塊代碼 });
其中,require用來加載其它模塊,而exports能夠用來實現向外提供模塊接口
define(function(require, exports) { // 對外提供 foo 屬性 exports.foo = 'bar'; // 對外提供 doSomething 方法 exports.doSomething = function() {}; });
module是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法,傳給factory構造方法的exports參數是module.exports對象的一個引用,只經過exports參數來提供接口,有時沒法知足開發者的全部需求,好比當模塊的接口是某個類的實例時,須要經過module.exports來實現
define(function(require, exports, module) { // exports 是 module.exports 的一個引用 console.log(module.exports === exports); // true // 從新給 module.exports 賦值 module.exports = new SomeClass(); // exports 再也不等於 module.exports console.log(module.exports === exports); // false });
說了這麼多,相信你們可能有點亂,來個簡單的例子,咱們看看使用AMD和CMD定義的模塊的寫法
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此處略去 100 行 var b = require('./b') // 依賴能夠就近書寫 b.doSomething() // ... }) // AMD 默認推薦的是 define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ... })
在上面的代碼中,相信你們很容易能夠看出區別吧,AMD和CMD都是經過define()定義模塊,AMD須要把依賴的模塊先寫出來,能夠經過return暴露接口,CMD在定義模塊須要傳入require,exports和module這幾個參數,要加載某個模塊時,使用require進行加載,要暴露接口時,能夠經過exports,module.exports和return
二、加載模塊
在前面定義模塊時,咱們說過,當factory爲函數時,require會做爲默認參數傳遞進去,而require能夠實現模塊的加載
require是一個方法,接受模塊標識做爲惟一參數,用來獲取其餘模塊提供的接口
define(function(require, exports) { // 獲取模塊 a 的接口 var a = require('./a'); // 調用模塊 a 的方法 a.doSomething(); });
// 定義模塊 myModule.js define(function(require, exports, module) { var $ = require('jquery.js') $('div').addClass('active'); exports.data = 1; }); // 加載模塊 seajs.use(['myModule.js'], function(my){ var star= my.data; console.log(star); //1 });
上面的代碼中定義了myModule.js模塊,由於該模塊依賴於jquery.js,所以在須要使用該模塊時可使用require進行模塊的加載,而後經過exports暴露出接口,經過SeaJS的use方法咱們能夠加載該模塊,而且使用該模塊暴露出的接口
4、es6中的模塊化
在es6沒有出來以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種,前者用於服務器,後者用於瀏覽器,ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案
es6中的模塊化有一個比較大的特色,就是實現儘可能的靜態化,好比說在CommonJS中咱們要加載fs中的幾個方法,須要這樣寫
// CommonJS模塊 let { stat, exists, readFile } = require('fs'); // 等同於 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
上面的代碼實際上是加載了fs中的全部方法,生成一個對象,再從這個對象上讀取方法,這種加載其實叫作運行時加載,也就是隻有運行時才能獲得這個對象,不能實如今編譯時實現靜態優化
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入
// ES6模塊 import { stat, exists, readFile } from 'fs';
上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載,這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高,固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象
一、export
模塊功能主要由兩個命令構成:export和import,export命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能
通常來講,一個模塊就是一個獨立的文件,該文件內部的全部變量,外部沒法獲取,若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量
// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958;
若是要輸出函數,能夠像下面這樣定義
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
上面的代碼中,咱們使用了as對函數的對外接口進行了重命名
二、import
使用export命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import命令加載這個模塊
// main.js import {firstName, lastName, year} from './profile.js'; function setName(element) { element.textContent = firstName + ' ' + lastName; }
import命令接受一對大括號,裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同
咱們也能夠對加載的模塊進行重命名
import { lastName as surname } from './profile.js';
除了指定加載某個輸出值,還可使用總體加載,即用星號(*
)指定一個對象,全部輸出值都加載在這個對象上面
下面是一個circle.js文件,它輸出兩個方法area和circumference
// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; }
總體加載的寫法以下
import * as circle from './circle'; console.log('圓面積:' + circle.area(4)); console.log('圓周長:' + circle.circumference(14));
這裏有一個地方須要注意,模塊總體加載所在的那個對象(上例是circle
),應該是能夠靜態分析的,因此不容許運行時改變,下面的寫法都是不容許的
import * as circle from './circle'; // 下面兩行都是不容許的 circle.foo = 'hello'; circle.area = function () {};
關於import其實還有不少用法,具體的你們能夠查看相關的文檔
今天就先介紹到這裏,其實還有commonjs,尚未進行介紹,若是你們感興趣,能夠查看相關的用法呢