『ES6知识点总结』模块Module

栏目: JavaScript · 发布时间: 5年前

阅读 15

『ES6知识点总结』模块Module

本文字符数8200+,阅读时间约16分钟。

『ES6知识点总结』模块Module

第一节:Module基本概念

【01】过去使用CommonJS和AMD,前者用于服务器,后者用于浏览器。

Module可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

【02】运行时加载和编译时加载

ES6模块的设计思想,是尽量的静态化,在编译时就能确定模块的依赖关系,以及输入和输出的变量。

CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。

这种加载称为“运行时加载”。

整体加载fs模块(即加载fs的所有方法),然后使用时用到3个方法。

let { stat, exists, readFile } = require('fs');
复制代码

ES6模块不是对象,而是通过export和import命令显式指定输出和输入的代码。

这种加载称为“编译时加载”,即ES6可以在编译时就完成模块编译,效率要比CommonJS模块的加载方式高。

实质是从fs模块加载3个方法,其他方法不加载。

import { stat, exists, readFile } from 'fs';
复制代码

【03】(+仅了解)好处:

01、不再需要UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。目前,通过各种 工具 库,其实已经做到了这一点。

02、将来浏览器的新API就能用模块格式提供,不再必要做成全局变量或者navigator对象的属性。

不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

【04】ES6的模块自动采用严格模式,不管有没有在模块头部加上"use strict"。

第二节:export命令

【01】模块功能由两个命令构成:export和import。

export命令用于规定本模块的对外接口。

import命令用于引入其他模块的功能。

【02】一个模块就是一个的文件。

该文件内部的所有变量,外部无法获取。

如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

【03】export可以输出变量、函数、类。

可以使用多个export。

可以export+变量声明赋值一起输出。

可以输出用逗号分隔的变量集合,用花括号括起来。

可以export+函数声明一起输出。

写法1:

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
复制代码

写法2:(推荐使用)

// profile.js
var firstName = 'Michael';var lastName = 'Jackson';var year = 1958;

export {firstName, lastName, year};
复制代码

写法3:

export function multiply (x, y) {return x * y;};
复制代码

【03】可以使用as关键字重命名输出的变量。甚至可以给一个变量取多个名字。

v1 as newName,v1 as newName2

function v1() { ... }function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
复制代码

【04】export命令可以出现在模块的任何位置,只要处于模块最外层(非某个块级作用域中)就可以。

如果处于块级作用域内,就会报错。

import命令也是如此。

function foo () {
  export default 'bar' // SyntaxError
}
foo()
复制代码

【05】export语句输出的值是动态绑定,绑定其所在的模块。

代码输出变量foo,值为bar,500毫秒之后变成baz。

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
复制代码

【06】模块之间也可以继承。

export * from "fileName"

假设有一个circleplus块,继承了circle模块。

export *命令会忽略circle模块的default方法。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {return Math.exp(x);}
复制代码

第三节:import命令

【01】通过import命令加载模块(文件)。

【02】import命令接受用逗号分隔的要从其他模块导入的变量列表,用花括号括起来。

变量名必须与被导入模块的输出的名称相同。

import {item1,item2} from "fileUrl";

// main.js

import {firstName, lastName, year} from './profile';function setName(element) {
  element.textContent = firstName + ' ' + lastName;}
复制代码

【03】使用as关键字,将引用变量重命名。

import { lastName as surname } from './profile';
复制代码

【04】import命令具有提升效果,会提升到整个模块的头部,首先执行。

foo();

import { foo } from 'my_module';//不会报错。
复制代码

【05】如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

但是从可读性考虑,不建议采用这种写法,而应该采用标准写法。

export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
复制代码

【06】另外,ES7有一个 提案 ,简化先输入后输出的写法,拿掉输出时的大括号。

// 提案的写法
export v from "mod";
// 现行的写法
export {v} from "mod";
复制代码

【07】import语句会执行加载的模块。

仅仅执行lodash模块,但是不输入任何值。

import+空格+模块名字符串。

import 'lodash'
复制代码

【08】整体加载

用星号(*)指代为一个对象,所有的引用值都加载在这个对象上面。

import+*+as+新变量名+from+模块地址字符串。

// circle.js

export function area(radius) {return Math.PI * radius * radius;}
export function circumference(radius) {return 2 * Math.PI * radius;}
复制代码

单一加载:

// main.js

import { area, circumference } from './circle';

console.log("圆面积:" + area(4));
console.log("圆周长:" + circumference(14));
复制代码

整体加载:

import * as circle from './circle';

console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));复制代码

【09】module命令

module命令可以取代import命令,达到整体引用模块的作用。

module命令后面跟一个变量,表示输入的模块定义在该变量上。

module +变量名+from+模块地址字符串

// main.js

module circle from './circle';

console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));
复制代码

第四节:export default命令

【01】使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。

但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

(zyx456:好像并不需要,直接整体加载就是了。)

【02】使用export default命令,为模块指定默认输出。

一个模块只能有一个默认输出,因此export deault命令只能使用一次。

所以,import命令后面才不用加大括号,因为只可能对应一个方法。

写法1:

export default 匿名函数。

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

这时就不需要知道原模块输出的函数名。

这时import命令后面,不使用大括号。

// export-default.js
export default function () {  console.log('foo');}复制代码
// import-default.js
import customName from './export-default';customName(); // 'foo' 复制代码

写法2:

export default 函数声明

