你不知道的 JavaScript 1.作用域和閉包

作用域

作用域是一套存儲(chǔ)、訪問變量的規(guī)則。這套規(guī)則用來管理引擎如何在當(dāng)前作用域以及嵌套的子作用域中根據(jù)標(biāo)識(shí)符名稱進(jìn)行變量查找。如果查找的目的是對(duì)變量進(jìn)行賦值,那么就會(huì)使用 LHS 查詢;如果目的是獲取變量的值,就會(huì)使用 RHS 查詢。

當(dāng)變量出現(xiàn)在賦值操作左側(cè)時(shí)進(jìn)行 LHS 查詢,出現(xiàn)在右側(cè)時(shí)進(jìn)行 RHS 查詢。

賦值操作符會(huì)導(dǎo)致 LHS 查詢。=操作符或調(diào)用函數(shù)時(shí)傳入?yún)?shù)的操作都會(huì)導(dǎo)致關(guān)聯(lián)作用域的賦值操作(即 LHS 查詢)。

編譯原理

傳統(tǒng)編程語言的編譯過程:

graph TD
A[分詞/詞法分析] -->B(解析/語法分析)
B --> C[代碼生成]
  1. 分詞/詞法分析:將由字符組成的字符串分解成(對(duì)編程語言來說)有意義的代碼塊,這些代碼塊被稱為詞法單元(token)。
  2. 解析/語法分析:將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語法結(jié)構(gòu)的樹。這個(gè)樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。
  3. 代碼生成:將 AST 轉(zhuǎn)換為可執(zhí)行代碼。

JavaScript 引擎的特點(diǎn):

  1. JavaScript 不是提前編譯的,編譯結(jié)果也不能在分布式系統(tǒng)中進(jìn)行移植。
  2. 在語法分析和代碼生成階段有特定的步驟來對(duì)運(yùn)行性能進(jìn)行優(yōu)化,包括對(duì)冗余元素進(jìn)行優(yōu)化等。
  3. JavaScript 引擎不會(huì)有大量的(像其他語言編譯器那么多的)時(shí)間用來進(jìn)行優(yōu)化,因?yàn)榕c其他語言不同,JavaScript 的編譯過程不是發(fā)生在構(gòu)建之前的。大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短!)的時(shí)間內(nèi)。

簡(jiǎn)單地說,任何 JavaScript 代碼在執(zhí)行前都要進(jìn)行編譯

理解作用域

引擎:從頭到尾負(fù)責(zé)整個(gè) JavaScript 程序的編譯及執(zhí)行過程。
編譯器:負(fù)責(zé)語法分析及代碼生成等。
作用域:負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問權(quán)限。

/* 變量的賦值操作:
 * 編譯器首先會(huì)在當(dāng)前作用域中聲明一個(gè)變量(如果之前沒有聲明過);
 * 在運(yùn)行時(shí),引擎會(huì)在作用域中查找該變量,如果能夠找到就會(huì)對(duì)它賦值。
 */
var a = 2;

編譯器首先會(huì)將這段程序分解成詞法單元,然后將詞法單元解析成一個(gè)樹結(jié)構(gòu)。接下來,編譯器執(zhí)行代碼生成流程:

  1. var a 聲明變量:「編譯器」會(huì)詢問「作用域」是否已經(jīng)有一個(gè)該名稱的變量存在于同一個(gè)作用域的集合中。如果是,編譯器會(huì)忽略該聲明,繼續(xù)進(jìn)行編譯;否則它會(huì)要求作用域在當(dāng)前作用域的集合中聲明一個(gè)新的變量,并命名為 a
  2. a = 2 賦值:「編譯器」會(huì)為「引擎」生成運(yùn)行時(shí)所需的代碼。「引擎」運(yùn)行時(shí)會(huì)首先詢問「作用域」,在當(dāng)前的作用域集合中是否存在一個(gè)叫作 a 的變量。如果是,引擎就會(huì)使用這個(gè)變量;如果否,引擎會(huì)繼續(xù)查找該變量。

編譯器查詢?cè)恚篖HS 和 RHS 查詢

當(dāng)變量出現(xiàn)在賦值操作左側(cè)時(shí)進(jìn)行 LHS 查詢,出現(xiàn)在右側(cè)時(shí)進(jìn)行 RHS 查詢。

LHS 查詢:找到變量的容器本身,從而可以對(duì)其賦值。
RHS 查詢:查詢某個(gè)變量的值;

// 對(duì) a 的引用是一個(gè) RHS 引用
// 因?yàn)檫@里 a 并沒有賦予任何值。相應(yīng)地,需要查找并取得 a 的值,這樣才能將值傳遞給 console.log(..)。
console.log(a);

// 對(duì) a 的引用則是 LHS 引用
// 因?yàn)閷?shí)際上我們并不關(guān)心當(dāng)前的值是什么,只是想要為 = 2 這個(gè)賦值操作找到一個(gè)目標(biāo)。
a = 2;

既有 RHS 引用,又有 LHS 引用的情況:

function foo(a) {
  console.log('object :', a)
}

/* 
 * 首先,foo(...) 函數(shù)調(diào)用需要對(duì) foo 進(jìn)行 RHS 引用,即找到 foo 的值。
 * 隱式的 a=2 操作:首先對(duì) a 進(jìn)行 LHS 查詢,再對(duì) a 進(jìn)行 RHS 引用對(duì) a 進(jìn)行賦值。
 */
foo(2)

作用域嵌套

當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。
因此,在當(dāng)前作用域中無法找到某個(gè)變量時(shí),引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止。

把作用域鏈比喻成一個(gè)建筑

把作用域鏈比喻成一個(gè)建筑

這個(gè)建筑代表程序中的嵌套作用域鏈。第一層樓代表當(dāng)前的執(zhí)行作用域,也就是你所處的位置。建筑的頂層代表全局作用域。

LHS 和 RHS 引用都會(huì)在當(dāng)前樓層進(jìn)行查找,如果沒有找到,就會(huì)坐電梯前往上一層樓,如果還是沒有找到就繼續(xù)向上,以此類推。一旦抵達(dá)頂層(全局作用域),可能找到了你所需的變量,也可能沒找到,但無論如何查找過程都將停止。

異常

在變量還沒有聲明(在任何作用域中都無法找到該變量)的情況下,LHS 和 RHS 查詢的行為是不一樣的。

非嚴(yán)格模式下:

  • 如果 RHS 查詢?cè)谒星短椎淖饔糜蛑斜閷げ坏剿璧淖兞浚婢蜁?huì)拋出 ReferenceError 異常。
  • 當(dāng)引擎執(zhí)行 LHS 查詢時(shí),如果在頂層(全局作用域)中也無法找到目標(biāo)變量,全局作用域中就會(huì)創(chuàng)建一個(gè)具有該名稱的變量,并將其返還給引擎,前提是程序運(yùn)行在非“嚴(yán)格模式”下。

嚴(yán)格模式下:

  • 嚴(yán)格模式禁止自動(dòng)或隱式地創(chuàng)建全局變量。因此,在嚴(yán)格模式中 LHS 查詢失敗時(shí),并不會(huì)創(chuàng)建并返回一個(gè)全局變量,引擎會(huì)拋出同 RHS 查詢失敗時(shí)類似的 ReferenceError 異常。
  • 如果 RHS 查詢找到了一個(gè)變量,但是你嘗試對(duì)這個(gè)變量的值進(jìn)行不合理的操作,那么引擎會(huì)拋出另外一種類型的異常,叫作 TypeError。
  • ReferenceError 同作用域判別失敗相關(guān),而 TypeError 則代表作用域判別成功了,但是對(duì)結(jié)果的操作是非法或不合理的。

