深入理解ES6 013【學習筆記】

用模塊封裝代碼

javascript用“共享一切”的方法加載代碼,這是該語言中最容易出錯且另人感到困惑的地方。其他語言使用諸如包這樣的概念來定義代碼作用域。在es6以前,在應用程序的每一個js中定義的一切都共享一個全局作用域。隨著web應用程序變得更加複雜,js代碼的使用量也開始增長,這一做法會引起問題,如命名衝突和安全問題。es6的一個目標是解決作用域問題,也為了使用js應用程序顯得有序,於是引進了模塊

甚麼是模塊

模塊是自動運行在嚴格模式下並且沒有辦法退出運行的js代碼。與共享一切架構相反的是,在模塊頂部創建的變量不會自動被添加到全局共享作用域,這個變量僅在模塊的頂級作用域存在,而且模塊必須導出一些外部代碼可以訪問的元素,如變量或函數。模塊也可以從其他模塊導入綁定。

另外兩個模塊的特性與作用域關係不大,但也很重要。首先,在模塊的頂部,this的值是undefined;其次,模塊不支持html風格的代碼注釋,這是從早期瀏覽器殘餘下來的javascript的特性。

腳本,也就是任何不是模塊的javascript代碼,則缺少這些特性。模塊和其他javascript代碼之間的差異可能乍一看不起眼(好像一樣)。但是它們代表了javascript代碼加載和求值的一個重要變化。模塊真正魔力所在是僅導出和導入你需要的綁定,而不是將所有的東西都放到一個文件。只有很好的理解了導出和導入才能理解模塊與腳本的區別。

導出的基本語法

可以使用export關鍵字將一部分已發佈的代碼暴露給其他模塊,在最簡單的用例中,可以將export放在任何變量、函數或類聲明的前面,以將它們從模塊中導出。如:

// 導出數據
export var color = "red"
export let name = "Nicholas";
export const magicNumber = 7;

