当前位置:首页 > 科技  > 软件

50 种 ES6 模块,面试被问麻了

来源: 责编: 时间:2023-09-28 10:09:07 179观看
导读测验 #1: 53%的答案正确// index.mjsimport { default } from './module.mjs';console.log(default);// module.mjsexport default 'bar';首先,让我们记住各种导入和导出语法:如果检查表中的 Import 语法,就会发现没有与

tmz28资讯网——每日最新资讯28at.com

测验 #1: 53%的答案正确

// index.mjsimport { default } from './module.mjs';console.log(default);
// module.mjsexport default 'bar';

首先,让我们记住各种导入和导出语法:tmz28资讯网——每日最新资讯28at.com

tmz28资讯网——每日最新资讯28at.com

tmz28资讯网——每日最新资讯28at.com

如果检查表中的 Import 语法,就会发现没有与我们的代码相匹配的语法:tmz28资讯网——每日最新资讯28at.com

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

因为禁止使用这种语法。测验代码会出现以下错误:tmz28资讯网——每日最新资讯28at.com

SyntaxError: Unexpected reserved wordtmz28资讯网——每日最新资讯28at.com

在 import { default } from ‘./module.mjs’; 行中, default 是 export 的名称,也是该作用域中的变量名称,这是被禁止的,因为 default 是一个保留字。解决方法很简单:tmz28资讯网——每日最新资讯28at.com

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

现在, default 是导出的名称, foo 是变量的名称。换句话说,如果你想在默认导出中使用命名导入语法,就必须重命名它。就是这样,非常简单!tmz28资讯网——每日最新资讯28at.com

测验 #2:35% 的正确答案

// index.jsconsole.log('index.js');import { sum } from './helper.js';console.log(sum(1, 2));
// helper.jsconsole.log('helper.js');export const sum = (x, y) => x + y;

没有多少开发人员知道的一个重要的细微差别是,导入是被提升的。也就是说,在引擎解析代码时,导入就会被加载。所有依赖项都将在代码运行前加载。tmz28资讯网——每日最新资讯28at.com

因此,我们将按照以下顺序查看日志:tmz28资讯网——每日最新资讯28at.com

helper.js, index.js, 3

如果希望在导入声明之前执行某些代码,可考虑将其移至单独的文件中:tmz28资讯网——每日最新资讯28at.com

// new index.jsimport './logs.js';import { sum } from './helper.js';console.log(sum(1, 2));

logs.js

console.log('index.js');

现在我们有了预期的输出结果:tmz28资讯网——每日最新资讯28at.com

index.js, helper.js, 3

测验 #3:42% 的正确答案

index.mjs

// index.mjsimport './module.mjs';import { num } from './counter.mjs';console.log('index num =', num);

module.mjs

// module.mjsimport { num } from './counter.mjs';console.log('module num =', num);

counter.mjs

// counter.mjsif (!globalThis.num) {  globalThis.num = 0;}export const num = ++globalThis.num;

Modules are singletons. 模块是单例。tmz28资讯网——每日最新资讯28at.com

无论从同一位置或不同位置导入模块多少次,模块都只会被执行和加载一次。换句话说,模块实例只有一个。tmz28资讯网——每日最新资讯28at.com

测验 #4:34% 的正确答案

index.mjs

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

module.mjs

// module.mjsconsole.log(import.meta.url);

这个问题如果没有对ES6有比较深的理解,就不太好回答出来。tmz28资讯网——每日最新资讯28at.com

根据 MDN:tmz28资讯网——每日最新资讯28at.com

import.meta 对象为 JavaScript 模块提供特定于上下文的元数据。它包含有关模块的信息。tmz28资讯网——每日最新资讯28at.com

它返回一个带有 url 属性的对象,url 属性表示模块的基本 URL。对于外部脚本,url 将是获取脚本的 URL;对于内嵌脚本,url 将是包含脚本的文档的基本 URL。tmz28资讯网——每日最新资讯28at.com

请注意,这将包括查询参数和/或哈希值(即,跟在 "?" 或 "#" 之后的部分)。tmz28资讯网——每日最新资讯28at.com

测验 #5:45% 的正确答案

index.js

import myCounter from './counter';myCounter += 1;console.log(myCounter);

counter.js

// counter.jslet counter = 5;export default counter;

