【译】《Understanding ECMAScript陆》- 第7章-Module

目录

JavaScript令人可疑并且易抓住错误的特征之1是以“1切皆共享”的章程加载代码。全部文件钦点义的万事代码都共享叁个大局功效域,那或多或少是JavaScript落后于其余编制程序语言之处(比方Java中的package)。随着web应用变得越来越变得庞大复杂,“1切皆共享”的秘诀暴表露1多级弊端,举个例子命名争论、安全性等等。ES陆的靶子之1就是减轻这种难题,加强JavaScript代码协会的有序性。那正是Module(模块)的效率。

module是什么

Module能够大约明了为加载JavaScript文书的1种至极措施。近年来,不论是浏览器依旧NodeJS,都未曾得以完成原生ES6Module的支撑,可是大家能够期待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与否,注脚语句的语法与正规1致;
  2. 被export导出的函数和class必须有总之的类名/函数名。无名氏函数/类不可能选用上述语法导出;
  3. export不仅能够在评释语句前使用,也得以用在引用前边,如上述代码中的multiply;
  4. 并未有被明显导出的变量、函数、class被号称当前模块的个体成员,不可能被外表代码访问,如上述代码中的substract()函数。

使用export的2个首要限制是,必须在时下模块的最顶层成效域使用,不然会抛出语法错误。如下:

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

上述代码中,export在if块级域内接纳会抛出语法错误。export不可能以其他动态的方法导出,那样做的补益是足以令JavaScript引擎对导出的模块进行清晰地保管。由此,export只可以在一个模块的最顶层功效域内使用。

好几转译器(如Babel.js)能够打破那种限制,开荒者能够在其他岗位使用export。可是那种方式只在代码被转译为ES5正式时能够符合规律工作,并不帮助原生的ES六模块系统。

假使使用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导入了1遍example模块,可是example模块背部的代码钟会被施行3回。在首先次被导入后,example模块被实例化,随后此实例引用将积累在内部存款和储蓄器中。在此之后,不论import多少次,以至被多个不一样的模块import,都将选用内部存款和储蓄器中的example模块实例,而毋庸再次推行模块内部的代码。

接口标志符重命名

常常情状下,为了压实代码的易读性,大家反复不直接采取有个别变量、函数可能class的固著名称。ES陆的模块标准允许在导出或导入时修改接口标记符的称谓。

诸如,在导出有些函数时梦想改换函数名,能够选用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的值修改为“格雷戈”,并且修改后的结果自动映射到了导入name的模块。

缺省接口

模块export的缺省接口是由default关键字修饰的叁个单独的变量、函数或然class。如下:

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

上述代码是3个优良的export缺省接口。default关键字标明那是一个缺省接口,并且缺省接口的函数不要求内定具体的函数名,因为模块本人就表示着此接口函数。

也足以将缺省接口重命名,如下:

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

export { sum as default };

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

各类模块只好被定义2个缺省接口。尝试定义八个缺省接口会引起语法错误。

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

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

虽说上述模块未有导出/导入任何接口,但它本人是贰个符合规范的模块。上述代码可以视作贰个模块使用,也足以当作1段普通的脚本。由于模块未导出其余接口,你能够运用简化的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。感兴趣的读者可机关查阅有关材料。

总结

ES陆引进模块机制的目标是提供1种代码成效化的卷入情势。模块与普通脚本的最大的两样在于其顶层效能域内的变量、函数和class并不会暴光在全局域内,而且this的值为undefined。专业原理的例外,也须要1套全然分化的载入方式协理。

借使想在模块外部使用本模块的一些成效,必须使用export关键字将其导出。任何变量、函数和class都足以被导出。别的,各种模块只可以导出1个缺省接口。被导出后,其余模块便得以导入部分要么真个模块。被导入的接口标记符类似const定义的常量,具有块级域绑定性格。

其它,未有导出任何接口的模块在被此外模块导入时不会创制绑定关系。