自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

50 種 ES6 模塊,面試被問(wèn)麻了

開(kāi)發(fā) 前端
如果你問(wèn)開(kāi)發(fā)人員:"對(duì)你來(lái)說(shuō)最難的 JS 題目是什么?",你絕不會(huì)聽(tīng)到他說(shuō)是 ES6 模塊。但統(tǒng)計(jì)數(shù)據(jù)更能說(shuō)明問(wèn)題!我們統(tǒng)計(jì)了我們電報(bào)頻道中各種主題的問(wèn)答錯(cuò)誤答案數(shù)量,發(fā)現(xiàn) ES6 模塊是最難的主題之一。

測(cè)驗(yàn) #1: 53%的答案正確

// index.mjs
import { default } from './module.mjs';

console.log(default);
// module.mjs
export default 'bar';

首先,讓我們記住各種導(dǎo)入和導(dǎo)出語(yǔ)法:

如果檢查表中的 Import 語(yǔ)法,就會(huì)發(fā)現(xiàn)沒(méi)有與我們的代碼相匹配的語(yǔ)法:

import { default } from ‘./module.mjs’;

因?yàn)榻故褂眠@種語(yǔ)法。測(cè)驗(yàn)代碼會(huì)出現(xiàn)以下錯(cuò)誤:

SyntaxError: Unexpected reserved word

在 import { default } from ‘./module.mjs’; 行中, default 是 export 的名稱,也是該作用域中的變量名稱,這是被禁止的,因?yàn)?nbsp;default 是一個(gè)保留字。解決方法很簡(jiǎn)單:

import { default as foo } from ‘./module.mjs’;

現(xiàn)在, default 是導(dǎo)出的名稱, foo 是變量的名稱。換句話說(shuō),如果你想在默認(rèn)導(dǎo)出中使用命名導(dǎo)入語(yǔ)法,就必須重命名它。就是這樣,非常簡(jiǎn)單!

測(cè)驗(yàn) #2:35% 的正確答案

// index.js
console.log('index.js');

import { sum } from './helper.js';

console.log(sum(1, 2));
// helper.js

console.log('helper.js');
export const sum = (x, y) => x + y;

沒(méi)有多少開(kāi)發(fā)人員知道的一個(gè)重要的細(xì)微差別是,導(dǎo)入是被提升的。也就是說(shuō),在引擎解析代碼時(shí),導(dǎo)入就會(huì)被加載。所有依賴項(xiàng)都將在代碼運(yùn)行前加載。

因此,我們將按照以下順序查看日志:

helper.js, index.js, 3

如果希望在導(dǎo)入聲明之前執(zhí)行某些代碼,可考慮將其移至單獨(dú)的文件中:

// new index.js

import './logs.js';
import { sum } from './helper.js';

console.log(sum(1, 2));

logs.js

console.log('index.js');

現(xiàn)在我們有了預(yù)期的輸出結(jié)果:

index.js, helper.js, 3

測(cè)驗(yàn) #3:42% 的正確答案

index.mjs

// index.mjs
import './module.mjs';
import { num } from './counter.mjs';

console.log('index num =', num);

module.mjs

// module.mjs
import { num } from './counter.mjs';

console.log('module num =', num);

counter.mjs

// counter.mjs
if (!globalThis.num) {
  globalThis.num = 0;
}

export const num = ++globalThis.num;

Modules are singletons. 模塊是單例。

無(wú)論從同一位置或不同位置導(dǎo)入模塊多少次,模塊都只會(huì)被執(zhí)行和加載一次。換句話說(shuō),模塊實(shí)例只有一個(gè)。

測(cè)驗(yàn) #4:34% 的正確答案

index.mjs

// index.mjs
import './module.mjs?param=5;'

module.mjs

// module.mjs
console.log(import.meta.url);

這個(gè)問(wèn)題如果沒(méi)有對(duì)ES6有比較深的理解,就不太好回答出來(lái)。

根據(jù) MDN:

import.meta 對(duì)象為 JavaScript 模塊提供特定于上下文的元數(shù)據(jù)。它包含有關(guān)模塊的信息。

它返回一個(gè)帶有 url 屬性的對(duì)象,url 屬性表示模塊的基本 URL。對(duì)于外部腳本,url 將是獲取腳本的 URL;對(duì)于內(nèi)嵌腳本,url 將是包含腳本的文檔的基本 URL。

請(qǐng)注意,這將包括查詢參數(shù)和/或哈希值(即,跟在 "?" 或 "#" 之后的部分)。

