js中的模塊化


  前陣子一直忙着找實習,發現已經有一段時間沒寫博客了,面試很多時候會被問到模塊化,今天就讓我們一起來總結下把

 

一、什么是模塊化

  在js出現的時候,js一般只是用來實現一些簡單的交互,后來js開始得到重視,用來實現越來越復雜的功能,而為了維護的方便,我們也把不同功能的js抽取出來當做一個js文件,但是當項目變的復雜的時候,一個html頁面可能需要加載好多個js文件,而這個時候就會出現各種命名沖突,如果js也可以像java一樣,把不同功能的文件放在不同的package中,需要引用某個函數或功能的時候,import下相關的包,這樣可以很好的解決命名沖突等各種問題,但是js中沒有模塊的概念,又怎么實現模塊化呢

  模塊化開發是一種管理方式,是一種生產方式,一種解決問題的方案,一個模塊就是實現特定功能的文件,有了模塊,我們就可以更方便地使用別人的代碼,想要什么功能,就加載什么模塊,但是模塊開發需要遵循一定的規范,否則就都亂套了,因此,才有了后來大家熟悉的AMD規范,CMD規范

  接下來,我們就一起學習下AMD,CMD和es6中的模塊化吧

 

二、AMD

  AMD 即Asynchronous Module Definition,中文名是“異步模塊定義”的意思,它采用異步方式加載模塊,模塊的加載不影響它后面語句的運行,所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之后,這個回調函數才會運行

  一般來說,AMD是 RequireJS 在推廣過程中對模塊定義的規范化的產出,因為平時在開發中比較常用的是require.js進行模塊的定義和加載,一般是使用define來定義模塊,使用require來加載模塊

1、定義模塊

  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

2、加載模塊

  require.js中采用require()語句加載模塊,在定義好了模塊后,我們可以使用require進行模塊的加載

require([module], callback);

  require要傳入兩個參數,第一個參數[module],是一個數組,里面的成員就是要加載的模塊,第二個參數callback,則是加載成功之后的回調函數

  下面我們來看一個例子

require([increment'], function (increment) {
    increment.add(1);
});

  上面的代碼中,比如我們現在已經定義了一個模塊,名字為increment,里面有一個add方法,我們現在需要用到里面的方法,只要像上面一樣將模塊加載進來,然后調用方法就可以了

3、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方法了

 

三、CMD

   CMD 即Common Module Definition通用模塊定義,CMD規范是國內發展出來的,同時,CMD是在SeaaJS推廣的過程中形成的,CMD和AMD要解決的都是同個問題,在使用上也都很像,只不過兩者在模塊定義方式和模塊加載時機上有所不同
1、定義模塊
  在 CMD 規范中,一個模塊就是一個文件,通過define()進行定義
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

2、加載模塊

  在前面定義模塊時,我們說過,當factory為函數時,require會作為默認參數傳遞進去,而require可以實現模塊的加載

  require是一個方法,接受模塊標識作為唯一參數,用來獲取其他模塊提供的接口

define(function(require, exports) {

  // 獲取模塊 a 的接口
  var a = require('./a');

  // 調用模塊 a 的方法
  a.doSomething();

});
   從上面定義模塊和加載模塊的方式上,我們也可以看出AMD和CMD主要有下面幾個不同:
      (1)AMD是RequireJS在推廣過程中對模塊定義的規范化產出,CMD是SeaJS在推廣過程中對模塊定義的規范化產出
      (2)對於依賴的模塊,AMD是提前執行,CMD是延遲執行
      (3)對於依賴的模塊,AMD推崇依賴前置,CMD推崇依賴就近
3、seajs使用例子
  因為CMD是SeaJS在推廣過程中對模塊定義的規范化產出,因此一般在實際開發中,我們都是通過SeaJS進行模塊的定義和加載
  下面是一個簡單的例子
// 定義模塊  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方法我們可以加載該模塊,並且使用該模塊暴露出的接口

 

四、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 模塊本身,因為它不是對象

1、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對函數的對外接口進行了重命名

2、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,還沒有進行介紹,如果大家感興趣,可以查看相關的用法呢

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM