module-pattern
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseModule Pattern
模块模式
As your application and codebase grow, it becomes increasingly important to keep your code maintainable and separated. The module pattern allows you to split up your code into smaller, reusable pieces.
Besides being able to split your code into smaller reusable pieces, modules allow you to keep certain values within your file private. Declarations within a module are scoped (encapsulated) to that module, by default. If we don't explicitly export a certain value, that value is not available outside that module. This reduces the risk of name collisions for values declared in other parts of your codebase, since the values are not available on the global scope.
随着应用和代码库规模的增长,保持代码的可维护性和分离性变得愈发重要。模块模式可以帮助你将代码拆分为更小的、可复用的片段。
除了能将代码拆分为更小的可复用片段外,模块还允许你将文件中的某些值设为_私有_。默认情况下,模块内的声明作用域是_封装_在该模块内的。如果我们没有显式导出某个值,该值在模块外部就无法访问。这降低了代码库其他部分声明的值发生命名冲突的风险,因为这些值不会出现在全局作用域中。
When to Use
使用场景
- Use this when you need to organize code into maintainable, encapsulated units
- This is helpful when you want to keep certain values private to a module and avoid global scope pollution
- Use this to enable tree-shaking and reduce bundle sizes
- 当你需要将代码组织为可维护、封装的单元时使用
- 当你希望将某些值设为模块私有,避免污染全局作用域时,这种模式很有帮助
- 使用它来启用 tree-shaking,减小打包体积
Instructions
使用说明
- Use ES2015 /
importsyntax for module definitionsexport - Use named exports for multiple values and default exports for the primary value of a module
- Keep non-exported values private to reduce naming collision risks
- Use dynamic for on-demand module loading to reduce initial bundle size
import()
- 使用ES2015的/
import语法定义模块export - 多个值使用命名导出,模块的主要值使用默认导出
- 将未导出的值设为私有,降低命名冲突风险
- 使用动态实现按需加载模块,减小初始打包体积
import()
Details
详细内容
ES2015 Modules
ES2015模块
ES2015 introduced built-in JavaScript modules. A module is a file containing JavaScript code, with some difference in behavior compared to a normal script.
Let's look at an example of a module called , containing mathematical functions.
math.jsjs
export function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}We have a file containing some simple mathematical logic. We have functions that allow users to add, multiply, subtract, and get the square of values that they pass.
math.jsIn order to make the functions from available to other files, we first have to export them. In order to export code from a module, we can use the keyword. One way of exporting the functions, is by using named exports: we can simply add the keyword in front of the parts that we want to publicly expose.
math.jsexportexportWe can then import the values in another file using the keyword. To let JavaScript know from which module we want to import these functions, we need to add a value and the relative path to the module.
importfromjs
import { add, multiply, subtract, square } from "./math.js";A great benefit of having modules, is that we only have access to the values that we explicitly exported using the keyword. Values that we didn't explicitly export using the keyword, are only available within that module.
exportexportLet's create a value that should only be referenceable within the file, called .
math.jsprivateValuejs
const privateValue = "This is a value private to the module!";
export function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}Notice how we didn't add the keyword in front of . Since we didn't export the variable, we don't have access to this value outside of the module!
exportprivateValueprivateValuemath.jsBy keeping the value private to the module, there is a reduced risk of accidentally polluting the global scope. You don't have to fear that you will accidentally overwrite values created by developers using your module, that may have had the same name as your private value: it prevents naming collisions.
Sometimes, the names of the exports could collide with local values. In this case, we can rename the imported values, by using the keyword.
asjs
import {
add as addValues,
multiply as multiplyValues,
subtract,
square,
} from "./math.js";
function add(...args) {
return args.reduce((acc, cur) => cur + acc);
}
function multiply(...args) {
return args.reduce((acc, cur) => cur * acc);
}
/* From math.js module */
addValues(7, 8);
multiplyValues(8, 9);
subtract(10, 3);
square(3);
/* From index.js file */
add(8, 9, 2, 10);
multiply(8, 9, 2, 10);Besides named exports, you can also use a default export. You can only have one default export per module.
js
export default function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}The difference between named exports and default exports, is the way the value is exported from the module, effectively changing the way we have to import the value.
Previously, we had to use the brackets for our named exports: .
With a default export, we can import the value without the brackets: .
import { module } from 'module'import module from 'module'js
import add, { multiply, subtract, square } from "./math.js";
add(7, 8);
multiply(8, 9);
subtract(10, 3);
square(3);Since JavaScript knows that this value is always the value that was exported by default, we can give the imported default value another name than the name we exported it with.
We can also import all exports from a module, meaning all named exports and the default export, by using an asterisk and giving the name we want to import the module as.
*js
import * as math from "./math.js";
math.default(7, 8);
math.multiply(8, 9);
math.subtract(10, 3);
math.square(3);In this case, we're importing all exports from a module. Be careful when doing this, since you may end up unnecessarily importing values.
Using the only imports all exported values. Values private to the module are still not available in the file that imports the module, unless you explicitly exported them.
*ES2015引入了内置的JavaScript模块。模块是包含JavaScript代码的文件,其行为与普通脚本有所不同。
让我们看一个名为的模块示例,其中包含一些数学函数。
math.jsjs
export function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}我们有一个文件,包含一些简单的数学逻辑。其中的函数支持用户对传入的值进行加法、乘法、减法运算,以及求平方。
math.js为了让中的函数能被其他文件访问,我们首先需要_导出_它们。要从模块中导出代码,可以使用关键字。一种导出函数的方式是使用_命名导出_:只需在我们希望公开的部分前添加关键字即可。
math.jsexportexport然后我们可以在另一个文件中使用关键字导入这些值。为了让JavaScript知道要从哪个模块导入这些函数,我们需要添加值以及模块的相对路径。
importfromjs
import { add, multiply, subtract, square } from "./math.js";模块的一大优势是,我们_只能访问使用关键字显式导出的值_。未使用关键字显式导出的值,只能在该模块内部访问。
exportexport让我们创建一个只能在文件中引用的值,名为。
math.jsprivateValuejs
const privateValue = "This is a value private to the module!";
export function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}注意我们没有在前添加关键字。由于我们没有导出变量,在模块外部无法访问这个值!
privateValueexportprivateValuemath.js通过将值设为模块私有,可以降低意外污染全局作用域的风险。你不必担心会意外覆盖使用你模块的开发者创建的同名值:这避免了命名冲突。
有时,导出的名称可能会与本地值冲突。这种情况下,我们可以使用关键字_重命名_导入的值。
asjs
import {
add as addValues,
multiply as multiplyValues,
subtract,
square,
} from "./math.js";
function add(...args) {
return args.reduce((acc, cur) => cur + acc);
}
function multiply(...args) {
return args.reduce((acc, cur) => cur * acc);
}
/* 来自math.js模块 */
addValues(7, 8);
multiplyValues(8, 9);
subtract(10, 3);
square(3);
/* 来自index.js文件 */
add(8, 9, 2, 10);
multiply(8, 9, 2, 10);除了命名导出,你还可以使用_默认导出_。每个模块只能有一个默认导出。
js
export default function add(x, y) {
return x + y;
}
export function multiply(x) {
return x * 2;
}
export function subtract(x, y) {
return x - y;
}
export function square(x) {
return x * x;
}命名导出和默认导出的区别在于值从模块导出的方式,这实际上改变了我们导入值的方式。
之前,我们必须使用括号来导入命名导出:。
对于默认导出,我们可以_不使用括号_导入值:。
import { module } from 'module'import module from 'module'js
import add, { multiply, subtract, square } from "./math.js";
add(7, 8);
multiply(8, 9);
subtract(10, 3);
square(3);由于JavaScript知道这个值始终是默认导出的值,我们可以给导入的默认值起一个与导出时不同的名称。
我们还可以使用星号导入模块的所有导出值,包括所有命名导出_和_默认导出,并指定我们想要用来引用该模块的名称。
*js
import * as math from "./math.js";
math.default(7, 8);
math.multiply(8, 9);
math.subtract(10, 3);
math.square(3);在这种情况下,我们导入了模块的_所有_导出值。这样做时要小心,因为你可能会不必要地导入一些值。
使用只会导入所有导出的值。模块私有值在导入模块的文件中仍然不可用,除非你显式导出了它们。
*React
React
When building applications with React, you often have to deal with a large amount of components. Instead of writing all of these components in one file, we can separate the components in their own files, essentially creating a module for each component.
We can split components into separate files:
- for the
TodoList.jscomponentList - for the customized
Button.jscomponentButton - for the customized
Input.jscomponentInput
Throughout the app, we don't want to use the default and component, imported from a UI library. Instead, we want to use our custom version of the components, by adding custom styles to it defined in the object in their files. Rather than importing the default and component each time in our application and adding custom styles to it over and over, we can now simply import the default and component once, add styles, and export our custom component.
ButtonInputstylesButtonInputButtonInputNotice how we can have an object called in both and . Since this value is module-scoped, we can reuse the variable name without risking a name collision.
styleButton.jsInput.js使用React构建应用时,你经常需要处理大量组件。与其将所有组件写在一个文件中,我们可以将组件拆分到各自的文件中,本质上为每个组件创建一个模块。
我们可以将组件拆分到不同的文件中:
- 用于
TodoList.js组件List - 用于自定义的
Button.js组件Button - 用于自定义的
Input.js组件Input
在整个应用中,我们不想使用从UI库导入的默认和组件。相反,我们希望使用自定义版本的组件,通过在它们的文件中定义的对象添加自定义样式。我们无需在应用中每次都导入默认的和组件并反复添加自定义样式,现在只需导入一次默认的和组件,添加样式后导出我们的自定义组件即可。
ButtonInputstylesButtonInputButtonInput注意我们可以在和中都有一个名为的对象。由于这个值是_模块作用域_的,我们可以重用变量名而不必担心命名冲突。
Button.jsInput.jsstyleDynamic import
动态导入
When importing all modules on the top of a file, all modules get loaded before the rest of the file. In some cases, we only need to import a module based on a certain condition. With a dynamic import, we can import modules on demand.
js
import("module").then((module) => {
module.default();
module.namedExport();
});
// Or with async/await
(async () => {
const module = await import("module");
module.default();
module.namedExport();
})();By dynamically importing modules, we can reduce the page load time. We only have to load, parse, and compile the code that the user really needs, when the user needs it.
Besides being able to import modules on-demand, the function can receive an expression. It allows us to pass template literals, in order to dynamically load modules based on a given value.
import()js
const res = await import(`../assets/dog${num}.png`);This way, we're not dependent on hard-coded module paths. It adds flexibility to the way you can import modules based on user input, data received from an external source, the result of a function, and so on.
With the module pattern, we can encapsulate parts of our code that should not be publicly exposed. This prevents accidental name collision and global scope pollution, which makes working with multiple dependencies and namespaces less risky. In order to be able to use ES2015 modules in all JavaScript runtimes, a transpiler such as Babel is needed.
当在文件顶部导入所有模块时,所有模块会在文件其余部分加载前加载。在某些情况下,我们只需要根据特定条件导入模块。通过动态导入,我们可以按需导入模块。
js
import("module").then((module) => {
module.default();
module.namedExport();
});
// 或者使用async/await
(async () => {
const module = await import("module");
module.default();
module.namedExport();
})();通过动态导入模块,我们可以减少页面加载时间。我们只需要在用户真正需要的时候加载、解析和编译他们所需的代码。
除了能够按需导入模块外,函数还可以接收表达式。它允许我们传入模板字面量,以便根据给定值动态加载模块。
import()js
const res = await import(`../assets/dog${num}.png`);这样,我们就不必依赖硬编码的模块路径。这为你根据用户输入、从外部源接收的数据、函数结果等动态导入模块提供了灵活性。
使用模块模式,我们可以封装不应公开的代码部分。这可以防止意外的命名冲突和全局作用域污染,从而降低使用多个依赖和命名空间的风险。为了能在所有JavaScript运行时中使用ES2015模块,需要使用Babel这样的转译器。