測(cè)驗(yàn) #5:45% 的正確答案

index.js

import myCounter from './counter';

myCounter += 1;

console.log(myCounter);

counter.js

// counter.js
let counter = 5;
export default counter;

另一個(gè)大多數(shù)開(kāi)發(fā)者容易忽視的非常重要的點(diǎn)是,在導(dǎo)入模塊的作用域中,導(dǎo)入的變量表現(xiàn)得像常量。

為了使代碼正常工作,我們可以導(dǎo)出一個(gè)對(duì)象,例如,并更改其屬性。

測(cè)驗(yàn) #6:11%的正確答案

// index.mjs
import foo from './module.mjs';

console.log(typeof foo);
// module.mjs
foo = 25;

export default function foo() {}

首先,這:

export default function foo() {}

等于:

function foo() {}
export { foo as default }

這也等于:

function foo() {}
export default foo

現(xiàn)在是時(shí)候回想起函數(shù)是如何被提升的,以及變量的初始化總是在函數(shù)/變量聲明之后進(jìn)行。

引擎處理完模塊代碼后,看起來(lái)是這樣的:

function foo() {}

foo = 25;

export { foo as default }

因此,測(cè)驗(yàn)結(jié)果就是 number 。

測(cè)驗(yàn) #7:17%的正確答案

// index.mjs
import defaultFoo, { foo } from './module.mjs';

setTimeout(() => {
  console.log(foo);
  console.log(defaultFoo);
}, 2000);
// module.mjs

let foo = 'bar';

export { foo };
export default foo;

setTimeout(() => {
  foo = 'baz';
}, 1000);

在大多數(shù)情況下,導(dǎo)入的數(shù)據(jù)是實(shí)時(shí)的。也就是說(shuō),如果導(dǎo)出的值發(fā)生了變化,這種變化會(huì)反映在導(dǎo)入的變量上。

但默認(rèn)導(dǎo)出并非如此:

export default foo;

使用這種語(yǔ)法時(shí),導(dǎo)出的不是變量,而是變量值??梢韵襁@樣導(dǎo)出默認(rèn)值,而無(wú)需使用變量:

export default ‘hello’;
export default 42;

如果查看測(cè)驗(yàn) #1 中使用導(dǎo)出語(yǔ)法的表格,就會(huì)發(fā)現(xiàn) export default function () {} 與 export default foo ( Export of values ) 所處的列 ( Default export ) 不同。

這是因?yàn)樗鼈兊男袨榉绞讲煌?,函?shù)仍然作為活引用傳遞:

// module.mjs
  export { foo };

  export default function foo() {};

  setTimeout(() => {
    foo = 'baz';
  }, 1000);
// index.mjs
  import defaultFoo, { foo } from './module.mjs';

  setTimeout(() => {
    console.log(foo); // baz
    console.log(defaultFoo); //baz
  }, 2000);

export { foo as default }; 位于 Named Export 列,與這兩列都不同。但對(duì)我們來(lái)說(shuō),唯一重要的是它不在 Export of values 列中。因此,這意味著當(dāng)以這種方式導(dǎo)出數(shù)據(jù)時(shí),它將與導(dǎo)入值進(jìn)行實(shí)時(shí)綁定。

測(cè)驗(yàn) #8: 40% 的正確答案

// index.mjs
import { shouldLoad } from './module1.mjs';

let num = 0;

if (shouldLoad) {
  import { num } from './module2.mjs';
}

console.log(num);
// module1.mjs
export const shouldLoad = true;
//module2.mjs
export const num = 1;

import { num } from ‘./module2.mjs’; 行將會(huì)出錯(cuò),因?yàn)閷?dǎo)入結(jié)構(gòu)必須位于腳本的頂層:

SyntaxError: Unexpected token ‘{‘

這是一個(gè)重要的限制,加上在文件路徑中使用變量的限制,使得 ES6 模塊成為靜態(tài)模塊。這意味著,與 Node.js 中使用的 Common.js 模塊不同,不必執(zhí)行代碼就能找出模塊之間的所有依賴關(guān)系。

在這個(gè)使用 Common.js 模塊的示例中,要確定將加載 a 或 b 模塊,需要運(yùn)行以下代碼:

let result;
if (foo()) {
    result = require('a');
} else {
    result = require('b');
}

模塊的靜態(tài)特性有很多好處。以下是其中一些:

  • 總是知道導(dǎo)入數(shù)據(jù)的確切結(jié)構(gòu)。這有助于在執(zhí)行代碼前發(fā)現(xiàn)錯(cuò)別字。
  • 異步加載。這是因?yàn)槟K是靜態(tài)的,可以在執(zhí)行模塊主體之前加載導(dǎo)入。
  • 支持循環(huán)依賴關(guān)系。我們將在下一次測(cè)驗(yàn)中詳細(xì)探討這種可能性。
  • 高效捆綁。在此不多贅述,您可以在本文中自行了解 Rollup 捆綁程序如何有效地構(gòu)建 ES6 模塊。

