【译】《Understanding ECMAScript6》- 第八章节-Module

目录

  • 模块是什么
  • 以基础
  • 接口标识符重命名
  • 缺省接口
  • Re-exporting
  • 非绑定import
  • 总结

JavaScript令人困惑并且爱抓住错误的特性有是坐“一切皆共享”的法加载代码。所有文件内定义之满贯代码都共享一个大局作用域,这一点是JavaScript落后于任何编程语言的处(比如Java中的package)。随着web应用变得尤为粗大复杂,“一切都共享”的方法暴露出同样雨后春笋弊端,比如命名冲突、安全性等等。ES6的靶子之一即是釜底抽薪这种问题,增强JavaScript代码组织的有序性。这虽是Module(模块)的来意。

module是什么

Module可以概括了解也加载JavaScript文书之一律种植特有措施。目前,不论是浏览器还是NodeJS,都没落实原生ES6
Module的支持,但是我们得望Module作为一如既往种默认的体制于周边利用。模块化的代码和非模块的代码来以下分别:

  1. 模块化代码强制当严峻模式下实施;
  2. 一个模块最顶层作用域中定义之变量不见面暴露在共享的大局域内;
  3. 一个模块的绝顶层作用域中之this值为undefined;
  4. 不支持html格式的注解语法();
  5. 一个模块必须导出可供应模块以外代码用的接口。

模块化JavaScript文件以及例行的公文一律,都是通过文本编辑器做,使用.js扩展名。唯一的分别是,模块化代码应用新的代码语法。

应用基础

export关键字用来导出一个模块暴露被外部的代码。最简单易行的一律种采取方式是在外变量、函数、class声明语句的前使用export。如下:

// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

// export function
export function sum(num1, num2) {
    return num1 + num1;
}

// export class
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}

// this function is private to the module
function subtract(num1, num2) {
    return num1 - num2;
}

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

// export later
export multiply;

亟待小心以下几点:

  1. 凭使用export与否,声明语句的语法与正常一致;
  2. 被export导出的函数和class必须产生鲜明的类名/函数名为。匿名函数/类非可知采取上述语法导出;
  3. export不仅可在宣称语句前用,也得据此当援前,如上述代码中的multiply;
  4. 从未有过吃明显导出的变量、函数、class被称呼当前模块的民用成员,不可知吃表面代码访问,如上述代码中的substract()函数。

使用export的一个关键限制是,必须于眼前模块的无比顶层作用域使用,否则会丢掉来语法错误。如下:

if (flag) {
    export flag;    // syntax error
}

上述代码中,export在if块级域内利用会废弃来语法错误。export不可知为另外动态的法导出,这样做的益处是可以令JavaScript引擎对导出的模块进行清地管理。因此,export只能够当一个模块的极端顶层作用域内使用。

好几转译器(如Babel.js)可以打破这种限制,开发者可以以另外职务使用export。但是这种模式只在代码被转译为ES5标准时能健康办事,并无支持原生的ES6模片系统。

一旦使用export导出某个模块的职能,便可当其余模块中经过import关键字用它们。import语句包括个别局部:被导入的标识符和夫标识符的源模块。如下:

import { identifier1, identifier2 } from "module";

花括号内之标识符代表的凡由指定模块中导出的变量。关键字from后底模块名代表的凡于导出变量的指定模块。模块名是一个字符串。截止到本书写日期,模块名之写规范还是不最后定稿。

尽管import后的花括号形式以及解构Object类似,但它们独自是导出标识符的列表,并无是解构Object。

运用import从模块中导出的变量类似于以const定义之常量。也就是说,在同样作用域内,不克定义跟之同名的变量,不可知于import之前运用她,也无克还赋值。

本章第一单例中的模块我们命名为“example”,你可利用多主意导出example模块的标识符,最简单易行的艺术如下:

// import just one
import { sum } from "example";

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

sum = 1;        // error

上述代码导出了example模块的sum()函数。不论example模块export多少只接口,开发者可以因不同之运场景import任意个数的接口。上述代码中品尝对sum重新赋值,抛来语法错误,验证了于导入的接口变量不克吃重新赋值这长达规则。

设想import多单接口变量,可以运用以下方法:

// import multiple
import { sum, multiply, magicNumber } from "example";
console.log(sum(1, magicNumber));   // 8
console.log(multiply(1, 2));        // 2

上述代码中导入了example模块的老三个接口变量:sum、multiply以及magicNumber。

若还足以拿一切模块导出为一个独门的对象,其被export的接口变量作为这目标的习性使用。如下:

// import everything
import * as example from "example";
console.log(example.sum(1,
        example.magicNumber));          // 8
console.log(example.multiply(1, 2));    // 2

上述代码中,example模块作为一个完为导入,以一个名为example的对象下,example模块暴露出的sum()、multiply()和magicNumber作为example对象的特性使用。

急需留意的凡,不论是采取import多次导入一个模块,被导入模块内部的代码只会于实施同样不好。如下:

import { sum } from "example";
import { multiply } from "example";
import { magicNumber } from "example";

上述代码中,使用import导入了3次example模块,但是example模块背部之代码钟会被实施同样涂鸦。在第一涂鸦为导入后,example模块于实例化,随后此实例引用将储存在内存中。在此之后,不论import多少坏,甚至被多单不等之模块import,都用使用外存中的example模块实例,而毋庸再执行模块内部的代码。

接口标识符重命名

日常状态下,为了增进代码的易读性,我们一再不直接采用有变量、函数或者class的原名称。ES6的模块规范允许在导出或导入时修改接口标识符的称。

准,在导出某个函数时愿意再度改函数叫做,可以采用as关键字展开如下修改:

