iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Modern Web

就是要搞懂 JavaScript 啦!系列 第 15

Day15 模組:神奇而神祕的工具箱

  • 分享至 

  • xImage
  •  

上一篇我們已經知道,只要有函式就有閉包,而其中一種將閉包運用得淋漓盡致的方法,就是所謂的模組(Modules)。

神奇的工具箱

模組就像一個設計好的工具箱,比方說料理工具箱、雕刻工具箱、水電工具箱,一個模組對應一整套功能,需要某樣工具時,就從對應的工具箱裡拿出來使用。

對外界來說,工具箱的內部是隱藏的,但如果知道打開的方法,也知道該如何操作這些工具,工具箱就能為你打造各種各樣的東西。

如果沒有做到「封閉」這點,讓工具箱內的東西被拿去隨意使用.......你可以想像小孩子拿精密的測量儀亂摔亂打,更糟糕的是,拿金屬起子去戳插座,導致完全無法收拾的後果。

而函數擁有的「閉包(Closure)」,就是在提供取用方法的前提下,避免汙染全域的問題。閉包封裝了函式內部的功能,將方法和變數限制在一個範圍內存取與使用。

讓我們來看看下面的程式碼:

function CoolModule() {
	var something = "cool";
	var another = [1, 2, 3];

	function doSomething() {
		console.log( something );
	}

	function doAnother() {
		console.log( another.join( " ! " ) );
	}

	return {
		doSomething: doSomething,
		doAnother: doAnother
	};
}

var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

以上程式碼中,CoolModule 最後返回了一個物件,它具有以下幾個特性:

  • 擁有指向內部函式 doSomethingdoAnother 的引用
  • 沒有內部變數的引用,內部變數依然是函式本身私有、無法被外部存取的。

像上面這樣一個物件,即是一個模組的公用 API。

API(Application Programming Interface)這個概念是和模組一同出現的,它在中文被翻譯成「應用程式介面」或「應用程序接口」,通常是指模組(或某個系統)暴露給外部取用的部分。

在使用造模組的過程中,有以下幾點要注意:

  • 函式必須被調用才能創建作用域,並產生閉包,以製作出模組的實例(instance)。
  • 模組不一定要返回一個物件,也可以是一個函式,而函式本身也是廣義的物件,所以能夠具備屬性(Properties)。

達成模組的條件

  • 至少有一個外圍函式包圍被隱藏/封裝的內容,且這個函式至少被調用過一次(每次都創建一個新的模組實例)。
  • 這個外圍函式至少返回一個內部函式,並且這個內部函式的使用了閉包特性,得以訪問或修改外部函式私有的作用域。

模組的應用

模組另一個強大之處,就在於可以從外部改變內部函式的變數值,而這也是 API 具備的功能之一。

API 向外界開放了函式/物件內部的參考,因此能夠改變這個模組,包括添加或刪除方法(method)、屬性(Property),或改變它們的值。

var foo = (function CoolModule(id) {
	function change() {
		// 修改公有 API
		publicAPI.identify = identify2;
	}

	function identify1() {
		console.log( id );
	}

	function identify2() {
		console.log( id.toUpperCase() );
	}

	var publicAPI = {
		change: change,
		identify: identify1
	};

	return publicAPI;
})( "foo module" );

foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

如果只需要一個模組實例,可以使用 IIFE:

var foo = (function CoolModule() {
	var something = "cool";
	var another = [1, 2, 3];

	function doSomething() {
		console.log( something );
	}

	function doAnother() {
		console.log( another.join( " ! " ) );
	}

	return {
		doSomething: doSomething,
		doAnother: doAnother
	};
})();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

模組模式Module Pattern)

模組模式(Module Pattern)是當前常見的一種程式設計模式,利用閉包的特性,將方法和變數限制在一個範圍內存取和使用。

const counter = (function () {
  // private variable
  let i = 0;

  // private function
  const sayTheNumber = function () {
    console.log(`Now it's ${i}`)
  };

  // public function
  return {
    get: function () {
      return i;
    },
    set: function (val) {
      i = val;
      sayTheNumber();
    },
    increment: function () {
      return ++i;
    },
    decrement: function () {
      return --i;
    }
  };
})();

console.log(counter.get()); // 0
console.log(counter.decrement()); // -1
counter.set(3); // Now it's 3
console.log(counter.increment()); // 4
console.log(counter.increment()); // 5

引用模組

ES6 以後,JS 便開始將一個文件視為一個獨立的模組,每個模組(文件)能夠導入其它模組或特定的 API,也可以導出它自己公有的 API。

由於模組的 API 也能在執行時期被改變,所以基於函式的模組模式無法進行靜態分析(編輯器無法知道模組的內容)。

但 ES6 的模組 API 是靜態的,不會在運行時改變,它在編譯時期就會檢查被導入的模組 API 成員是否存在,如果該 API 引用不存在,編譯器就會提早拋出一個錯誤,而不是等到執行時期才發現。

ES6 的每個模組必須定義在一個單獨的文件中,瀏覽器或 JS 引擎擁有自己的「模組加載器」,在模組被導入時會同步加載該模組文件。

在一個「模組文件」內部的內容就像包裹在函式內的作用域,這些被導出的 API 同樣具有閉包特性,就像使用函式的閉包一樣。

以下是範例:

【bar.js】

function hello(who) {
	return "Let me introduce: " + who;
}

export hello;

【foo.js】

// 導入 bar 模組中的 "hello()"
import hello from "bar";

var hungry = "hippo";

function awesome() {
	console.log(
		hello( hungry ).toUpperCase()
	);
}
export awesome;

【index.js】

// 導入 bar 和 foo 整個模組
import foo from "foo";
import bar from "bar";

console.log(
	bar.hello( "rhino" )
);
// Let me introduce: rhino

foo.awesome();
// LET ME INTRODUCE: HIPPO

參考資料


上一篇
Day14 閉包:服務生!麻煩整個打包帶走
下一篇
Day16 this:你說的這個到底是哪個?
系列文
就是要搞懂 JavaScript 啦!73
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言