前端模塊化方案

前端模塊化方案

前言

各位在看本片文章之前可以先閱讀以下文章,瞭解下什麼是模塊化?爲什麼要模塊化?模塊化演進過程是怎麼樣的?什麼是模塊化規範?

Javascript模塊化編程(一):模塊的寫法

Javascript模塊化編程(二):AMD規範

Javascript模塊化編程(三):require.js的用法

Javascript模塊化編程(四):CommonJS規範

看過以上的博文之後,相信你對模塊化有所瞭解,模塊化的開發方式可以提高代碼複用率,方便進行代碼的管理。通常一個文件就是一個模塊,有自己的作用域,只向外暴露特定的變量和函數。目前流行的js模塊化規範有CommonJS、AMD、CMD以及ES6的模塊系統。接下來先簡單介紹各規範下的模塊化實現方式。

前端模塊化方案簡介

一、CommonJS

​ Node.js是commonJS規範的主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:module、exports、require、global。實際使用時,用module.exports定義當前模塊對外輸出的接口(不推薦直接用exports),用require加載模塊。

//定義模塊math.js
var basicNum = 0;
function add(a, b) {
    return a + b;
}
module.exports = { //在這裏寫上需要向外暴露的函數、變量
    add: add,
    basicNum: basicNum
}
 
//引用自定義的模塊時,參數包含路徑,可省略.js
var math = require('./math');
math.add(2, 5);
 
//引用核心模塊時,不需要帶路徑
var http = require('http');
http.createService(...).listen(3000);

​ commonJS用同步的方式加載模塊。在服務端,模塊文件都存在本地磁盤,讀取非常快,所以這樣做不會有問題。但是在瀏覽器端,限於網絡原因,更合理的方案是使用異步加載。

二、AMD和require.js

AMD規範採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之後,這個回調函數纔會運行。這裏介紹用require.js實現AMD規範的模塊化:用require.config()指定引用路徑等,用define()定義模塊,用require()加載模塊。

首先我們需要引入require.js文件和一個入口文件main.js。main.js中配置require.config()並規定項目中用到的基礎模塊。

/** 網頁中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>
 
/** main.js 入口文件/主模塊 **/
//首先用config()指定各模塊路徑和引用名
require.config({
    baseUrl: "js/lib",
    paths: {
        "jquery": "jquery.min",  //實際路徑爲js/lib/jquery.min.js
        "underscore": "underscore.min",
    }
});
//執行基本操作
require(["jquery","underscore"],function($,_){
    // some code here
});

​ 引用模塊的時候,我們將模塊名放在[]中作爲reqiure()的第一參數;如果我們定義的模塊本身也依賴其他模塊,那就需要將它們放在[]中作爲define()的第一參數。

//定義math.js模塊
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});
//定義一個依賴underscore.js的模塊
define(['underscore'],function(_){
    var classify = function(list){
        _.countBy(list,function(num){
            return num >30 ? 'old': 'young';
        })
    };
    return {
        classify :classify
    };
})
 
//引用模塊,將模塊放在[]內
require([jquery,math],function($,math){
    var sum = math.add(10,20);
    $("#sum").html(sum);
});

三、CMD和sea.js

require.js在申明依賴的模塊時會在第一之間加載並執行模塊內的代碼:

define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
     // 等於在最前面聲明並初始化了要用到的所有模塊
    if (false) {
        // 即便沒用到某個模塊 b,但 b 還是提前執行了
        b.foo()
    }
});

​ CMD是另一種js模塊化方案,它與AMD很類似,不同點在於:AMD 推崇依賴前置、提前執行,CMD推崇依賴就近、延遲執行。此規範其實是在sea.js推廣過程中產生的。

/** AMD寫法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
     // 等於在最前面聲明並初始化了要用到的所有模塊
    a.doSomething();
    if (false) {
        // 即便沒用到某個模塊 b,但 b 還是提前執行了
        b.doSomething()
    }
});
 
/** CMD寫法 **/
define(function(require, exports, module) {
    var a = require('./a'); //在需要時申明
    a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }
});
 
/** sea.js **/
// 定義模塊 math.js
define(function(require, exports, module) {
    var $ = require('jquery.js');
    var add = function(a,b){
        return a+b;
    }
    exports.add = add;
});
// 加載模塊
seajs.use(['math.js'], function(math){
    var sum = math.add(1+2);
});

四、ES6 Module

ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,旨在成爲瀏覽器和服務器通用的模塊解決方案。其模塊功能主要由兩個命令構成:exportimportexport命令用於規定模塊的對外接口,import命令用於輸入其他模塊提供的功能。

/** 定義模塊 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };
 
/** 引用模塊 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

​ 如上例所示,使用import命令的時候,用戶需要知道所要加載的變量名或函數名。其實ES6還提供了export default命令,爲模塊指定默認輸出,對應的import語句不需要使用大括號。這也更趨近於ADM的引用寫法。

/** export default **/
//定義輸出
export default { basicNum, add };
//引入
import math from './math';
function test(ele) {
    ele.textContent = math.add(99 + math.basicNum);
}

​ ES6的模塊不是對象,import命令會被 JavaScript 引擎靜態分析,在編譯時就引入模塊代碼,而不是在代碼運行時加載,所以無法實現條件加載。也正因爲這個,使得靜態分析成爲可能。

其他可以參考以下博文:

前端工程師必備:前端的模塊化

前端模塊化問題解答

前端模塊化中module.exports、exports、export和export default,import和require區別與聯繫

一、首先搞清楚一個基本問題:

require導入,import module.exportsexports導出,都是與屬於CommonJS模塊規範!

import導入,exportexport default導出,都是屬於ES6語法!

二、module.exports和exports的區別與聯繫

