JavaScript模塊化-AMD

最近寫一個小遊戲的時候用的是RequireJs構建項目,順便補了一下RequireJs,下面講解一些基礎和進階的用法。html

image-20200702234124624

AMD

AMDAsync Module Definition表明的意思爲異步模塊定義,是Javascript模塊化的瀏覽器解決方案,它採用異步的方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在回調函數中,等到加載完成以後,這個回調函數纔會運行。jquery

image-20200702233814576

AMD規範定義了一個函數define,經過define方法定義模塊:git

define(id?, dependencies?, factory);

而且採用require()語句加載模塊:github

require([module], callback);

引入的模塊和回調函數不是同步的,因此瀏覽器不會由於引入的模塊加載不成功而假死。api

RequireJS

RequireJS是一個基於AMD規範實現的JavaScript文件和模塊加載器。它針對瀏覽器內使用進行了優化,而且對其餘JS環境(Rhino和Node)作了兼容。使用RequireJS這樣的模塊化腳本加載器能夠提升代碼的速度和質量。瀏覽器

  • 異步加載: 使用 RequireJS,會在相關的 js 加載後執行回調函數,這個過程是異步的,因此它不會阻塞頁面。
  • 按需加載: 經過 RequireJS,你能夠在須要加載 js 邏輯的時候再加載對應的 js 模塊,不須要的模塊就不加載,這樣避免了在初始化網頁的時候發生大量的請求和數據傳輸。

基本使用

根據官方文檔和項目實例,接下來講一下ReuqireJS的基本使用:app

Reuqire Download

下載最新版的RequireJS異步

Project Structure

下面是官方示例的RequireJS項目結構,對內容作了小小的改動,www做爲項目的根目錄,lib中存放項目依賴即須要的一些JS庫,app.js爲主入口文件,app中存放本身寫的模塊文件。模塊化

image-20200702194455250

Project Code

1. index.html函數

index.html中定義了一個script標籤來引入require.js,其中data-main屬性是一個自定義屬性,這個屬性指定在加載完 reuqire.js 後,就將屬性指定路徑下的JS文件並運行,這個文件即入口文件,這裏的app.jsjs後綴被省略掉了。

<!DOCTYPE html>
<html>
    <head>
        <script data-main="app" src="lib/require.js"></script>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>
若是 <script/>~~~~ 標籤引入 require.js 時沒有指定 data-main 屬性,則以引入該 jshtml 文件所在的路徑爲根路徑,若是有指定 data-main 屬性,也就是有指定入口文件,則以入口文件所在的路徑爲根路徑。

2. app.js

Main.js加載主模塊而且配置項目依賴,要改變 RequireJS的默認配置,可使用require.config函數傳入一個可選參數對象。下面是一些可使用的配置:

// For any third party dependencies, like jQuery, place them in the lib folder.

// Configure loading modules from the lib directory,
// except for 'app' ones, which are in a sibling
// directory.
requirejs.config({
  // 模塊加載的根路徑。
  baseUrl: ".",
     // 用於一些經常使用庫文件或者文件夾路徑映射,js後綴能夠省略
  paths: {
    app: "app/",
    fmt: "lib/fmt",
  },
});

// Start loading the main app file. Put all of
// your application logic in there.
requirejs(["app/main"]);
若是在 require.config() 中有配置 baseUrl,則以 baseUrl 的路徑爲根路徑,這條規則會覆蓋上面 data-main的效果。

3. app/

Main.js中咱們經過require函數加載了一個message模塊,該模塊用於打印一些定義好的字符串。

define(function (require) {
  var msg = require("./message");
  msg.helloWorld();
});

Main.js中使用的模塊定義在message.js中,他引入了一個輸出依賴fmt

define(["fmt"], function (fmt) {
  return {
    helloWorld: function () {
      fmt.println("hello word");
    },
  };
});
這兩種依賴的加載方式又和不一樣稍後介紹。

4. lib/