測(cè)驗(yàn) #9: 3% 的正確答案

// index.mjs
import { double, square } from './module.mjs';

export function calculate(value) {
  return value % 2 ? square(value) : double(value);
}
// module.mjs
import { calculate } from './index.mjs';

export function double(num) {
  return num * 2;
}

export function square(num) {
  return num * num;
}

console.log(calculate(3));

在上面的代碼中,我們可以看到循環(huán)依賴關(guān)系: index.mjs 從 module.mjs 導(dǎo)入 double 和 square 函數(shù),而 module.mjs 從 index.mjs 導(dǎo)入 calculation 函數(shù)。

這段代碼之所以能運(yùn)行,是因?yàn)?ES6 模塊本質(zhì)上非常支持循環(huán)依賴關(guān)系。例如,如果我們將這段代碼改寫為使用 Common.js 模塊,它將不再工作:

// index.js
const helpers = require('./module.js');

function calculate(value) {
  return value % 2 ? helpers.square(value) : helpers.double(value);
}

module.exports = {
  calculate
}
// module.js
const actions = require('./index.js');

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

console.log(actions.calculate(3)); // TypeError: actions.calculate is not a function

module.exports = {
  double,
  square
}
  • index.js 開(kāi)始加載。
  • 加載在第一行中斷,以加載 module.js :const helpers = require(‘./module.js’)。
  • module.js 開(kāi)始加載。
  • 在 console.log(actions.calculate(3)); 行中,由于 actions.calculate 未定義,代碼出錯(cuò)。這是因?yàn)?Common.js 同步加載模塊。 index.js 尚未加載,其導(dǎo)出對(duì)象目前為空。

如果調(diào)用一個(gè)帶延遲的導(dǎo)入函數(shù), index.js 模塊將有時(shí)間加載,代碼也將相應(yīng)地工作:

// module.js
const actions = require('./index.js');

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

function someFunctionToCallLater() {
  console.log(actions.calculate(3)); // Works
}

module.exports = {
  double,
  square
}

從前面的測(cè)驗(yàn)中了解到的,ES6 模塊支持循環(huán)依賴關(guān)系,因?yàn)樗鼈兪庆o態(tài)的--模塊的依賴關(guān)系在代碼執(zhí)行之前就已加載。

使上述代碼工作的另一個(gè)因素是提升。當(dāng)調(diào)用 calculate 函數(shù)時(shí),我們還沒(méi)有進(jìn)入定義該函數(shù)的行。

下面是捆綁模塊后的代碼:

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

console.log(calculate(3));

function calculate(value) {
  return value % 2 ? square(value) : double(value);
}

如果沒(méi)有變量提升,它將無(wú)法工作。

如果我們將計(jì)算聲明函數(shù)改為函數(shù)表達(dá)式:

export let calculate = function(value) {
  return value % 2 ? square(value) : double(value);
}

會(huì)出現(xiàn)以下錯(cuò)誤:

ReferenceError: Cannot access ‘calculate’ before initialization

測(cè)驗(yàn) #10: 31%的正確答案

// index.mjs
import { num } from './module.mjs';

console.log(num);
export let num = 0;

num = await new Promise((resolve) => {
  setTimeout(() => resolve(1), 1000);
});

頂層 await 是一個(gè)非常有用的特性,許多開(kāi)發(fā)者都不了解,也許是因?yàn)樗窃谧罱?ECMAScript 2022 中才引入的。啊,真不錯(cuò)!

頂層 await 使模塊能夠像大型異步函數(shù)一樣運(yùn)作:通過(guò)頂層 await,ECMAScript 模塊(ESM)可以等待資源,導(dǎo)致導(dǎo)入它們的其他模塊在開(kāi)始評(píng)估其主體之前必須等待。

