簡述JavaScript模塊化編程(二)

前置閱讀:簡述JavaScript模塊化(一)前端

在前面一文中,咱們對前端模塊化所經歷的三個階段進行了瞭解:web

  1. CommonJs,因爲是同步的,因此主要應用於服務器端,以Node.js爲表明。
  2. AMD,異步模塊定義,預加載,推薦依賴前置。以require.js爲表明。
  3. CMD,通用模塊加載,懶加載,推薦依賴就近。以Sea.js爲表明。

而在ES6已經大行其道的今天,ES6中所提供的模塊化的方法也天然而然成了咱們進行JavaScript模塊化編程的標準,所以ES6模塊的語法雖然在一些較爲老的瀏覽器上不能直出,須要進行轉譯,但卻表明着將來的JavaScript發展趨勢。面試

ES6模塊特性

在ES6中將模塊認爲是自動運行在嚴格模式下而且沒有辦法退出運行的JavaScript代碼。在一個模塊中定義的變量不會自動被添加到全局共享的做用域之中,這個變量只能做用在這個做用域中。此外模塊還必須導出一些外部文件能夠訪問的元素,以供其餘模塊或代碼使用。編程

除了這個基本特性,ES6模塊還有兩大特性也十分重要,須要額外注意:瀏覽器

  • 首先是在模塊的頂部this值是undefined,這是因爲在ES6中的模塊的代碼是在嚴格模式下執行的。(若是對this不是很熟悉的能夠去看個人這篇文章:深刻淺出this關鍵字
  • 其次,模塊不支持HTML風格的代碼註釋,這是早期瀏覽器所遺留下的JavaScript特性,在ES6的語法裏不予支持。

基本用法-模塊加載

首先咱們來看瀏覽器是如何加載模塊的。其實在ES6規範出來以前,web瀏覽器就規定了三種方式來引入JavaScript文件:服務器

  • 在沒有src屬性的<script>元素中直接內嵌JavaScript代碼
  • 在<script>元素中經過src屬性指定一個地址來加載JavaScript代碼文件
  • 經過Web Worker或Service Worker的方法加載並執行JavaScript代碼

而在瀏覽器中,默認的行爲就是將JavaScript做爲腳原本進行加載,而非模塊。因此咱們要告訴瀏覽器咱們加載的是模塊,方法就是在<script>元素中,將type屬性指定爲"module"。具體看下面的示例:異步

1 // 第一種方式
2 <script type=""module>
3     import { add } from "./example";
4     let num = add(1, 1);
5 </script>
6 //  第二種方式
7 <script type="module" src="example.js">
8 // 第三種方式,以腳本的方式加載example.js
9 let worker = new Worker("example.js");

當HTML解析器遇到<script>元素的type="module"的時候,模塊文件就開始下載,直到文件被徹底解析完成纔會去執行模塊內的代碼。模塊文件是按照他們出如今HTML文件中順序執行的,也就是說不管用何種方式引入模塊,第一個<script type=""module>老是在第二個<script type=""module>以前執行。模塊化

基本用法-導出

在ES6中咱們可使用export關鍵字將一部分代碼暴露給其餘模塊,以供其餘模塊或代碼使用。先讓咱們來看看export關鍵字在MDN的定義吧:函數

export語句用於在建立JavaScript模塊時,從模塊中導出函數、對象或原始值,以便其餘程序能夠經過 import 語句使用它們。(
此特性目前僅在 Safari 和 Chrome 原生實現。它在許多轉換器中實現,如Traceur Compiler,Babel或Rollup。)優化

經過MDN的定義咱們能夠知道:export關鍵字能夠將其放在任何函數、對象或原始值前面,從而將它們從模塊中導出。示例以下:

 1 //   ./example.js
 2 // 導出變量
 3 export var a = 1;
 4 // 導出函數
 5 export function addA(value) {
 6     return value + a;
 7 }
 8 //導出類
 9 export class add1 {
10     constructor(value) {
11         this.value = value + a;
12     }
13 }
14 //這個函數就是這個模塊所私有的,在外部不能訪問它
15 function say1() {
16     console.log('我是否是很帥');
17 }
18 //這又是個函數
19 function say2() {
20     console.log('沒錯我就是很帥');
21 }
22 //在後面對函數進行導出,它就不是私有的了
23 export say2;

須要注意的是:使用export導出的函數和類都須要一個名稱,除非使用default關鍵字,不然就不能用這個方法導出匿名函數或類。因此當咱們須要導出匿名的函數或者類時,咱們能夠這麼作:

 1 //   ./example.js
 2 //導出匿名函數
 3 export default function(a, b) {
 4     return a + b;
 5 }
 6 //或者導出匿名的類
 7 export default class {
 8 consturctor(value) {
 9     this.value = value + 1;
10     }
11 }

具體關於default關鍵字的用法我會在後面作具體介紹,如今只需記住:當咱們須要導出匿名的函數或者類時要使用export default語法。

基本語法-導入

在ES6中,從模塊中導入的功能能夠經過import關鍵字。import語句由兩部分組成:要導入元素的標識符和元素應當從哪一個模塊導入。

1 //  ./say.js
2 import { say2 } from "./example.js";
3 console.log(say2()); // '沒錯我就是很帥'

import 後面的大括號中的say2表示從規定模塊導入的元素的名稱。關鍵字from後面的字符串則表示要導入的模塊的路徑,這一般是包含模塊的.js文件的相對或絕對路徑名,須要注意的是隻容許使用單引號和雙引號的字符串來包裹路徑,瀏覽器使用的路徑格式與傳給<script>元素的相同,因此必須把文件的擴展名也加上。

(注:因爲Node.js遵循基於文件系統前綴以區分本地文件個包的慣例,即example是一個包,而./exampple.js是一個本地文件。爲了更好的兼容多個瀏覽器Node.js環境,咱們必定要在路徑前包含./或../來表示要導入的文件。)

除此以外,咱們還能夠導入多個元素或者直接導入整個模塊:

 1 // 導入多個元素
 2 improt { a, addA, say2 } from "./example.js";
 3 console.log(a); // 1
 4 sonsole.log(addA(1); // 2
 5 
 6 // 導入整個模塊
 7 import * as example from "./example.js"
 8 console.log(example.a); // 1
 9 sonsole.log(example.addA(1); // 2
10 console.log(example.say2()); // '沒錯我就是很帥'

上面的導入整個模塊就是把example.js中導出的全部元素所有加載到一個叫作example的對象中,而所導出的元素就會做爲example的屬性被訪問。由於example對象是做爲example.js中所導出成員的命名空間對象而被建立的,因此這種導入方式被稱爲命名空間導入(name space import)。

還有一點要注意的是,無論import語句把一個模塊寫了多少次,該模塊只執行一次。意思就是,在首次執行導入模塊後,實例化的模塊就會被保存在內存中,只要使用import語句引用它就能夠重複使用它:

1 // 首次導入須要加載模塊example.js
2 import { a } from "./example.js"
3 // 下面的兩個import將無需加載example.js了
4 import { addA } from "./example.js"
5 import { say2 } from "./example.js"

當從模塊中導入一個元素時,它與const是同樣沒法定義另外一個同名變量和導入一個同名元素,也沒法在import語句前使用元素或者改變導出的元素的值:

1 //接上面的代碼
2 
3 say2 = 1 ;  //會拋出一個錯誤

這是因爲ES6的import語句爲導入的元素建立的是隻讀綁定的標識符,而不是原始綁定。所以元素只有在被導出的模塊中才能夠被修改,即便是將該模塊的所有導入也沒法修改其中的元素。

 1 //   ./example.js
 2 // 這是一個函數
 3 export function setA(newA) {
 4     a = newA;
 5 }
 6 //  ./say.js
 7 import { a, setA } from "./example";
 8 console.log(a);  // 1
 9 a = 2;   //拋出錯誤
10 
11 // 因此咱們得這麼作
12 setA(2); 
13 console.log(a);  // 2

調用setA(2)時會返回到example.js中去執行,將a設置爲2。因爲say.js導入的只是a的只讀綁定的標識符而已,所以會自動進行更改。

其餘基本語法

1.語法限制

export和import在語法上還有一個重要的限制,那就是他們必須在條件語句和函數以外使用,例如:

1 if (ture) {
2     export var a = 1;      //語法錯誤
3 }
4 function imp() {
5     import a from "./example.js"; //語法錯誤
6 }

因爲模塊語法存在的其中一個緣由是讓JavaScript引擎能夠靜態地肯定哪些代碼是能夠導出的,所以export和import語句被設計成靜態的,不能進行任何形式的動態導出或導入。

2.重命名解決

有時在開發中,咱們在導入一些元素後不想使用它們的原始名稱了,咱們就能夠在導出過程或者導入過程當中去改變導出元素的名稱:

 1 // 導出過程
 2 function add(a, b) {
 3     return a + b;
 4 }
 5 export { add as add1 };  //在導入過程當中必須使用add1做爲名稱 
 6 
 7 // 導入過程
 8 import {add as add1 } from "./example"
 9 console.log(add1(1,1));  // 2
10 console.log(typeof add); //undefined

3.模塊的默認值

在CommonJS等其餘的模塊化規範中,從模塊中導出或導入默認值是一個常見的用法,所以在ES6中也延用了這種用法並進行了優化。在ES6中咱們可使用default關鍵字來指定默認值,而且一個模塊只能默認一個導出值:

 1 // ./example.js
 2 // 第一種默認導出語法
 3 export default function(a, b) {
 4     return a + b;
 5 }
 6 // 第二種默認導出語法
 7 function add(a, b) {
 8     return a + b;
 9 }
10 export default add;
11 // 第三種默認導出語法
12 function add(a, b) {
13     return a + b;
14 }
15 export { add as default };

須要注意的是第三種語法,default關鍵字雖然不能做爲元素的名稱,但能夠做爲元素的屬性名稱,所以可使用as語法將add函數的屬性設置爲default。

導入默認值的語法則是這樣的:

1 //  第一種語法
2 import add from "./example";
3 //  第二種語法
4 import { default as add } from "./example";

看到這裏有些朋友可能會發現,咱們的第一種語法中import關鍵字後面並無加大括號,認爲這是錯誤的。其實這是導入默認值的獨特語法,在這的本地名稱add用於表示模塊導出的任何默認函數,這種語法是最純淨的,ES6標準建立團隊的大佬們也但願這種語法能成爲web主流的模塊導入形式。

咱們前面說的導入匿名函數也一樣使用這種語法:

1 //   ./example.js
2 //導出匿名函數
3 export default function(a, b) {
4     return a + b;
5 }
6 // ./say.js
7 import add from "./example";
8 console.log(add(1,1));  // 2

在這裏本地名稱add就是用於表示上面的匿名函數的。

4.導出已導入的元素

咱們一樣能夠在本模塊內導出咱們在本模塊內導入的元素,有如下幾種語法:

 1 //  第一種語法
 2 import { add } from ./example.js;
 3 export { add };
 4 
 5 //  第二種語法
 6 export { add } from ./example.js;
 7 
 8 //換一個名稱導出
 9 export { add as add1 } from ./example.js; //以add這個名稱導入,再以add1的名稱導出
10 
11 // 導出整個模塊
12 export *  from ./example.js;

// 最後預祝本身8月份面試成功offer滿天飛-_-||

相關文章
相關標籤/搜索