天天看點

javascript中的子產品系統簡介CommonJS和NodejsAMD異步子產品加載CMDES modules和現代浏覽器在HTML中使用module和要注意的問題

簡介

在很久以前,js隻是簡單的作為浏覽器的互動操作而存在,一般都是非常短小的腳本,是以都是獨立存在的。

但是随着現代浏覽器的發展,特别是nodejs的出現,js可以做的事情變得越來越多也越來越複雜。于是我們就需要子產品系統來組織不同用途的腳本,進行邏輯的區分和引用。

今天将會給大家介紹一下js中的子產品系統。

CommonJS和Nodejs

CommonJS是由Mozilla公司在2009年1月份提出來了。沒錯,就是那個firfox的公司。

最初的名字叫做ServerJS,在2009年8月的時候為了表示這個标準的通用性,改名為CommonJS。

CommonJS最主要的應用就是服務端的nodejs了。浏覽器端是不直接支援CommonJS的,如果要在浏覽器端使用,則需要進行轉換。

CommonJS使用require()來引入子產品,使用module.exports來導出子產品。

我們看一個CommonJS的例子:

require("module"); 
require("../file.js"); 
exports.doStuff = function() {}; 
module.exports = someValue;           

注意,CommonJS是同步加載的。

AMD異步子產品加載

AMD的全稱是Asynchronous Module Definition 。它提供了一個異步加載子產品的模式。

AMD是RequireJS在推廣過程中對子產品定義的規範化産出。

異步加載的好處就是可以在需要使用子產品的時候再進行加載,進而減少了一次性全部加載的時間,尤其是在浏覽器端,可以提升使用者的體驗。

看下AMD加載子產品的定義:

define(id?, dependencies?, factory);           

AMD是通過define來定義和加載依賴子產品的。

其中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中導出了beta子產品的verb方法。

define中id和dependencies都不是必須的:

//無id
  define(["alpha"], function (alpha) {
       return {
         verb: function(){
           return alpha.verb() + 2;
         }
       };
   });

//無依賴
   define({
     add: function(x, y){
       return x + y;
     }
   });           

甚至我們可以在AMD中使用CommonJS:

define(function (require, exports, module) {
     var a = require('a'),
         b = require('b');

     exports.action = function () {};
   });           

定義完之後,AMD使用require來加載子產品:

require([dependencies], function(){});           

第一個參數是依賴子產品,第二個參數是回調函數,會在前面的依賴子產品都加載完畢之後進行調用。加載的子產品會以參數形式傳入該函數,進而在回調函數内部就可以使用這些子產品。

require(["module", "../file"], function(module, file) { /* ... */ });            

require加載子產品是異步加載的,但是後面的回調函數隻會在所有的子產品都加載完畢之後才運作。

CMD

CMD是SeaJS在推廣過程中對子產品定義的規範化産出。它的全稱是Common Module Definition。

CMD也是使用define來定義子產品的,CMD推崇一個檔案作為一個子產品:

define(id?, deps?, factory)           

看起來和AMD的define很類似,都有id,依賴子產品和factory。

這裡的factory是一個函數,帶有三個參數,function(require, exports, module)

我們可以在factory中通過require來加載需要使用的子產品,通過exports來導出對外暴露的子產品,module表示的是目前子產品。

// 定義子產品  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 加載子產品
seajs.use(['myModule.js'], function(my){

});           

是以總結下AMD和CMD的差別就是,AMD前置要加載的依賴子產品,在定義子產品的時候就要聲明其依賴的子產品。

而CMD加載完某個依賴子產品後并不執行,隻是下載下傳而已,隻有在用到的時候才使用require進行執行。

ES modules和現代浏覽器

ES6和現代浏覽器對子產品化的支援是通過import和export來實作的。

首先看下import和export在浏覽器中支援的情況:

javascript中的子產品系統簡介CommonJS和NodejsAMD異步子產品加載CMDES modules和現代浏覽器在HTML中使用module和要注意的問題
javascript中的子產品系統簡介CommonJS和NodejsAMD異步子產品加載CMDES modules和現代浏覽器在HTML中使用module和要注意的問題

首先我們看下怎麼使用export導出要暴露的變量或者方法:

export const name = 'square';

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}           

基本上,我們可以使用export導出var, let, const變量或者function甚至class。前提是這些變量或者函數處于top-level。

更簡單的辦法就是将所有要export的放在一行表示:

export { name, draw, reportArea, reportPerimeter };           

export實際上有兩種方式,named和default。上面的例子中的export是named格式,因為都有自己的名字。

下面看下怎麼使用export導出預設的值:

// export feature declared earlier as default
export { myFunction as default };

// export individual features as default
export default function () { ... } 
export default class { .. }           

named可以導出多個對象,而default隻可以導出一個對象。

導出之後,我們就可以使用import來導入了:

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';           

如果導出的時候選擇的是default,那麼我們在import的時候可以使用任何名字:

// file test.js
let k; export default k = 12;

// some other file
import m from './test'; // 因為導出的是default,是以這裡我們可以使用import m來引入
console.log(m);        // will log 12           

我們可以在一個module中使用import和export從不同的子產品中導入,然後在同一個子產品中導出,這樣第三方程式隻需要導入這一個子產品即可。

export { default as function1,
         function2 } from 'bar.js';           

上面的export from 等價于:

import { default as function1,
         function2 } from 'bar.js';
export { function1, function2 };           

上面的例子中,我們需要分别import function1 function2才能夠使用,實際上,我們可以使用下面的方式将所有的import作為Module對象的屬性:

import * as Module from './modules/module.js';

Module.function1()
Module.function2()           

然後function1,function2就變成了Module的屬性,直接使用即可。

在HTML中使用module和要注意的問題

怎麼在HTML中引入module呢?我們有兩種方式,第一種是使用src選項:

<script type="module" src="main.js"></script>           

第二種直接把module的内容放到script标簽中。

<script type="module">
  /* JavaScript module code here */
</script>           

注意,兩種script标簽的類型都是module。

在使用script來加載module的時候,預設就是defer的,是以不需要顯示加上defer屬性。

如果你在測試的時候使用file:// 來加載本地檔案的話,因為JS子產品安全性的要求,很有可能得到一個CORS錯誤。

最後,import() 還可以作為函數使用,來動态加載子產品:

squareBtn.addEventListener('click', () => {
  import('./modules/square.js').then((Module) => {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});           

本文作者:flydean程式那些事

本文連結:

http://www.flydean.com/js-modules/

本文來源:flydean的部落格

歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

繼續閱讀