模塊的標(biāo)準(zhǔn)行為是,在加載模塊導(dǎo)入的所有模塊并執(zhí)行其代碼之前,模塊中的代碼不會(huì)被執(zhí)行(參見(jiàn)測(cè)驗(yàn) #2)。事實(shí)上,隨著頂級(jí)等待的出現(xiàn),一切都沒(méi)有改變。模塊中的代碼不會(huì)被執(zhí)行,直到所有導(dǎo)入模塊中的代碼都被執(zhí)行,只是現(xiàn)在這包括等待模塊中所有等待的承諾被解決。

// index.js
console.log('index.js');

import { num } from './module.js';

console.log('num = ', num);
// module.js
export let num = 5;

console.log('module.js');

await new Promise((resolve) => {
  setTimeout(() => {
    console.log('module.js: promise 1');
    num = 10;
    resolve();
  }, 1000);
});

await new Promise((resolve) => {
  setTimeout(() => {
    console.log('module.js: promise 2');
    num = 20;
    resolve();
  }, 2000);
});

輸出:

module.js
module.js: promise 1
module.js: promise 2
index.js
num = 20

如果我們刪除 module.js 中第 5 行和第 13 行的等待,并在文件 index.js 中添加超時(shí),就會(huì)像這樣:

console.log('index.js');

import { num } from './module.js';

console.log('num = ', num);

setTimeout(() => {
  console.log('timeout num = ', num);
}, 1000);

setTimeout(() => {
  console.log('timeout num = ', num);
}, 2000);

輸出:

module.js
index.js
num = 5
module.js: promise 1
timeout num = 10
module.js: promise 2
timeout num = 20

我們將在今后的測(cè)驗(yàn)中再次使用頂級(jí)等待功能。

測(cè)驗(yàn) #11: 16%的正確答案

//index.mjs

import { shouldLoad } from './module1.mjs';

let num = 0;

if (shouldLoad) {
   ({ num } = import('./module2.mjs'));
}

console.log(num);
// module1.mjs

export const shouldLoad = true;
//module2.mjs

export const num = 1;

import() 調(diào)用(通常稱為動(dòng)態(tài)導(dǎo)入)是一種類似函數(shù)的表達(dá)式,它允許異步動(dòng)態(tài)加載 ECMAScript 模塊。它允許繞過(guò)導(dǎo)入聲明的語(yǔ)法限制,有條件或按需加載模塊。

該功能在 ES2020 中引入。

import(module) 返回一個(gè) promise ,該承諾會(huì)履行到一個(gè)包含模塊所有輸出的對(duì)象。由于 import(module) 返回的是一個(gè) promise,為了修正測(cè)驗(yàn)代碼,我們必須在導(dǎo)入調(diào)用之前添加 await 關(guān)鍵字:

if (shouldLoad) {
   ({ num } = await import('./module2.mjs'));
}

在這里,我們?cè)俅问褂庙攲?nbsp;await,這讓我們想起了這一功能的酷炫之處。

我敢肯定,你的應(yīng)用程序至少有一次出錯(cuò)崩潰了:

SyntaxError: await is only valid in async functions

當(dāng)試圖從全局作用域調(diào)用異步函數(shù)時(shí),經(jīng)常會(huì)出現(xiàn)這種情況。為了解決這個(gè)問(wèn)題,我們必須躲避丑陋的代碼:

(async () => {
  await [someAsyncFunc]();
})();

這不僅難看,而且在使用此模式異步加載模塊時(shí)可能會(huì)導(dǎo)致錯(cuò)誤。例如

// module1.mjs
let num;
(async () => {
 ({ num } = await import(‘./module2.mjs’));
})();

export { num };
// module2.mjs
export const num = 5;

導(dǎo)入 module1.mjs 時(shí), num 的結(jié)果會(huì)是什么 - 來(lái)自 module2 或 undefined 的值?這取決于何時(shí)訪問(wèn)變量:

import { num } from './module1.mjs';

console.log(num); // undefined
setTimeout(() => console.log(num), 100); // 5

有了頂級(jí) await ,只要您訪問(wèn)從 module1 導(dǎo)入的 num ,它就永遠(yuǎn)不會(huì)是 undefined :

let { num } = await import('./module2.mjs');

export { num };
import { num } from './module1.mjs';

console.log(num); // 5

測(cè)驗(yàn) #12: 21% 的正確答案

// index.mjs

const module1 = await import('./module1.mjs');
const module2 = await import('./module2.mjs');

console.log(module1, module2);

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

console.log(multiply(module1, module2));
// module1.mjs
export default await new Promise((resolve) => resolve(1));
// module2.mjs
export default await new Promise((resolve) => resolve(2));

上述代碼會(huì)出錯(cuò):

TypeError: Cannot convert object to primitive value