另一个大多数开发者容易忽视的非常重要的点是,在导入模块的作用域中,导入的变量表现得像常量。tmz28资讯网——每日最新资讯28at.com

为了使代码正常工作,我们可以导出一个对象,例如,并更改其属性。tmz28资讯网——每日最新资讯28at.com

测验 #6:11%的正确答案

// index.mjsimport foo from './module.mjs';console.log(typeof foo);
// module.mjsfoo = 25;export default function foo() {}

首先,这:tmz28资讯网——每日最新资讯28at.com

export default function foo() {}

等于:tmz28资讯网——每日最新资讯28at.com

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

这也等于:tmz28资讯网——每日最新资讯28at.com

function foo() {}export default foo

现在是时候回想起函数是如何被提升的,以及变量的初始化总是在函数/变量声明之后进行。tmz28资讯网——每日最新资讯28at.com

引擎处理完模块代码后,看起来是这样的:tmz28资讯网——每日最新资讯28at.com

function foo() {}foo = 25;export { foo as default }

因此,测验结果就是 number 。tmz28资讯网——每日最新资讯28at.com

测验 #7:17%的正确答案

// index.mjsimport defaultFoo, { foo } from './module.mjs';setTimeout(() => {  console.log(foo);  console.log(defaultFoo);}, 2000);
// module.mjslet foo = 'bar';export { foo };export default foo;setTimeout(() => {  foo = 'baz';}, 1000);

在大多数情况下,导入的数据是实时的。也就是说,如果导出的值发生了变化,这种变化会反映在导入的变量上。tmz28资讯网——每日最新资讯28at.com

但默认导出并非如此:tmz28资讯网——每日最新资讯28at.com

export default foo;

使用这种语法时,导出的不是变量,而是变量值。可以像这样导出默认值,而无需使用变量:tmz28资讯网——每日最新资讯28at.com

export default ‘hello’;export default 42;

如果查看测验 #1 中使用导出语法的表格,就会发现 export default function () {} 与 export default foo ( Export of values ) 所处的列 ( Default export ) 不同。tmz28资讯网——每日最新资讯28at.com

这是因为它们的行为方式不同,函数仍然作为活引用传递:tmz28资讯网——每日最新资讯28at.com

// 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 列,与这两列都不同。但对我们来说,唯一重要的是它不在 Export of values 列中。因此,这意味着当以这种方式导出数据时,它将与导入值进行实时绑定。tmz28资讯网——每日最新资讯28at.com

测验 #8: 40% 的正确答案

// index.mjsimport { shouldLoad } from './module1.mjs';let num = 0;if (shouldLoad) {  import { num } from './module2.mjs';}console.log(num);
// module1.mjsexport const shouldLoad = true;
//module2.mjsexport const num = 1;

import { num } from ‘./module2.mjs’; 行将会出错,因为导入结构必须位于脚本的顶层:tmz28资讯网——每日最新资讯28at.com