詞法作用域

作用域的兩種工作模型:

  1. 詞法作用域;
  2. 動(dòng)態(tài)作用域。

JavaScript 中的作用域就是詞法作用域。

1.詞法階段

詞法作用域就是定義在詞法階段的作用域。詞法作用域意味著作用域是由書寫代碼時(shí)函數(shù)聲明的位置來決定的。編譯的詞法分析階段基本能夠知道全部標(biāo)識(shí)符在哪里以及是如何聲明的,從而能夠預(yù)測(cè)在執(zhí)行過程中如何對(duì)它們進(jìn)行查找。

該示例中有三個(gè)逐級(jí)嵌套的作用域。為了幫助理解,可以將它們想象成幾個(gè)逐級(jí)包含的氣泡:


詞法作用域
  1. 包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符:foo。
  2. 包含著 foo 所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符:a、bar 和 b。
  3. 包含著 bar 所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c。

2.查找

作用域氣泡的結(jié)構(gòu)和互相之間的位置關(guān)系給引擎提供了足夠的位置信息,引擎用這些信息來查找標(biāo)識(shí)符的位置。

  • 作用域查找始終從運(yùn)行時(shí)所處的最內(nèi)部作用域開始,逐級(jí)向外或者說向上進(jìn)行查找,直到遇見第一個(gè)匹配的標(biāo)識(shí)符為止。
  • 作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止。在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,這叫作“遮蔽效應(yīng)”(內(nèi)部的標(biāo)識(shí)符“遮蔽”了外部的標(biāo)識(shí)符)。
  • 詞法作用域查找只會(huì)查找一級(jí)標(biāo)識(shí)符。

欺騙詞法

?? 欺騙詞法作用域會(huì)導(dǎo)致性能下降,因?yàn)橐鏌o法在編譯時(shí)對(duì)作用域查找進(jìn)行優(yōu)化,故不推薦使用。

eval() 不推薦使用

JavaScript 中的 eval(...) 函數(shù)可以接受一個(gè)字符串為參數(shù),并將其中的內(nèi)容視為好像在書寫時(shí)就存在于程序中這個(gè)位置的代碼。換句話說,可以在你寫的代碼中用程序生成代碼并運(yùn)行,就好像代碼是寫在那個(gè)位置的一樣。

JavaScript 中的 eval(...) 函數(shù)可以對(duì)一段包含一個(gè)或多個(gè)聲明的“代碼”字符串進(jìn)行演算,并借此來修改已經(jīng)存在的詞法作用域(在運(yùn)行時(shí))。

默認(rèn)情況下,如果 eval(..) 中所執(zhí)行的代碼包含有一個(gè)或多個(gè)聲明(無論是變量還是函數(shù)),就會(huì)對(duì) eval(..) 所處的詞法作用域進(jìn)行修改。

function foo(str, a) {
    // 這段代碼實(shí)際上在 foo(..) 內(nèi)部創(chuàng)建了一個(gè)變量 b,并遮蔽了外部(全局)作用域中的同名變量。
    eval( str ); // 欺騙!
    console.log( a, b );
}

var b = 2;

foo( "var b = 3;", 1 ); // 1 3

在程序中動(dòng)態(tài)生成代碼的使用場(chǎng)景非常罕見,因?yàn)樗鶐淼暮锰師o法抵消性能上的損失。

with 不推薦使用

with 通常被當(dāng)作重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式,可以不需要重復(fù)引用對(duì)象本身。

with 可以將一個(gè)沒有或有多個(gè)屬性的對(duì)象處理為一個(gè)完全隔離的詞法作用域,因此這個(gè)對(duì)象的屬性也會(huì)被處理為定義在這個(gè)作用域中的詞法標(biāo)識(shí)符。

