天天看點

CommonJS、AMD規範的requirejs、es6對比

檔案路徑

首先先搞清楚檔案路徑的寫法,這裡我總是記不住,有點暈,正好這次整理一下。

  • /

    為起始,表示從根目錄開始解析;
  • ./

    為起始,表示從目前目錄開始解析;
  • ../

    為起始,表示從上級目錄開始解析;

CommonJS

CommonJS是nodejs也就是伺服器端廣泛使用的子產品化機制。

該規範的主要内容是,子產品必須通過module.exports 導出對外的變量或接口,通過 require() 來導入其他子產品的輸出到目前子產品作用域中。

根據這個規範,每個檔案就是一個子產品,有自己的作用域,檔案中的變量、函數、類等都是對其他檔案不可見的。

如果想在多個檔案分享變量,必須定義為global對象的屬性。(不推薦)

定義子產品

在每個子產品内部,module變量代表目前子產品。它的exports屬性是對外的接口,将子產品的接口暴露出去。其他檔案加載該子產品,實際上就是讀取module.exports變量。

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

加載子產品

require方法用于加載子產品,字尾名預設為.js

var app = require('./app.js');           

子產品加載的順序,按照其在代碼中出現的順序

根據參數的不同格式,require指令去不同路徑尋找子產品檔案。

  • 如果參數字元串以“/”開頭,則表示加載的是一個位于絕對路徑的子產品檔案。
  • 如果參數字元串以“./”開頭,則表示加載的是一個位于相對路徑的子產品檔案
  • 如果參數字元串不以“./“或”/“開頭,則表示加載的是一個預設提供的核心子產品(node核心子產品,或者通過全局安裝或局部安裝在node_modules目錄中的子產品)

入口檔案

一般都會有一個主檔案(入口檔案),在index.html中加載這個入口檔案,然後在這個入口檔案中加載其他檔案。

可以通過在package.json中配置main字段來指定入口檔案。

子產品緩存

第一次加載某個子產品時,Node會緩存該子產品。以後再加載該子產品,就直接從緩存取出該子產品的module.exports屬性。

加載機制

CommonJS子產品的加載機制是,輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,子產品内部的變化就影響不到這個值。

AMD

AMD(異步子產品定義)是為浏覽器環境設計的,因為 CommonJS 子產品系統是同步加載的,目前浏覽器環境還沒有準備好同步加載子產品的條件。

requirejs即為遵循AMD規範的子產品化工具。

RequireJS的基本思想是,通過define方法,将代碼定義為子產品;通過require方法,實作代碼的子產品加載。

define方法用于定義子產品,RequireJS要求每個子產品放在一個單獨的檔案裡。

按照是否依賴其他子產品,可以分成兩種情況讨論。第一種情況是定義獨立子產品,即所定義的子產品不依賴其他子產品;第二種情況是定義非獨立子產品,即所定義的子產品依賴于其他子產品。

獨立子產品

define(function(){
    ……
    return {
        //傳回接口
    }
})           

define定義的子產品可以傳回任何值,不限于對象。

非獨立子產品

define(['module1','module2'],function(m1,m2){
    ……
    return {
        //傳回接口
    }
})           

要定義的子產品依賴于module1和module2,那麼第一個參數就是依賴的子產品的數組。

第二個參數是一個函數,僅當依賴的子產品都加載成功後才會被調用。此函數的參數m1,m2與前面數組中的依賴子產品一一對應。

此子產品必須傳回一個對象,供其他子產品調用。

同樣使用require()方法來加載子產品,但由于是異步的,是以使用回調函數的形式。

require(['foo','bar'],function(foo,bar){
    ……
})           

上面方法表示加載foo和bar兩個子產品,當這兩個子產品都加載成功後,執行一個回調函數。該回調函數就用來完成具體的任務。

require方法也可以用在define方法内部。

define(function(require){
     var otherModule = require('otherModule');
})
           

require方法允許添加第三個參數,即錯誤處理的回調函數。

require(
    [ "backbone" ], 
    function ( Backbone ) {
        return Backbone.View.extend({ /* ... */ });
    }, 
    function (err) {
        // ...
    }
);           
  • 7
  • 8
  • 9

配置

require方法本身也是一個對象,它帶有一個config方法,用來配置require.js運作參數。

require.config({
    paths: {
        jquery:'lib/jquery'
    }
});           
  • paths:paths參數指定各個子產品的位置。這個位置可以是同一個伺服器上的相對位置,也可以是外部網址。可以為每個子產品定義多個位置,如果第一個位置加載失敗,則加載第二個位置。上面就是指定了jquery的位置,那麼就可以直接在檔案中

    require(['jquery'],function($){})

  • shim:有些庫不是AMD相容的,這時就需要指定shim屬性的值。shim可以了解成“墊片”,用來幫助require.js**加載非AMD規範的庫**。
require.config({
    paths: {
        "backbone": "vendor/backbone",
        "underscore": "vendor/underscore"
    },
    shim: {
        "backbone": {
            deps: [ "underscore" ],
            exports: "Backbone"
        },
        "underscore": {
            exports: "_"
        }
    }
});           
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用

在首頁面index.html中先通過script标簽引入require.min.js。

再通過script标簽引入一個入口檔案main.js,此入口檔案一般用于配置(require.config),以及引入其他子產品。

CommonJS與AMD

  • CommonJS規範加載子產品是同步的,也就是說,隻有加載完成,才能執行後面的操作。
  • AMD規範則是異步加載子產品,允許指定回調函數,在回調函數中執行操作。