SyntaxError: Unexpected token ‘{‘tmz28资讯网——每日最新资讯28at.com

这是一个重要的限制,加上在文件路径中使用变量的限制,使得 ES6 模块成为静态模块。这意味着,与 Node.js 中使用的 Common.js 模块不同,不必执行代码就能找出模块之间的所有依赖关系。tmz28资讯网——每日最新资讯28at.com

在这个使用 Common.js 模块的示例中,要确定将加载 a 或 b 模块,需要运行以下代码:tmz28资讯网——每日最新资讯28at.com

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

模块的静态特性有很多好处。以下是其中一些:tmz28资讯网——每日最新资讯28at.com

  • 总是知道导入数据的确切结构。这有助于在执行代码前发现错别字。
  • 异步加载。这是因为模块是静态的,可以在执行模块主体之前加载导入。
  • 支持循环依赖关系。我们将在下一次测验中详细探讨这种可能性。
  • 高效捆绑。在此不多赘述,您可以在本文中自行了解 Rollup 捆绑程序如何有效地构建 ES6 模块。

测验 #9: 3% 的正确答案

// index.mjsimport { double, square } from './module.mjs';export function calculate(value) {  return value % 2 ? square(value) : double(value);}
// module.mjsimport { calculate } from './index.mjs';export function double(num) {  return num * 2;}export function square(num) {  return num * num;}console.log(calculate(3));

在上面的代码中,我们可以看到循环依赖关系: index.mjs 从 module.mjs 导入 double 和 square 函数,而 module.mjs 从 index.mjs 导入 calculation 函数。tmz28资讯网——每日最新资讯28at.com

这段代码之所以能运行,是因为 ES6 模块本质上非常支持循环依赖关系。例如,如果我们将这段代码改写为使用 Common.js 模块,它将不再工作:tmz28资讯网——每日最新资讯28at.com

// index.jsconst helpers = require('./module.js');function calculate(value) {  return value % 2 ? helpers.square(value) : helpers.double(value);}module.exports = {  calculate}
// module.jsconst 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 functionmodule.exports = {  double,  square}
  • index.js 开始加载。
  • 加载在第一行中断,以加载 module.js :const helpers = require(‘./module.js’)。
  • module.js 开始加载。
  • 在 console.log(actions.calculate(3)); 行中,由于 actions.calculate 未定义,代码出错。这是因为 Common.js 同步加载模块。 index.js 尚未加载,其导出对象目前为空。

如果调用一个带延迟的导入函数, index.js 模块将有时间加载,代码也将相应地工作:tmz28资讯网——每日最新资讯28at.com

// module.jsconst 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}

从前面的测验中了解到的,ES6 模块支持循环依赖关系,因为它们是静态的--模块的依赖关系在代码执行之前就已加载。tmz28资讯网——每日最新资讯28at.com

使上述代码工作的另一个因素是提升。当调用 calculate 函数时,我们还没有进入定义该函数的行。tmz28资讯网——每日最新资讯28at.com

下面是捆绑模块后的代码:tmz28资讯网——每日最新资讯28at.com

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);}

如果没有变量提升,它将无法工作。tmz28资讯网——每日最新资讯28at.com

如果我们将计算声明函数改为函数表达式:tmz28资讯网——每日最新资讯28at.com

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

会出现以下错误:tmz28资讯网——每日最新资讯28at.com

ReferenceError: Cannot access ‘calculate’ before initialization

测验 #10: 31%的正确答案

// index.mjsimport { num } from './module.mjs';console.log(num);
export let num = 0;num = await new Promise((resolve) => {  setTimeout(() => resolve(1), 1000);});

顶层 await 是一个非常有用的特性,许多开发者都不了解,也许是因为它是在最近的 ECMAScript 2022 中才引入的。啊,真不错!tmz28资讯网——每日最新资讯28at.com

顶层 await 使模块能够像大型异步函数一样运作:通过顶层 await,ECMAScript 模块(ESM)可以等待资源,导致导入它们的其他模块在开始评估其主体之前必须等待。tmz28资讯网——每日最新资讯28at.com

模块的标准行为是,在加载模块导入的所有模块并执行其代码之前,模块中的代码不会被执行(参见测验 #2)。事实上,随着顶级等待的出现,一切都没有改变。模块中的代码不会被执行,直到所有导入模块中的代码都被执行,只是现在这包括等待模块中所有等待的承诺被解决。tmz28资讯网——每日最新资讯28at.com

// index.jsconsole.log('index.js');import { num } from './module.js';console.log('num = ', num);
// module.jsexport 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);});

输出:tmz28资讯网——每日最新资讯28at.com

module.jsmodule.js: promise 1module.js: promise 2index.jsnum = 20

如果我们删除 module.js 中第 5 行和第 13 行的等待,并在文件 index.js 中添加超时,就会像这样:tmz28资讯网——每日最新资讯28at.com

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);

输出:tmz28资讯网——每日最新资讯28at.com

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

我们将在今后的测验中再次使用顶级等待功能。tmz28资讯网——每日最新资讯28at.com

测验 #11: 16%的正确答案

//index.mjsimport { shouldLoad } from './module1.mjs';let num = 0;if (shouldLoad) {   ({ num } = import('./module2.mjs'));}console.log(num);
// module1.mjsexport const shouldLoad = true;
//module2.mjsexport const num = 1;

import() 调用(通常称为动态导入)是一种类似函数的表达式,它允许异步动态加载 ECMAScript 模块。它允许绕过导入声明的语法限制,有条件或按需加载模块。tmz28资讯网——每日最新资讯28at.com

该功能在 ES2020 中引入。tmz28资讯网——每日最新资讯28at.com