with 本質(zhì)上是通過將一個(gè)對(duì)象的引用當(dāng)作作用域來處理,將對(duì)象的屬性當(dāng)作作用域中的標(biāo)識(shí)符來處理,從而創(chuàng)建了一個(gè)新的詞法作用域(同樣是在運(yùn)行時(shí))。

function foo(obj) {
    with (obj) {
        a = 2;
    }
}

var o1 = {
    a: 3
};

var o2 = {
    b: 3
};

foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
// o2 的作用域、foo(..) 的作用域和全局作用域中都沒有找到標(biāo)識(shí)符 a,因此當(dāng) a=2 執(zhí)行 時(shí),自動(dòng)創(chuàng)建了一個(gè)全局變量(因?yàn)槭欠菄?yán)格模式)。
console.log( a ); // 2 -- Oops, leaked global!

函數(shù)作用域和塊作用域

函數(shù)是 JavaScript 中最常見的作用域單元,除此之外,JavaScript 中也有幾個(gè)叫做「塊作用域」的特性(如letconst)。

函數(shù)中的作用域

函數(shù)作用域:屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用(事實(shí)上在嵌套的作用域中也可以使用)。

隱藏內(nèi)部實(shí)現(xiàn)

由「函數(shù)作用域」這一規(guī)則所引發(fā)的啟示:可以把變量和函數(shù)包裹在一個(gè)函數(shù)的作用域中,然后用這個(gè)作用域來“隱藏”它們。

最小特權(quán)原則/最小授權(quán)或最小暴露原則:在軟件設(shè)計(jì)中,應(yīng)該最小限度地暴露必要內(nèi)容,而將其他內(nèi)容都“隱藏”起來,比如某個(gè)模塊或?qū)ο蟮?API 設(shè)計(jì)。

規(guī)避沖突

“隱藏”作用域中的變量和函數(shù)可以避免同名標(biāo)識(shí)符之間的沖突,兩個(gè)標(biāo)識(shí)符可能具有相同的名字但用途卻不一樣,無意間可能造成命名沖突。沖突會(huì)導(dǎo)致變量的值被意外覆蓋。

  1. 全局命名空間;
  2. 模塊機(jī)制:通過依賴管理器的機(jī)制將庫(kù)的標(biāo)識(shí)符顯式地導(dǎo)入到另外一個(gè)特定的作用域中。

函數(shù)作用域

在任意代碼片段外部添加包裝函數(shù),可以將內(nèi)部的變量和函數(shù)定義“隱藏”起來,外部作用域無法訪問包裝函數(shù)內(nèi)部的任何內(nèi)容。

var a = 2;

// 函數(shù)表達(dá)式語法
// (function foo(){ .. })作為函數(shù)表達(dá)式意味著foo只能在..所代表的位置中被訪問,外部作用域則不行。
// foo 被綁定在函數(shù)表達(dá)式自身的函數(shù)中
// foo 變量名被隱藏在自身中意味著不會(huì)非必要地污染外部作用域
(function foo () {
  var a = 3;
  console.log(a); // 3
})();

console.log(a); // 2

匿名和具名

匿名函數(shù)表達(dá)式

// function().. 沒有名稱標(biāo)識(shí)符
setTimeout( function(){
    console.log("I waited 1 second!");
}, 1000 );

函數(shù)表達(dá)式可以匿名,而函數(shù)聲明則不可以省略函數(shù)名。
匿名函數(shù)的缺點(diǎn):

  1. 調(diào)試?yán)щy,匿名函數(shù)在棧追蹤中不會(huì)顯示出有意義的函數(shù)名。
  2. 無法引用自身,如果沒有函數(shù)名,當(dāng)函數(shù)需要引用自身時(shí)只能使用已經(jīng)過期的 arguments.callee 引用。
  3. 匿名函數(shù)沒有函數(shù)名,降低了代碼的可讀性/可理解性。一個(gè)描述性的名稱可以讓代碼不言自明。

行內(nèi)函數(shù)表達(dá)式