Node應用由模塊組成,採用CommonJS模塊規範。根據這個規範,每個文件就是一個模塊,有自己的作用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其他文件不可見。

普及下:

  • 在javascript中有2種作用域:全局作用域和函數作用域,在瀏覽器端, 全局作用域就是window對象的屬性,函數作用域就是函數內部的對象屬性。
  • 在node中,也有2種作用域:全局作用域和模塊作用域,因此要想實現在nodejs中多個文件中分享變量,就必須定義成全局對象 (global)的屬性, global定義的變量,在任何地方都可以使用,類似於瀏覽器端定義在全局 範圍中的變量。

CommonJS規範規定,每個模塊內部,module變量代表當前模塊。如果使用node命令去console.log(module),你會發現這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,其實是加載該模塊的module.exports屬性。

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

看了剛剛這段commonjs規範上面的介紹可以知道module.exports 和 exports 的關係 :

內存結構示意圖

其實exports變量是指向module.exports,也就是說exports = module.exports 都指向同一個內存地址,加載模塊實際是加載該模塊的module.exports

var exports = module.exports;
其實就是幹了這麼一件事
exports = module.exports = {};

於是我們可以直接在 exports 對象上添加方法,表示對外輸出的接口,如同在module.exports上添加一樣。注意,不能直接將exports變量指向一個值,因爲這樣等於切斷了exports與module.exports的聯繫。直接看例子明瞭。

// a.js
let a = 100;

exports.a = 200;
// 然後把a指向另外一個地址
exports = '另外的地址'

// b.js
// 引入模塊

var example = require('./a.js')
console.log(example.a)  // 200

可以看出,require導出的內容是module.exports的內容,而不是exports的,簡單來說,exports用來幫助module.exports操作內存中的數據。來看下nodejs中require的加載機制。

function require(...) {  
  var module = { exports: {} };
  ((module, exports) => {
    // 你的被引入代碼 Start
    // var exports = module.exports = {}; (默認都有的)
    function some_func() {};
    exports = some_func;
    // 此時,exports不再掛載到module.exports,
    // export將導出{}默認對象
    module.exports = some_func;
    // 此時,這個模塊將導出some_func對象,覆蓋exports上的some_func    
     // 你的被引入代碼 End
  })(module, module.exports);
 // 不管是exports還是module.exports,最後返回的還是module.exports 
  return module.exports;
}
console.log(exports); // {}  
console.log(module.exports);  // {}  
console.log(exports === module.exports);    // true  
console.log(exports == module.exports);        // true  
console.log(module);
/**
 Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/larben/Desktop/demo.js',
  loaded: false,
  children: [],
  paths:
   [ '/Users/larben/Desktop/node_modules',
     '/Users/larben/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }
 */

一句話概括重點:exports就是module.exports的引用,最簡單避錯的方法就是儘量都用 module.exports。

如果不太理解,提供以下博文作爲參考:

module.exports和moudle和exports的區別很容易理解

exports 和 module.exports 的區別

四、export和export default的區別與聯繫

模塊功能主要由:exportimport構成export導出模塊的對外接口,import命令導入其他模塊暴露的接口。

export其實和export default就是寫法上面有點差別,一個是導出一個個單獨接口,一個是默認導出一個整體接口。使用import命令的時候,用戶需要知道所要加載的變量名或函數名,否則無法加載。這裏就有一個簡單寫法不用去知道有哪些具體的暴露接口名,就用export default命令,爲模塊指定默認輸出。

export可以這樣寫

// testA.js
var f = 'Miel';
var name = 'Jack';
var data= 1988;

export {f, name, data};

使用export命令定義了模塊的對外接口以後,其他 JS 文件就可以通過import命令加載這個模塊。

// main.js
import {f, name, data} from './testA';

export default可以這樣寫

// export-default.js
export default function () {
  console.log('foo');
}
// 或者寫成

function foo() {
  console.log('foo');
}
export default foo;
// import-default.js
import customName from './export-default';
customName(); // 'foo'

下面比較一下export default和export 輸出。

// 第一組
export default function car() { // 輸出
  // ...
}

import car from 'car'; // 輸入

// 第二組
export function car2() { // 輸出
  // ...
};

import {car2} from 'car2'; // 輸入

可以看到第一組是使用export default,import語句不需要使用大括號;第二組使用export,對應的import語句需要使用大括號,一個模塊只能有一個默認輸出,所以export default只能使用一次。

再看個例子直接就明白了

//a.js
export const a = '100'  // 方式一
const Stark = function(){ 
    console.log('im ironman')
}// 方式二

export { Stark }


// export default方式
const mark85 = 1000
export default mark85
// b.js
import { a, Stark } from './a.js';  // 引入export導出的數據
import ironman from './a.js' // 引入export default導出的數據
import * as hero from './a.js' // 集合成對象一併導出,es6語法的本意是想將 es6 模塊的所有命名輸出以及defalut輸出打包成一個對象賦值給hero變量。as簡單的說就是取一個別名,export中可以用,import中其實可以用:

console.log(a) // 100
Stark(); // im ironman
console.log(ironman) // 1000
console.log(hero.a) // 100
console.log(hero.default) // 1000

一句話重點:export 和 export default的區別:

  • 在一個文件或模塊中,export、import可以有多個,export default僅有一個;
  • 通過export方式導出,在導入時要加{ },export default則不需要;
  • export能直接導出變量表達式,export default不行。

五、import和require的區別與聯繫
看了上面其實已經清楚了,import和require是分別屬於ES6和CommonJS的兩種導入模塊的語法而已。

其他可以參考以下博文:

import、require、export、module.exports 混合使用詳解

*徹底搞清楚javascript中的require、import和export

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章