同意,一個(gè)相當(dāng)意外的錯(cuò)誤措辭。讓我們來(lái)看看這個(gè)錯(cuò)誤從何而來(lái)。

在這段代碼中,我們使用了動(dòng)態(tài)導(dǎo)入,這在前面的示例中已經(jīng)介紹過(guò)。要理解這段代碼中的問(wèn)題,我們需要仔細(xì)看看 import() 的返回值。

變量 module1 和 module2 的值與我們的預(yù)期不同。 import() 返回一個(gè) promise ,該promise  將實(shí)現(xiàn)一個(gè)與命名空間導(dǎo)入形狀相同的對(duì)象:

import * as name from moduleName

default 輸出可作為名為 default 的鍵使用。

因此,在變量 module1 和 module2 中分別有對(duì)象 { default: 1 } 和 { default: 2 } ,而不是值 1 和 2 。

那么,為什么兩個(gè)對(duì)象相乘時(shí)會(huì)出現(xiàn)如此奇怪的錯(cuò)誤,而不是我們習(xí)慣的 NaN 呢?

這是因?yàn)榉祷氐膶?duì)象具有 null 原型。因此,它沒(méi)有用于將對(duì)象轉(zhuǎn)換為基元的 toString() 方法。如果這個(gè)對(duì)象有一個(gè) Object 原型,我們就會(huì)在控制臺(tái)中看到 NaN 。

要修復(fù)測(cè)驗(yàn)代碼,我們需要做以下更改:

console.log(pow(module1.default, module2.default));

或:

const { default: module1 } = await import('./module1.mjs');
const { default: module2 } = await import('./module2.mjs');

測(cè)驗(yàn) #13:17%的正確答案

// index.js
import * as values from './intermediate.js';

console.log(values.x);
// module1.js
export const x = 1;
// module2.js
export const x = 2;
// intermediate.js
export * from './module1.js';
export * from './module2.js';

export * from ‘module’ 語(yǔ)法會(huì)將 "模塊"文件中所有已命名的導(dǎo)出內(nèi)容重新導(dǎo)出為當(dāng)前文件中已命名的導(dǎo)出內(nèi)容。如果存在多個(gè)同名導(dǎo)出,則不會(huì)重新導(dǎo)出其中任何一個(gè)。

因此,運(yùn)行這段代碼時(shí),我們會(huì)在控制臺(tái)中看到 undefined 。只有 17% 的答題者回答正確,大多數(shù)答題者(59%)認(rèn)為這段代碼會(huì)出錯(cuò)。事實(shí)上,這種無(wú)聲的失敗似乎并不是嚴(yán)格模式的典型表現(xiàn)。(如果您知道這種行為的原因,請(qǐng)?jiān)谠u(píng)論中告訴我。

順便提一下,如果在同樣的情況下顯式導(dǎo)入 x ,就會(huì)出現(xiàn)預(yù)期的錯(cuò)誤:

import { x } from ‘./intermediate.js’;
SyntaxError: The requested module ‘./intermediate.js’ contains conflicting star exports for name ‘x’

最后

沖~~~

責(zé)任編輯:姜華 來(lái)源: 大遷世界
相關(guān)推薦

2021-12-26 21:49:19

微信面試參數(shù)

2023-12-18 08:03:56

并發(fā)編程Java

2023-03-01 15:39:50

JavaScrip對(duì)象屬性ES6

2023-05-28 23:49:38

JavaScrip開(kāi)發(fā)

2021-02-05 12:34:33

線程池系統(tǒng)

2023-11-03 08:10:49

ThreadLoca內(nèi)存泄露

2021-08-16 07:05:58

ES6Promise開(kāi)發(fā)語(yǔ)言

2025-03-28 08:53:51

2021-08-02 05:51:29

foreachES6數(shù)組

2021-07-30 07:10:07

ES6函數(shù)參數(shù)

2017-10-09 18:21:20

JavaScriptES6ES8

2024-06-26 08:18:08

ES6模板字符串

2023-11-23 10:21:11

ECMAScriptJavaScript

2023-11-21 20:28:02

C++Pythonweb開(kāi)發(fā)

2021-08-18 07:05:57

ES6Asyncawait

2017-08-31 14:25:34

前端JavascriptES6

2020-07-01 07:58:20

ES6JavaScript開(kāi)發(fā)

2025-04-23 08:10:00

2021-04-13 10:41:25

Redis內(nèi)存數(shù)據(jù)庫(kù)

2022-07-26 09:02:15

ES6ES13ECMAScript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)