// 導出函數
export funciton sum(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

注意到,除中了export關鍵字外,每一個聲明與腳本中的一模一樣,因為導出的函數和類聲明需要一個名稱,所以代碼中的每一個函數或類也確實有這個名稱。除非用default關鍵字, 所以不能用export直接導入一個匿名函數或類,multiply 在定義它時沒有馬上導出它,由於不必總是導出聲明,可以導出引用。

任何未顯示導出的變量、函數或類都是模塊私有的,無法從模塊外部訪問的

導入語法

import { identifier1, identifier2 } from './example.js';

導入綁定的列表看起來與解構對象很相似,但它不是,從模塊中導入一個綁定時,它就好像使用了const定義的一樣。結果是你無法定義另一個同名變量(包括導入另一個同名綁定)也無法在import語句前使用標識符或改變綁定的值。

// 導入一個
import { sum } from './example.js'

console.log(sum(1,2)) //3
sum = 1 //拋出一個錯誤
// 不能給導入的綁定重新賦值

// 導入多個綁定
import { sum, multiply, magicNumber } from './example.js'
console.log(sum(1, magicNumber)); //8
console.log(multiply(1,2)); // 2

// 導入整個模塊作為一個單一的對象。然後所有導出都可以作為對象的屬性使用
import * as exapmle from './example.js'
console.log(exapmle.sum(1, magicNumber)); //8
console.log(exapmle.multiply(1,2)); // 2

import * as exapmle from './example.js' 這種引入格式被稱作命名空間導入(namespace import)。 因為example.js文件中不存在example對象,故而它作為example.js中所有導出成員的命名空間對象而被創建。但是,請注意,不管在import語句中把一個模塊寫了多少次,該模塊將只執行一次。,導入模塊的代碼執行後,實例化過的模塊被保存在內存中,只要另一個import語句引用它就可以重復使用它。

import { smu } from './example.js';
import { multiply } from './example.js';
import { magicNumber } from './example.js';

儘管模塊中有3個import語句,但example.js將只執行一次,如果同一個應用程序中的其它模塊也從example.js中導入綁定,那麼那些模塊與此代碼使用的是相同的模塊。

importexport的一個重要限制是,它們必須在其他語句和函數之外使用(if else funciton中)。

導入綁定的一個微妙怪異之外

es6的 import語句為變量、函數和類創建的是只讀綁定,而不是像正常變量一樣簡單地引用原始綁定。標籤符只有在被導出的模塊中可以修改,即便是導入綁定的模塊也無法更改綁定的值,如:

// example.js
export var name = "Nicholas";
export funtion setName(newName){
  name = newName
}

// 使用模塊
import { name, setName } from './example.js'

console.log(name); // Nicholas
setName("Greg"); 
// 會回到導出setName()的模塊中去執行,並將name設置為Greg
// 然後name的更改會體現在(自動)導入name模塊中,
// 導入的name是導出的name標識的本地名稱。
// 兩個name不是同一回事兒
console.log(name); // Greg

name = "hehe" // 拋出錯誤

導入和導出的 as 的使用

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

export { sum as add}
// sum 是以add名導出的,引入時只能引用add
import { add } from './sample.js';

// 導入也可以使用as給它重命個名
import { add as  sum } from './sample.js'
// 當前上下文中只有sum 沒有add

默認值 ,由於諸如commonjs(瀏覽器外的另一個js使用規範)的其它模塊系統中,從模塊中導出和導入默認值是一個常見的做法,該語法被進行優化。模塊的默認值指的是通過default關鍵字指定的單個變量、函數或類,只能為每個模塊設置一個默認的導出值,導出時多次使用default關鍵字是一個語法錯誤。default是一個關鍵字,不能出現在變量中

下面導出默認的3種方式

// 1
export default funciton(num1, num2) {
   return num1 + num2
}
// 2
function sum(num1, num2) {
   return num1 + num2
}
export default sum

// 3
function sum(num1, num2) {
   return num1 + num2
}
export {sum as default}

導入默認的使用

import sum from './sample.js'

console.log(sum(1,2)) //3

// 導入默念值及非默認值的混合
// example.js
export let color = 'red';
export default function(num1, num2) {
   return num1 + num2
}
// 使用時
import sum,{ color } from './sample.js'
// 默認導出的被重全名為sum,並且導入了color

重新導出已經導入的值

// 1
import { sum } from './example.js'
export { sum }
// 也可以通過一條語句來完成這個功能
export { sum } from './example.js'
// 或者給它起個別的名字導出
export { sum as add } from './example.js'

無綁定導入

某些模塊可能不導出任何東西,相反它們可能只修改全局作用域中的對象。儘管模塊中的頂層變量、函數和類不自動地出現在全局作用域中,但這並不意味著模塊無法方法全局作用域。內建對象(如Array和Object)的共享定義可以在模塊中訪問,對這些對象所做的更改將反映在其它模塊中。

如我們給Array添加一個pushAll方法,無綁定導入最有可能被應用於創建Polyfill和Shim

// sample.js
// 沒有導出也沒有導入
Array.prototype.pushAll = function(items){
  if(!Array.isArray(items)){
    throw new TypeError('參數必須是一個數組')
  }

  return this.push(...items)
}

// 使用
import './example.js' //沒導入任何,僅執行了一下
let colors = ["red", "green", "blue"];
let items = []
items.pushAll(colors)

在web應用中使用模塊

es6以前,web瀏覽器也有多種方式可以將javascript包含在web應用程序中,這些腳本加載的方式:

  • <script>元素中通過src屬性指定一個加載代碼的地址來加載javascript代碼文件
  • 將javascript代碼內嵌到沒有src屬性的<script>元素中
  • 通過web worker或service worker的方法加載並執行javascript代碼文件

為了完全支持模塊功能,web瀏覽器必須更新這些機制,具體說明總結如下:

<script>中使用模塊

非模塊加載時,當type屬性缺失或包含一個javascript內容類型"text/javascript"時是作為腳本加載,<script>元素可以執行內聯代碼工加載src指定的文件,當type屬性值為type="module"時支持加載模塊,這種模式下可以讓瀏覽器將所有內聯代碼或包含在src指定的文件中的代碼按照模塊而非腳本的方式加載。區分是模塊還是腳本就是看這個type="module"與否

<!-- 加載一個javascript 模塊文件 -->
<script type="module" src="module.js"></script>

<!-- 內聯引入一個模塊 -->
<script type="module">
  import { sum } from './example.js'
  let result = sum(1,2);
</script>

web瀏覽器中模塊的加載順序

模塊加載時,defer這個可選屬性是必須的(默認就是這種,不一定要寫在那),因為模塊和腳本不同它是獨一無二的,可以通過import關鍵字來指明其所依賴的其他文件,並且這些文件被加載進該模塊才能正確執行。

模塊是按照它們出現在html文件中的順序執行,也就是說,無論模塊中包含的是內聯代碼還是指定的src屬性,第一個<script type="module">總是在第二個之前執行,如下

<!-- 先執行這個 -->
<script type="module" src="module1.js"></script>

<!--第二執行這個 -->
<script type="module">
  import { sum } from './example.js'
  let result = sum(1,2);
</script>
<!-- 最後執行這個 -->
<script type="module" src="module2.js"></script>

因為每個模塊都可以從一個或多個其它的模塊導入,這會使問題複雜化。因此,首先解析模塊以識別所有導入語句,然後,每個導入語句都觸發一次獲取過程(從網絡或緩存),並且在所有導入資源被加載和執行後才會執行當前模塊。

<script type="module">顯示引入和用import隱式導入的所有模塊都是按需加載並執行的。 執行過程描述如下

  1. 下載並解析module1.js
  2. 遞歸下載並解析module1.js中導入的資源
  3. 解析內聯模塊
  4. 遞歸下載並解析內聯模塊中導入的資源
  5. 下載並解析module2.js
  6. 遞歸下載並解析module2.js中導入的資源

加載完成後,只有當文檔完全被解析之後才會執行其它操作,文檔解析後,會發生以下操作

  1. 遞歸執行module1.js中導入的資源
  2. 執行module1.js
  3. 遞歸執行內聯模塊中導入的資源
  4. 執行內聯模塊
  5. 遞歸執行module2.js中導入的資源
  6. 執行module2.js

web瀏覽器中的異步模塊加載

script中的asnyc屬性,當其應用於腳本時,腳本文件將在文件完全下載並解析後執行。但是,文檔中asnyc腳本的順序不會影響腳本的執行順序,腳本在下載完成後立即執行,而不必等待包含的文檔完成解析。這個屬性應用於module時,情況類似,唯一區別是,在模塊執行前,模塊中所有的導入資源都必須下載下來。這可以大確保只有當模塊執行所需的所有資源都下載完成後才執行模塊,但不能保證模塊的執行時機。

<!-- 不能保證哪個先執行,哪個先把相關資源下載完,哪個先執行 -->
<script type="module" src="module1.js" asnyc></script>
<script type="module" src="module2

交模塊作為Worker加載

// 第二個參數用來指定類型
let worker = new Worker('module.js',{type:'module'})

瀏覽器模塊說明符解析

瀏覽器要求模塊說明符具有以下幾個格式之一:

  • /開頭的解析為從根目錄開始
  • ./開頭的解析為從當前目錄開始
  • ../開頭的解析為從父目錄開始
  • URL格式

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4339

(0)
Walker的頭像Walker
上一篇 2025年2月26日 17:52
下一篇 2025年3月8日 13:04

相關推薦

  • Go工程師體系課 010【學習筆記】

    es 安裝 elasticsearch(理解為庫) kibana(理解為連接工具)es 和 kibana(5601) 的版本要保持一致 MySQL 對照學習 Elasticsearch(ES) 術語對照 MySQL Elasticsearch database index(索引) table type(7.x 起固定為 _doc,8.x 徹底移除多 type…

    個人 2025年11月25日
    32400
  • Node深入淺出(聖思園教育) 003【學習筆記】

    WebSocket 與 SSE 總覽 WebSocket 基礎 定位:WebSocket 是一條在 HTTP 握手後升級的全雙工連接,允許客戶端與服務器在同一 TCP 通道上雙向推送數據,省去了反復輪詢。 握手流程: 客戶端通過 Upgrade: websocket 頭髮起 HTTP 請求; 服務器響應 101 Switching Protocols,雙方協…

    個人 2025年11月24日
    31400
  • 【開篇】

    我是Walker,生於八十年代初,代碼與生活的旅者。全棧開發工程師,游走於前端與後端的邊界,執著於技術與藝術的交匯點。 代碼,是我編織夢想的語言;項目,是我刻畫未來的畫布。在鍵盤的敲擊聲中,我探索技術的無盡可能,讓靈感在代碼里永恆綻放。 深度咖啡愛好者,迷戀每一杯手衝的詩意與儀式感。在咖啡的醇香與苦澀中,尋找專注與靈感,亦如在開發的世界中追求極致與平衡。 騎…

    2025年2月6日 個人
    2.2K00
  • 無畏前行,拳釋力量 🥊💪

    拼搏,是一種態度 生活就像一場比賽,沒有捷徑可走,只有不斷訓練、突破、超越,才能站上屬於自己的舞台。這不僅是一種對抗,更是一種自我的覺醒——敢於迎戰,敢於挑戰,敢於成為更強的自己。 運動中的拼搏精神 無論是拳擊、跑步,還是力量訓練,每一次出拳、每一次揮汗、每一次咬牙堅持,都是對身體與心靈的磨煉。拼搏不是單純的對抗,而是一種態度——面對挑戰,不退縮;面對失敗,…

    個人 2025年2月26日
    1.3K00
  • Go工程師體系課 009【學習筆記】

    其它一些功能 個人中心 收藏 管理收貨地址(增刪改查) 留言 拷貝inventory_srv--> userop_srv 查詢替換所有的inventory Elasticsearch 深度解析文檔 1. 甚麼是Elasticsearch Elasticsearch是一個基於Apache Lucene構建的分布式、RESTful搜索和分析引擎,能夠快速地…

    個人 2025年11月25日
    23800
簡體中文 繁體中文 English
歡迎🌹 Coding never stops, keep learning! 💡💻 光臨🌹