由于Node.js主要用于伺服器程式設計,子產品檔案一般都已經存在于本地硬碟,是以加載起來比較快,不用考慮非同步加載的方式,是以CommonJS規範比較适用。但是,如果是浏覽器環境,要從伺服器端加載子產品,這時就必須采用非同步模式,是以浏覽器端一般采用AMD規範。

AMD規範允許輸出的子產品相容CommonJS規範,這時define方法需要寫成下面這樣:

define(function(require,exports,module){
    var someModule = require("someModule");
    var anotherModule = require("anotherModule");
    ……
    exports.asplode = function(){

    }
})           

ES6 Modules

ES6正式提出了内置的子產品化文法,我們在浏覽器端無需額外引入requirejs來進行子產品化。

ES6中的子產品有以下特點:

  • 子產品自動運作在嚴格模式下
  • 在子產品的頂級作用域建立的變量,不會被自動添加到共享的全局作用域,它們隻會在子產品頂級作用域的内部存在;
  • 子產品頂級作用域的 this 值為 undefined
  • 對于需要讓子產品外部代碼通路的内容,子產品必須導出它們

使用export關鍵字将任意變量、函數或者類公開給其他子產品。

//導出變量
export var color = "red";
export let name = "cz";
export const age = 25;

//導出函數
export function add(num1,num2){
    return num1+num2;
}

//導出類
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}

function multiply(num1, num2) {
    return num1 * num2;
}

//導出對象,即導出引用
export {multiply}           
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

重命名子產品

重命名想導出的變量、函數或類的名稱

function sum(num1, num2) {
    return num1 + num2;
}

export {sum as add}           

這裡将本地的sum函數重命名為add導出,是以在使用此子產品的時候必須使用add這個名稱。

導出預設值

子產品的預設值是使用 default 關鍵字所指定的單個變量、函數或類,而你在每個子產品中隻能設定一個預設導出。

export default function(num1, num2) {
    return num1 + num2;
}           

此子產品将一個函數作為預設值進行了導出, default 關鍵字标明了這是一個預設導出。此函數并不需要有名稱,因為它就代表這個子產品自身。對比最前面使用export導出的函數,并不是匿名函數而是必須有一個名稱用于加載子產品的時候使用,但是預設導出則無需一個名字,因為子產品名就代表了這個導出值。

也可以使用重命名文法來導出預設值。

function sum(num1, num2) {
    return num1 + num2;
}

export { sum as default };           

在子產品中使用import關鍵字來導入其他子產品。

import 語句有兩個部分,一是需要導入的辨別符,二是需導入的辨別符的來源子產品。此處是導入語句的基本形式:

import { identifier1,identifier2 } from "./example.js"           
  • 大括号中指定了從給定子產品導入的辨別符
  • from指明了需要導入的子產品。子產品由一個表示子產品路徑的字元串來指定。

當從子產品導入了一個綁定時,你不能在目前檔案中再定義另一個同名變量(包括導入另一個同名綁定),也不能在對應的 import 語句之前使用此辨別符,更不能修改它的值。

導入單個綁定

如果一個子產品隻導出了一個函數(或變量或類),或者導出了多個接口但是隻選擇導入其中的一個,那麼就可以寫成下面單個導入的模式:

import {sum} from './example.js'           

導入多個綁定

從一個子產品中導入多個綁定:

import {sum,multiply} from './example.js'           

完全導入一個子產品

還有一種情況,就是将整個子產品當做單一對象導入,該子產品的所有導出都會作為對象的屬性存在:

import * as example from './example.js'
example.sum(1,2);
example.multiply(2,3);           

在此代碼中, example.js 中所有導出的綁定都被加載到一個名為 example 的對象中,具名導出( sum() 函數、 multiple() 函數)都成為 example 的可用屬性。

這種導入格式被稱為命名空間導入,這是因為該 example 對象并不存在于 example.js 檔案中,而是作為一個命名空間對象被建立使用,其中包含了 example.js 的所有導出成員。

然而要記住,無論你對同一個子產品使用了多少次 import 語句,該子產品都隻會被執行一次。

在導出子產品的代碼執行之後,已被執行個體化的子產品就被保留在記憶體中,并随時都能被其他 import 所引用.

import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";           

盡管此處的子產品使用了三個 import 語句,但 example.js 隻會被執行一次。若同一個應用中的其他子產品打算從 example.js 導入綁定,則那些子產品都會使用這段代碼中所用的同一個子產品執行個體。

重命名導入

與導出相同,我們同樣可以重命名導入的綁定:

import { sum as add} from './example.js'           

導入預設值

如果一個子產品導出了預設值,那麼可以這樣導入預設值:

import sum from "./example.js";           

這個導入語句從 example.js 子產品導入了其預設值。注意此處并未使用花括号,與之前在非預設的導入中看到的不同。本地名稱 sum 被用于代表目标子產品所預設導出的函數,是以無需使用花括号。

如果一個子產品既導出了預設值、又導出了一個或更多非預設的綁定的子產品:

export let color = "red";

export default function(num1, num2) {
    return num1 + num2;
}           

可以像下面這樣使用一條import語句來導入它的所有導出綁定:

import sum,{color} from "./example.js"           

逗号将預設的本地名稱與非預設的名稱分隔開,後者仍舊被花括号所包裹。

要記住在 import 語句中預設名稱必須位于非預設名稱之前。

導入的再導出

有時想在目前的子產品中将已導入的内容再導出去,可以像下面這樣寫:

import {sum} from './example.js'
……
export {sum}           

但是有一種更簡潔的方法:

export {sum} from './example.js'           
export { sum as add } from "./example.js";           
export * from "./example.js";           

限制