公司网站制作与推广,网站快照历史,WordPress激活邮件链接无效,上海建设摩托车科技有限公司官网通过示例详细了解ES6导入导出模块
似乎许多开发人员认为 ES6 模块只不过是export、import关键字。事实上#xff0c;它更加多样化。它拥有强大的功能和鲜为人知的问题。在本文中#xff0c;我们将使用一些示例来了解这些内容。
示例一
// index.mjs
import { default } fr…通过示例详细了解ES6导入导出模块
似乎许多开发人员认为 ES6 模块只不过是export、import关键字。事实上它更加多样化。它拥有强大的功能和鲜为人知的问题。在本文中我们将使用一些示例来了解这些内容。
示例一
// index.mjs
import { default } from ./module.mjs;console.log(default);// module.mjs
export default bar;首先让我们记住各种导入和导出语法
导出 导入 如果我们对照上面的import语法会看到没有与我们的代码匹配的语法
import { default } from ‘./module.mjs’;因为这个语法是禁止的。测试代码抛出以下错误:
SyntaxError: Unexpected reserved word
在import { default } from ‘./module.mjs’;这张代码中导出的名字是default在这个上下文中的变量名也是default但是default 是个保留词不能这样使用。解决这个问题很容易
import { default as foo } from ‘./module.mjs’;现在导出的名字是default上下文中的变量名改成foo。换句话说如果我们想为默认导出使用指定的导入语法则必须重命名它。
示例二
// index.js
console.log(index.js);import { sum } from ./helper.js;console.log(sum(1, 2));// help.js
console.log(helper.js);export const sum (x, y) x y;很少有开发者知道的重要细微之处是import被提升了。也就是说当引擎正在解析代码时它们就会上升。在代码运行之前将加载所有依赖项。
打印的日志为
helper.js
index.js
3如果我们希望在导入声明之前执行一些代码请考虑将其移到一个单独的文件中
import ./logs.js;
import { sum } from ./helper.js;console.log(sum(1, 2));// logs.js
console.log(index.js);打印顺序为
index.js
helper.js
3示例三
// index.mjs
import ./module.mjs;
import { num } from ./counter.mjs;console.log(index num , num);
// index num 1;// module.mjs
import { num } from ./counter.mjs;console.log(module num , num);
// module num 1;// counter.mjs
if (!globalThis.num) {globalThis.num 0;
}export const num globalThis.num;模块是单例的
无论我们从同一文件或不同文件导入模块多少次该模块只能执行和加载一次。换句话说只有一个模块实例。
示例四
// index.mjs
import ./module.mjs?param5;// module.mjs
console.log(import.meta.url);根据MDN : 重要的是。元对象将上下文特定的元数据公开给一个JavaScript模块。它包含关于模块的信息。 它返回一个具有URL属性的对象该属性指示URL模块的。这将是获取脚本的URL用于外部脚本,或者是包含文档的文档URL用于内联脚本。 请注意这将包括查询参数和/或#(即跟着?或者#)。
示例五
import myCounter from ./counter;myCounter 1;console.log(myCounter);// counter.js
let counter 5;
export default counter;大多数开发人员忽略的另一个极其重要部分是导入像常量这样变量的时候不能直接修改这个变量值修改的值只在当前文件生效不会更新到模块中为了使代码有效可以导出对象并更改其属性。
示例六
// index.mjs
import foo from ./module.mjs;console.log(typeof foo);
// number// module.mjs
foo 25;export default function foo() {}首先,这个
export default function foo() {}等于
function foo() {}
export { foo as default }也等于
function foo() {}
export default foo但一开始的打印并不是function 而是number。别惊讶继续读。我们下面会继续讲。
现在是时候记住了函数声明会被提升变量的初始化总是在函数/变量声明之后进行的。
引擎处理完模块代码后看起来是这样的
function foo() {}foo 25;export { foo as default }因此打印的结果是number。
示例七 // 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);在大多数情况下export数据是活的。也就是说如果导出值发生了变化则此变化会反映在导入变量中。
但对export default而言并非如此:
export default foo;当使用此语法时不是导出变量而是它的值。我们可以使用以下语法导出默认值
export default ‘hello’;
export default 42;如果我们查看示例一的export语法会发现export default function () {} 在另一个列(Default export)而不是export default foo (Export of values).
这是因为它们的行为不同函数仍然作为实时引用传递 // module.mjsexport { foo };export default function foo() {};setTimeout(() {foo baz;}, 1000);// index.mjsimport defaultFoo, { foo } from ./module.mjs;setTimeout(() {console.log(foo); // bazconsole.log(defaultFoo); //baz}, 2000);让我们再看一下示例一中的export表。
export { foo as default }; 是在Named Export内跟之前示例中不同。但对我们来说唯一重要的是Export of values 部分。因此这意味着当我们以这种方式导出数据时它将是对导入值的活绑定。
示例八 // 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’; 这行代码会抛出一个错误因为import构造必须在脚本的最高层
SyntaxError: Unexpected token ‘{‘
这是一个重要的限制它加上在文件路径中使用变量的限制使得ES6模块成为静态的。这意味着我们不需要执行代码来找出所有模块之间的依赖关系这一点与普通模块不同。
在这个例子中,使用common.js模块找出哪个模块 a 或b 将加载需要运行以下代码 let result;
if (foo()) {result require(a);
} else {result require(b);
}模块的静态特性有很多好处其中一些是:
我们总是知道导入数据的确切结构。这有助于指针在执行代码之前找到错误。异步加载。这是因为模块是静态的在执行模块体之前可以加载导入。支持循环依赖关系。我们将在下一个示例中更详细地探讨这种可能性。有效的捆绑。这个暂时不多讲之后会出一篇文章单独讲解
在ES6中,如果需要有条件地加载模块,可以使用import() 功能式结构。
示例九 // 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));在上面的代码中我们可以看到循环依赖关系index.mjs 引入 module.mjs模块的double 和square方法 而module.mjs又引入了index.mjs模块的calculation方法。
这个代码之所以有效是因为ES6模块本质上支持循环依赖关系。例如如果我们使用cjs来改写这个代码它将不再工作
// 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 functionmodule.exports {double,square
}这是nodejs中的常见问题让我们看看这个代码是如何工作的
开始加载index.js加载会在第一行中加载module.js时中断const helpers require(‘./module.js’);开始加载module.jsconsole.log(actions.calculate(3));这行代码引发错误是因为actions.calculate 没有定义。这是因为JS同步加载模块。index.js 它的导出对象目前是空的。
如果延迟调用导入函数,则index.js 模块将有时间加载
// 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
}正如我们在上一示例中所知道的es6模块支持循环依赖关系因为它们是静态的–模块的依赖关系在代码执行之前就被加载了。
另一个使上述代码工作的东西是函数提升。当calculate 函数被调用时我们还没有到calculate 函数定义的那一行
以下是在模块打包后代码的样子
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);
}如果没有函数提升这段代码就不能正常工作。
如果我们将函数声明改为函数表达式:
export let calculate function(value) {return value % 2 ? square(value) : double(value);
}它会造成以下错误
ReferenceError: Cannot access ‘calculate’ before initialization
示例十
// index.mjs
import { num } from ./module.mjs;console.log(num);export let num 0;num await new Promise((resolve) {setTimeout(() resolve(1), 1000);
});Top-level await这是一个非常有用的功能许多开发人员不知道,也许是因为它是最近在ECMASKIPT2022引入的。
根据 tc39 top-level await proposal : Top-level await 使得模块可以在异步函数中发挥非常大的作用在Top-level await 的情况下ECMAScript模块(esm)可以等待资源导致其他导入模块的部分需要等待完成后才能进行解析。 模块的标准行为是在它导入的所有模块都被加载并执行它们的代码之前模块中的代码不会被执行(参考示例二)。事实上Top-level await 也没有改变这个行为。模块中的代码在导入模块中的所有代码被执行之前才会执行只是现在包括等待模块中所有期待的promise被解决。
console.log(index.js);import { num } from ./module.js;console.log(num , num);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文件中添加timeout像这样
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示例十一 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;根据MDN : 调用import()通常称为动态导入是一个类似函数的表达式。允许异步和动态加载一个ECMAScript模块。它允许规避导入声明的语法刚度并有条件地或根据需要加载模块。 这一功能是在ES2020中引入的。
import(module)返回实现包含模块所有导出的对象的一个Promise。
在import调用之前添加await关键词
if (shouldLoad) {({ num } await import(./module2.mjs));
}在这里我们再次使用Top-level await。
在尝试从全局范围调用异步函数时经常会发生下面这种情况 SyntaxError: await is only valid in async functions
为了解决这个问题我们可以使用
(async () {await [someAsyncFunc]();
})();这段代码看起来就丑而且在异步中使用此模式加载模块时可能会导致错误。例如
// module1.mjs
let num;
(async () {({ num } await import(‘./module2.mjs’));
})();export { num };// module2.mjs
export const num 5;当导入module1.mjs 模块时num的值是什么是来自module2还是undefined这将取决于变量何时被访问 import { num } from ./module1.mjs;console.log(num); // undefined
setTimeout(() console.log(num), 100); // 5当我们导入一个具有Top-level await的模块时module1num的值永远都不会是undefined
let { num } await import(./module2.mjs);export { num };import { num } from ./module1.mjs;console.log(num); // 5示例十二
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));上面的代码会造成一个错误
TypeError: Cannot convert object to primitive value
让我们找出这个错误的来源。
在这段代码中我们使用了一个动态导入我们在前面的示例中已经遇到了它。为了理解代码中的问题我们需要更仔细地研究import()。
变量module1 和module2 不是我们期望那样的值。import() 返回一个具备和命名导入相同字段的已解决的promise
import * as name from moduleNamedefault导出一个键名为default的对象。
所以返回值不是1 和2而是{ default: 1 } 和{ default: 2 }。
为什么我们在用两个对象相乘时会有这样一个奇怪的错误而不是NaN
这是因为返回的对象有一个null 原型。因此它没有toString() 方法用于将对象转换为字符。如果这个对象有Object 原型我们才会看到NaN。
为了修正测试代码我们需要做以下修改
console.log(multiply(module1.default, module2.default));或者 const { default: module1 } await import(./module1.mjs);
const { default: module2 } await import(./module2.mjs);示例十三 // 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’ 会重新导出所有从module模块导出name exports并作为当前文件的name exports如果有重名的情况那么都不是重导出。
所以运行这个代码在控制台里会打印undefined。
另外如果在同样的情况下引入x 如预期的那样,我们会有一个错误:
import { x } from ‘./intermediate.js’;
SyntaxError: The requested module ‘./intermediate.js’ contains conflicting star exports for name ‘x’