js中的模塊化

  前陣子一直忙着找實習,發現已經有一段時間沒寫博客了,面試不少時候會被問到模塊化,今天就讓咱們一塊兒來總結下把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

   CMD 即Common Module Definition通用模塊定義,CMD規範是國內發展出來的,同時,CMD是在SeaaJS推廣的過程當中造成的,CMD和AMD要解決的都是同個問題,在使用上也都很像,只不過二者在模塊定義方式和模塊加載時機上有所不一樣
一、定義模塊
  在 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

二、加載模塊

  在前面定義模塊時,咱們說過,當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推崇依賴就近
三、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方法咱們能夠加載該模塊,而且使用該模塊暴露出的接口

 

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,尚未進行介紹,若是你們感興趣,能夠查看相關的用法呢

相關文章
相關標籤/搜索