export default命令用在非匿名函数前,也是可以的。函数的函数名,在模块外部是无效的。加载的时候,视同匿名函数加载。

// export-default.js
export default function foo() {  console.log('foo');}
// 或者写成
function foo() {  console.log('foo');}

export default foo;
复制代码

写法3:

export default value

如果要输出默认的值,只需将值跟在export default之后即可。

export default 42; 写法4:复制代码

export default也可以用来输出类。

// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass'let o = new MyClass();复制代码

【04】下面比较一下默认输出和正常输出。

// 输出
export default function crc32() { // ...}
// 输入
import crc32 from 'crc32';
// 输出
export function crc32() { // ...};
// 输入
import {crc32} from 'crc32';
复制代码

【05】本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。

所以,下面的写法是有效的。

// modules.js
function add(x, y) {return x * y;};
export {add as default};
// app.js
import { default as xxx } from 'modules';复制代码

【06】如果想在一条import语句中,同时输入默认方法和其他变量。

import customName, { otherMethod } from './export-default';
复制代码

【】例子:

import $ from 'jquery';复制代码

第五节:ES6模块加载的实质

【01】Moduel模块加载的机制,与CommonJS模块完全不同。

CommonJS模块输出的是一个值的拷贝,而Module模块输出的是值的引用。

CommonJS模块输入的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

例子。

下面是一个模块文件lib.js。

// lib.js
var counter = 3;function incCounter() {  counter++;}
module.exports = {
  counter: counter,
  incCounter: incCounter,};
复制代码

加载上面的模块。

counter输出以后,lib.js模块内部的变化就影响不到counter了。

// main.js
var counter = require('./lib').counter;var incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3
复制代码

ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。

等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的”符号连接“,原始值变了,输入值也会跟着变。

因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

还是举上面的例子。

// lib.js
export let counter = 3;
export function incCounter() {  counter++;}
// main1.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
复制代码

【】由于ES6输入的模块变量,只是一个”符号连接“,所以这个变量是只读的,对它重新赋值会报错。

因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。

// lib.js
export let obj = {};
// main.js
import { obj } from './lib';

obj.prop = 123; // OK
obj = {}; // TypeError
复制代码

第六节:循环加载

【01】“循环加载”(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。

通常,“循环加载”表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免出现。

但是实际上,这是很难避免的,尤其是依赖关系复杂的大项目,很容易出现a依赖b,b依赖c,c又依赖a这样的情况。这意味着,模块加载机制必须考虑“循环加载”的情况。

// a.js
var b = require('b');
// b.js
var a = require('a');
复制代码

【02】对于JavaScript语言来说,目前最常见的两种模块格式CommonJS和ES6,处理“循环加载”的方法是不一样的,返回的结果也不一样。

【03】CommonJS模块的加载原理

CommonJS的一个模块,就是一个脚本文件。

require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。

{
  id: '...',
  exports: { ... },
  loaded: true,...}
复制代码

上面代码中,该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。

以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。

CommonJS模块的循环加载

CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。

CommonJS的做法是,一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

让我们来看,Node官方文档里面的例子。

脚本文件a.js代码如下。

a.js脚本先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。

exports.done = false;var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
复制代码

再看b.js的代码。

exports.done = false;var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
复制代码

b.js执行到第二行,就会去加载a.js,这时,就发生了“循环加载”。系统会去a.js模块对应对象的exports属性取值,可是因为a.js还没有执行完,从exports属性只能取回已经执行的部分,而不是最后的值。

a.js已经执行的部分,只有一行。

exports.done = false;
复制代码

因此,对于b.js来说,它从a.js只输入一个变量done,值为false。

然后,b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js。

于是,a.js接着往下执行,直到执行完毕。

我们写一个脚本main.js,验证这个过程。

var a = require('./a.js');var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
复制代码

执行main.js,运行结果如下。

$ node main.js

在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
复制代码

上面的代码证明了两件事。一是,在b.js之中,a.js没有执行完毕,只执行了第一行。

二是,main.js执行到第二行时,不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行。

exports.done = true;
复制代码

总之,CommonJS输入的是被输出值的拷贝,不是引用。

ES6模块的循环加载

ES6处理“循环加载”与CommonJS有本质的不同。

ES6模块是动态引用,遇到模块加载命令import时,不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。

ES6模块中的值属于【动态只读引用】。

对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。

循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

zyx456:也就是说2个文件本身都是可以加载的,然后在运行时去找需要用的值,这时也是可以找到的。

不需要加载依赖关系。

例子

// a.js
import {bar} from './b.js';

export function foo() {
    bar();
    console.log('执行完毕');
}

foo();

// b.js
import {foo} from './a.js';

export function bar() {
    if (Math.random() > 0.5) {
        foo();
    }
}
复制代码

按照CommonJS规范,上面的代码是没法执行的。a先加载b,然后b又加载a,这时a还没有任何执行结果,所以输出结果为null,即对于b.js来说,变量foo的值等于null,后面的foo()就会报错。

但是,ES6可以执行上面的代码。

a.js之所以能够执行,原因就在于ES6加载的变量,都是动态引用其所在的模块。只要引用是存在的,代码就能执行。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Probability Theory and Its Applications

An Introduction to Probability Theory and Its Applications

William Feller / Wiley / 1991-1-1 / USD 120.00

Major changes in this edition include the substitution of probabilistic arguments for combinatorial artifices, and the addition of new sections on branching processes, Markov chains, and the De Moivre......一起来看看 《An Introduction to Probability Theory and Its Applications》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具