setTimeout( function timeoutHandler(){ // <-- Look, I have a name!
    console.log( "I waited 1 second!" );
}, 1000 );

通過「行內(nèi)函數(shù)表達(dá)式」的方式可以給函數(shù)表達(dá)式指定一個(gè)函數(shù)名,可以有效解決匿名函數(shù)的缺點(diǎn)。

??始終給函數(shù)表達(dá)式命名是一個(gè)最佳實(shí)踐。

立即執(zhí)行函數(shù)表達(dá)式(IIFE)

var a = 2;

// (function foo(){ .. })()
// 第一個(gè) ( ) 將函數(shù)變成了一個(gè)函數(shù)表達(dá)式,
// 第二個(gè) ( ) 表示立即執(zhí)行該函數(shù)。
(function foo () {
  var a = 3;
  console.log(a); // 3
})();

console.log(a); // 2

相較于傳統(tǒng)的 IIFE(Immediately Invoked Function Expression,立即執(zhí)行函數(shù)表達(dá)式) 形式,很多人都更喜歡另一個(gè)改進(jìn)的形式:(function(){ .. }())。兩者功能一致。

IIFE 的常見用法:

  1. 配合匿名函數(shù)表達(dá)式使用。
  2. 把匿名函數(shù)表達(dá)式當(dāng)作函數(shù)調(diào)用并傳遞參數(shù)。
var a = 2;

// 將 window 對(duì)象的引用傳遞進(jìn)去,但將參數(shù)命名為 global
(function IIFE (global) {
  var a = 3;
  console.log(a); // 3
  console.log(global.a); // 2
})(window);

console.log(a); // 2

IIFE 的常見用法:3.解決 undefined 標(biāo)識(shí)符的默認(rèn)值被錯(cuò)誤覆蓋導(dǎo)致的異常(不常見)。
IIFE 的常見用法:4.倒置代碼的運(yùn)行順序,UMD 項(xiàng)目中被廣泛使用。

塊作用域

塊作用域:變量和函數(shù)不僅可以屬于所處的作用域,也可以屬于某個(gè)代碼塊(通常指 { .. } 內(nèi)部)。

塊作用域的用處:變量的聲明應(yīng)該距離使用的地方越近越好,并最大限度地本地化。

with

with 從對(duì)象中創(chuàng)建出的作用域僅在 with 聲明中而非外部作用域中有效。

try/catch

JavaScript 的 ES3 規(guī)范中規(guī)定 try/catchcatch 分句會(huì)創(chuàng)建一個(gè)塊作用域,其中聲明的變量?jī)H在 catch 內(nèi)部有效。

try {
  undefined(); // 執(zhí)行一個(gè)非法操作來強(qiáng)制制造一個(gè)異常
}
catch (err) {
  console.log(err); // 能夠正常執(zhí)行!
}

console.log(err); // ReferenceError: `err` not found

let

let 關(guān)鍵字可以自動(dòng)將變量綁定到所在的任意作用域中。

var foo = true;

if (foo) {
  let bar = foo * 2;
  bar = something(bar);
  // 可以訪問 bar 變量
  console.log(bar);
}

// {...} 外部無法訪問 bar 變量
console.log(bar); // ReferenceError 引用錯(cuò)誤

使用 let 進(jìn)行的聲明不會(huì)在塊作用域中進(jìn)行提升:

{
   console.log( bar ); // ReferenceError!
   let bar = 2;
}

const

ES6 引入了 const,用來創(chuàng)建塊作用域變量,但其值是固定的 (常量)。之后任何試圖修改值的操作都會(huì)引起錯(cuò)誤。

提升

  • 變量/函數(shù)聲明提升:所有的聲明(變量和函數(shù))都會(huì)被“移動(dòng)”到各自作用域的最頂端。只有聲明本身會(huì)被提升,而賦值或其他運(yùn)行邏輯會(huì)留在原地。
  • 函數(shù)會(huì)首先被提升,然后才是變量。
  • 函數(shù)聲明會(huì)被提升,但是函數(shù)表達(dá)式卻不會(huì)被提升。
  • 使用 letconst 進(jìn)行聲明的變量不會(huì)在塊作用域中進(jìn)行提升。