Lib/fmt.js中我定義一個 js 模塊模擬gofmt包,經過return對外暴露出接口。注意,暴露的對象就是引入的對象。

define(function () {
  var print = function (msg) {
    console.log(msg);
  };
  var println = function (msg) {
    console.log(msg + "\n");
  };

  return {
    moduleName: "fmt",
    print: print,
    println: println,
  };
});

require.config 函數配置

要改變RequireJS的默認配置,可使用require.config函數傳入一個可選參數對象,其實這個對象能夠配置五個屬性:

require.config({
    baseUrl: '.',
      paths: {
        app: "app/",
        fmt: "lib/fmt",
      },
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        }
    },
    config: {
        'app/main': {
            ENV: 'development'
        }
    },
     map: {
        'script/newmodule': {
            'foo': 'foo1.2'
        },
        'script/oldmodule': {
            'foo': 'foo1.0'
        }
    },
});

1. baseUrl

baseUrl做爲加載模塊的根路徑。在配置這個屬性後,以後全部的路徑定義都是基於這個根路徑的(包括配置和依賴引入中)。

2. path

用於一些經常使用庫文件或者文件夾路徑映射,定義以後能夠直接在依賴引入中使用。

3. shim

雖然目前已經有一部分流行的函數庫符合 AMD 規範,但還有不少庫並不符合。shim就是爲了加載這些非AMD規範的js,並解決其載入順序的,好比上面的backbone

4. config

config將配置信息傳給一個模塊。這些配置每每是application級別的信息,須要一個手段將它們向下傳遞給模塊。

config: {
  'app/main': {
    ENV: 'development'
  }
}

能夠經過加載特殊的依賴module來獲取這些信息。

define(['module'], function (module) {
  var ENV = module.config().ENV;  // development
  var msg = require("./message");
  msg.helloWorld();
});

5. map

對於給定的模塊前綴,使用一個不一樣的模塊 ID 來加載該模塊。該手段對於某些大型項目很重要。好比上面配置之後,不一樣的模塊會使用不一樣版本的foo

some/newmodule調用了require('foo'),它將獲取到foo1.2.js文件,當oldmodule調用 require('foo'),時它將獲取到 foo1.0.js 文件。

map還支持*,意思是「對於全部的模塊加載,使用本 map 配置」。若是還有更細化的 map 配置,會優先於*配置。

requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

模塊定義

1. 對象

若是一個模塊僅含鍵值對,沒有任何依賴,能夠直接在define中定義。

define({
    foo: "foo",
    bar: function(){}
});

2. 須要預處理的對象

define(function () {
    console.log("Do something...");
 
    return {
        foo: "foo",
        bar: function(){}
    }
});

3. 只有一個函數

沒有任何依賴的函數直接這麼定義:

define(function () {
    return function (){};
});

調用時直接打()調用:

require(['lib/foo'],function (foo) {
    foo();
});

4. 須要其餘的依賴:

define(['jquery'],function($){
    return function (){};
});

循環加載

假設咱們有以下 a、b 兩個互相依賴的模塊,咱們若是調用 b 模塊的 b 方法。

// app/a.js
define(['app/b'],function(b){
    return function() { b() }
});

// app/b.js
define(['app/a'],function(a){
    return function() { a() }
});

會發現 b 調用 a 正常,可是 a 中調用 b 方法會報 undefined 錯誤。

require(['app/b'],function (b) {
    b();    // b is not defined.
});

解決:

循環依賴比較罕見,對於循環依賴,只要依賴環中任何一條邊是運行時依賴,這個環理論上就是活的。而若是所有邊都是裝載時依賴,這個環就是死的。

對模塊 a 進行以下修改,即再也不依賴前置加載。而是經過引入 require 依賴,而後再經過 require() 方法去載入模塊 b,並在回調中去執行。

// app/a.js
define(['require'],function(require){
    var b = require('b')
    return function() {
      b()
    }
});
相關文章
相關標籤/搜索