import(module) 返回一个 promise ,该承诺会履行到一个包含模块所有输出的对象。由于 import(module) 返回的是一个 promise,为了修正测验代码,我们必须在导入调用之前添加 await 关键字:tmz28资讯网——每日最新资讯28at.com

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

在这里,我们再次使用顶层 await,这让我们想起了这一功能的酷炫之处。tmz28资讯网——每日最新资讯28at.com

我敢肯定,你的应用程序至少有一次出错崩溃了:tmz28资讯网——每日最新资讯28at.com

SyntaxError: await is only valid in async functions

当试图从全局作用域调用异步函数时,经常会出现这种情况。为了解决这个问题,我们必须躲避丑陋的代码:tmz28资讯网——每日最新资讯28at.com

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

这不仅难看,而且在使用此模式异步加载模块时可能会导致错误。例如tmz28资讯网——每日最新资讯28at.com

// module1.mjslet num;(async () => { ({ num } = await import(‘./module2.mjs’));})();export { num };
// module2.mjsexport const num = 5;

导入 module1.mjs 时, num 的结果会是什么 - 来自 module2 或 undefined 的值?这取决于何时访问变量:tmz28资讯网——每日最新资讯28at.com

import { num } from './module1.mjs';console.log(num); // undefinedsetTimeout(() => console.log(num), 100); // 5

有了顶级 await ,只要您访问从 module1 导入的 num ,它就永远不会是 undefined :tmz28资讯网——每日最新资讯28at.com

let { num } = await import('./module2.mjs');export { num };
import { num } from './module1.mjs';console.log(num); // 5

测验 #12: 21% 的正确答案

// index.mjsconst 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.mjsexport default await new Promise((resolve) => resolve(1));
// module2.mjsexport default await new Promise((resolve) => resolve(2));

上述代码会出错:tmz28资讯网——每日最新资讯28at.com

TypeError: Cannot convert object to primitive valuetmz28资讯网——每日最新资讯28at.com

同意,一个相当意外的错误措辞。让我们来看看这个错误从何而来。tmz28资讯网——每日最新资讯28at.com

在这段代码中,我们使用了动态导入,这在前面的示例中已经介绍过。要理解这段代码中的问题,我们需要仔细看看 import() 的返回值。tmz28资讯网——每日最新资讯28at.com

变量 module1 和 module2 的值与我们的预期不同。 import() 返回一个 promise ,该promise  将实现一个与命名空间导入形状相同的对象:tmz28资讯网——每日最新资讯28at.com

import * as name from moduleName

default 输出可作为名为 default 的键使用。tmz28资讯网——每日最新资讯28at.com

因此,在变量 module1 和 module2 中分别有对象 { default: 1 } 和 { default: 2 } ,而不是值 1 和 2 。tmz28资讯网——每日最新资讯28at.com

那么,为什么两个对象相乘时会出现如此奇怪的错误,而不是我们习惯的 NaN 呢?tmz28资讯网——每日最新资讯28at.com

这是因为返回的对象具有 null 原型。因此,它没有用于将对象转换为基元的 toString() 方法。如果这个对象有一个 Object 原型,我们就会在控制台中看到 NaN 。tmz28资讯网——每日最新资讯28at.com

要修复测验代码,我们需要做以下更改:tmz28资讯网——每日最新资讯28at.com

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

或:tmz28资讯网——每日最新资讯28at.com

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

测验 #13:17%的正确答案

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

export * from ‘module’ 语法会将 "模块"文件中所有已命名的导出内容重新导出为当前文件中已命名的导出内容。如果存在多个同名导出,则不会重新导出其中任何一个。tmz28资讯网——每日最新资讯28at.com

因此,运行这段代码时,我们会在控制台中看到 undefined 。只有 17% 的答题者回答正确,大多数答题者(59%)认为这段代码会出错。事实上,这种无声的失败似乎并不是严格模式的典型表现。(如果您知道这种行为的原因,请在评论中告诉我。tmz28资讯网——每日最新资讯28at.com

顺便提一下,如果在同样的情况下显式导入 x ,就会出现预期的错误:tmz28资讯网——每日最新资讯28at.com

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

最后

冲~~~tmz28资讯网——每日最新资讯28at.com

tmz28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-11885-0.html50 种 ES6 模块,面试被问麻了

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 常用的限流方式-滑动窗口(计数器的改进版)

下一篇: Havoc远控源码剖析(协议篇)

标签:
  • 热门焦点
Top