作用域閉包

當(dāng)函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這時(shí)就產(chǎn)生了閉包。

function foo () {
  var a = 2;

  // 函數(shù) bar() 的詞法作用域能夠訪問 foo() 的內(nèi)部作用域。
  function bar () {
    console.log(a);
  }
    
  // 將 bar 所引用的函數(shù)對(duì)象本身當(dāng)作返回值(把一個(gè)內(nèi)部函數(shù)當(dāng)作值返回)
  return bar;
}

// 閉包:bar() 依然持有對(duì)它的作用域的引用。
// foo() 函數(shù)執(zhí)行之后,foo() 的整個(gè)內(nèi)部作用域并沒有被銷毀,因?yàn)?bar() 函數(shù)仍然引用著 foo() 的內(nèi)部作用域
var baz = foo();

// bar() 在自己定義的詞法作用域以外的地方執(zhí)行了
// 閉包使得 bar() 函數(shù)仍然可以繼續(xù)訪問它被定義時(shí)的詞法作用域
baz(); // 2 -- 朋友,這就是閉包的效果。

循環(huán)和閉包

使用 var 關(guān)鍵字:

// 盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè) i。
// for 循環(huán)外面也可以訪問變量 i
for (var i = 1; i <= 5; i++) {
  // 延遲函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行,而函數(shù)終止的條件是當(dāng) i = 6 時(shí)。
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

/*
6
6
6
6
6
*/

使用 let 關(guān)鍵字:

// 變量在循環(huán)過程中不止被聲明一次,每次迭代都會(huì)聲明。
// 每個(gè)迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來初始化這個(gè)變量。
for (let index = 0; index <= 5; index++) {
  setTimeout(() => {
    console.log(index)
  }, index);
}

/*
0
1
2
3
4
5
*/

模塊

模塊的底層原理就是閉包

模塊暴露

模塊的兩個(gè)主要特征:

  1. 為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù);
  2. 包裝函數(shù)的返回值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉包。

模塊也是普通的函數(shù),因此可以接受參數(shù)。

模塊模式另一個(gè)簡(jiǎn)單但強(qiáng)大的變化用法是,命名將要作為公共 API 返回的對(duì)象:

未來的模塊機(jī)制

ES6 中為模塊增加了一級(jí)語法支持。但通過模塊系統(tǒng)進(jìn)行加載時(shí),ES6 會(huì)將文件當(dāng)作獨(dú)立的模塊來處理。每個(gè)模塊都可以導(dǎo)入其他模塊或特定的 API 成員,同樣也可以導(dǎo)出自己的 API 成員。

ES6 的模塊必須被定義在一個(gè)獨(dú)立的文件中(即一個(gè)文件一個(gè)模塊)。

import 可以將一個(gè)模塊中的一個(gè)或多個(gè) API 導(dǎo)入到當(dāng)前作用域中,并分別綁定在一個(gè)變量上。
module 會(huì)將整個(gè)模塊的 API 導(dǎo)入并綁定到一個(gè)變量上。
export 會(huì)將當(dāng)前模塊的一個(gè)標(biāo)識(shí)符(變量、函數(shù))導(dǎo)出為公共 API。
這些操作可以在模塊定義中根據(jù)需要使用任意多次。

bar.js

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

export hello;

foo.js

// 僅從 "bar" 模塊導(dǎo)入 hello()
import hello from "bar";

var hungry = "hippo";

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

export awesome;

baz.js

// 導(dǎo)入完整的 "foo" 和 "bar" 模塊
module foo from "foo";
module bar from "bar";

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

foo.awesome(); // LET ME INTRODUCE: HIPPO
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容