function sum(num1, num2) {
    return num1 + num2;
}

export { sum as add };

上述代码中sum()函数在被导出时将接口函数名更改为add(),其他模块于导入此接口函数时要运用add标识符,如下:

import { add } from "example";

同理,在导入某个模块接口函数时,也可以使用as关键字修改标识符名称:

import { add as sum } from "example";
console.log(typeof add);            // "undefined"
console.log(sum(1, 2));             // 3

上述代码在导入接口函数add()时,将标识符名称修改也sum。

导入绑定

欲留意import表达式非常主要之一个细节:import的变量、函数或class并无是简约的援关系,而是创建了一如既往种绑定关系。换句话说,虽然不能够手动修改导入的接口成员,但是可由此自模块的逻辑进行修改。比如:

export var name = "Nicholas";
export function setName(newName) {
    name = newName;
}

当在其他模块中导入name和setName()后,可以透过调用setName()修改name的价值:

import { name, setName } from "example";

console.log(name);       // "Nicholas"
setName("Greg");
console.log(name);       // "Greg"

name = "Nicholas";       // error

调用setName(“Greg”)时,实际上回到了setName的源模块内实施,从而以name的价修改也“Greg”,并且修改后的结果自动映射到了导入name的模块。

缺省接口

模块export的缺省接口是出于default关键字修饰的一个独自的变量、函数或者class。如下:

export default function(num1, num2) {
    return num1 + num2;
}

上述代码是一个突出的export缺省接口。default关键字标明这是一个缺省接口,并且缺乏省接口的函数不待指定具体的函数誉为,因为模块本身即象征正这接口函数。

为可以拿欠省接口重命名,如下:

// equivalent to previous example
function sum(num1, num2) {
    return num1 + num2;
}

export { sum as default };

上述代码等价于前例,as default表明sum函数作为缺乏省接口被导出。

每个模块只能给定义一个缺省接口。尝试定义多单短省接口会引起语法错误。

导入缺省接口的语法与前文提到的导入整个模块的语法类似:

// import the default
import sum from "example";

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

上述代码导入example模块的缺省接口。请小心导入的缺省接口标识符并没有包装在花括号内。这种简洁之语法形式以变成web应用导入已怀对象的常用格式:

import $ from "jquery";

倘若要导入某个模块的缺省接口及非缺省接口,可以当一个表达式中落实。比如某个模块暴露出以下接口:

export let color = "red";

export default function(num1, num2) {
    return num1 + num2;
}

可以通过以下形式导入:

import sum, { color } from "example";

console.log(sum(1, 2));     // 3
console.log(color);         // "red"

上述代码有些许点用小心:

  1. 动逗号分隔缺省接口和非缺省接口;
  2. 非缺省接口包裹于花括号内。

导入缺省接口时方可重命名标识符:

// equivalent to previous example
import { default as sum, color } from "example";

console.log(sum(1, 2));     // 3
console.log(color);         // "red"

上述代码中,缺省接口标识符default为重新命名也sum,连同非缺省接口color一起让包裹于花括号内。

Re-exporting

或多或少场景下,开发者需要用导入的模块再次导出,可以动用以下模式:

import { sum } from "example";
export { sum }

除却,还有同栽更简洁的形式:

export { sum } from "example";

上述代码将example模块的sum接口再次导出。当然,可以采取as在导出时进行再命名:

export { sum as add } from "example";

上述代码导入example模块的sum接口,随后再次命名为add再次导出。

行使通配符*得拿模块作为完整导出:

export * from "example";

使上述代码导出整个example模块时,example模块的缺省接口和非缺省接口全部囊括在内,会潜移默化时模块的导出行为。比如,如果example模块出缺乏省接口,那么尽管不克于此时此刻模块中更定义缺省接口。

非绑定import

好几模块可能只是针对某全局变量进行了改,并未导出任何接口。虽然模块内部的变量、函数和相近并无露于大局意图域内,但连无代表模块内部未能够访问全局域的成员。在某个模块内对停放对象(比如Array或Object)进行了扩大修改,其他模块中为会见被震慑。

遵照,假要现在对Array对象多一个恢弘方法pushAll(),可以当某某模块内开展以下操作:

// module code without exports or imports
Array.prototype.pushAll = function(items) {

    // items must be an array
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }

    // use built-in push() and spread operator
    return this.push(...items);
};

虽说上述模块没有导出/导入外接口,但它们自己是一个符合规范的模块。上述代码可以用作一个模块使用,也可视作同样段普通的脚本。由于模块未导出其他接口,你得用简化的import表达式执行模块代码,而无需创建绑定关系。如下:

import from "example";

let colors = ["red", "green", "blue"];
let items = [];

items.pushAll(colors);

上述代码将example模块导入并实施,Array的恢弘方法pushAll()有效,可以在手上模块的行使。

非绑定的import通常给用来创造polyfill和shim。

翻译注:shim和polyfill是JavaScript应用开发中解决兼容性的方案用语。简单来说就是是行使原来环境之API实现新API。感兴趣的读者可机关查阅有关材料。

总结

ES6引入模块机制的目标是供平等栽代码功能化的包装模式。模块和日常脚本的无比特别之两样在于那顶层作用域内的变量、函数和class并无会见暴露在全局域内,而且this的价为undefined。工作原理的异,也急需平等效全然不同的载入方式支持。

一旦想在模块外部使用本模块的一点职能,必须使用export关键字用其导出。任何变量、函数和class都得给导出。此外,每个模块只能导出一个缺省接口。被导出后,其他模块便好导入部分要么确实个模块。被导入的接口标识符类似const定义的常量,拥有块级域绑定特性。

另外,没有导出任何接口的模块于受外模块导入时莫见面创造绑定关系。