Module模块

ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有实际的解决模块化的问题。
module功能就是为了解决这个问题而提出的。

1.export命令

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件,如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出这个变量。如下:

//写法一
export var first="michael";
export var last = "lion";
export var year = "mouse";

//写法二
 var first="michael";
 var last = "lion";
 var year = "mouse";
export {first,last,year};

如此就用export命令对外部输出了3个变量,这两种写法都是等价的,但是应该优先考虑使用写法二,因为这样写就可以在脚本尾部一眼看清输出了哪些变量。

其次,export命令除了输出变量,还可以输出函数或者类。
如:

export function multiply(x,y){
    renturn x*y;
}  //对外输出函数multiply

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
如下:

function v1(){ ... }
function v2(){ ... }
export {
    v1 as streamV1;
    v2 as streamV2;
    v2 as streamLastVersion
};

上面的代码使用as关键字重命名函数V1和V2的对外接口,重命名后,V2可以用不同的名字输出两次。
最后,export命令可以出现在模块的任何位置,只要处于模块顶层即可。

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

export var foo = "bar";
setTimeout(() =>foo = "baz" ,500);

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

2.import

使用export命令定义了模块的对外接口之后,其他js文件就可以通过import命令加载这个模块。
如:

import {first,last,year} from './profile';
function setName(element){
    element.textContent = first + ' ' + last;
}

上面的import就是用来加载profile.js文件,并从中输入变量。
import命令接受一个对象,用{}表示,里面制定要从其他模块导入的变量的名字,{}中的变量名字必须与被导入的模块对外接口的名称相同。
如果想为变量重新取一个名字,要在import中使用as关键字,将输入的变量重新命名。

import {last as surname} from './profile';

注意: import 命令具有提升效果,会提升到整个模块的头部首先执行。
例如:

foo();
import {foo} from 'my_modult';

上面的代码不会报错,因为import优先于foo之前执行。
如果在一个模块中先输入后输出同一个模块,import语句可以和export语句写在一起。
例如:

export {es6 as default} from './someModule';
//相当于:
import {es6} from './someModule';
export default es6;

上面的代码中,import语句可以和export语句写在一起,但是从性能的角度考虑,这样做是不太好的,所以一般采用标准写法。

3.模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,只要使用(*)制定一个对象,这样所有输出值都会加载在这个对象上。
例如,下面的first.js文件,输出两个方法:area和circumference:

//circum.js
export function area(radius){
    return Math.PI*radius*radius;
}
export function circumference(radius){
    return 2*Math.PI*radius;
}
//现在加载这个模块
//main.js
import {area,circum} from './circle';
console.log("圆形面积:"+area(4));
console.log("圆周长:"+circle.circumference(14));

上面的写法是逐一指定加载扥方法,整体加载的写法如下:

import *as circum from './circle';
console.log("圆形面积:"+area(4));
console.log("圆周长:"+circle.circumference(14));

4.module命令

module命令可以代替import语句,达到整体输入模块的作用。

//main.js
module  circum from './circle';
console.log("圆形面积:"+area(4));
console.log("圆周长:"+circle.circumference(14));

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

5.export default命令

为了方便使用者,使其不用阅读文档就能加载模块,就要用到export defalut命令,为模块指定默认输出。
例如:

//export-default.js
export default function (){
    console.log("foo");
}

上面的模块文件export-defalut.js的代码,它的默认输出是一个函数。
在其他模块中输出该模块时,import命令可以为该匿名函数指定任意名字,如下:

import-default.js
import customName from './export-default.js'
customName();    //foo

上面的import命令可以用任意名称指向export-default.js输出的方法,这样就不用必须知道元模块的函数名字了。但是注意的是,这时的import后面不用使用{}括起来。

export default命令用在非匿名函数前也是可以的。
如下:

//export-defalut.js
export default function foo(){
    console.log("foo");
}
//或者:
function foo(){
    console.log("foo");
}
export default foo;

可以这样做的原因,只是因为,foo函数在模块外部是无效的,加载的时候将其视同为匿名函数。

比较:

使用export default命令时,对应的import语句不需要使用大括号,但是只使用export命令时,import是要加括号的。

理由:
export defalut命令用于指定模块的默认输出,当然,一个模块只能有一个默认输出,因此export default命令只能使用一次,所以,其对应的import命令后面才不用加上大括号,因为只可能对应一个方法。

本质上,export default命令就是输出一个叫做default的变量或者方法,然后系统允许你为她起任意的名字,所以下面的写法也是有效的:

//module.js
function add(x,y){
    return x*y;
};
export {add as default};
//app.js
import {default as xxx} from './module';

有了export dedfault命令,输入模块时就非常直观了。
其次,如果想在一条import语句中同时输入默认方法和其他变量,可以如下写法:

import customName,{otherMethod} from './export-default'

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

//export default也可以用来输出类
export default 42
//myclass.js
export default class{...}
//main.js
import myclass from 'myclass'
let o = new myclass();

6.模块的继承

模块之间也是存在继承的,比如:
假设有一个circleplus模块继承了circle模块。

//circleplus.js
export *from 'circle';
export var e=2.718;
export default function(x){
    return Math.exp(x);
}

上面的export *就表示输出circle模块的所有属性和方法。
注意:
export *命令会忽略circle模块的default方法,之后又输出了自定义的e变量和默认方法。
这时也可以将circle的属性或方法改名后再输出。

//circleplus.js
export {area as circleArea}from 'circle';

上面的代码表示,只输出circle模块的area方法,且将其改名为circleArea。
加载上面的模块写法如下:

//main.js
module math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.E));

上面的import exp表示,将circleplus模块的默认方法加载为exp方法。

7.ES6模块加载的实质

首先,ES6模块加载机制与commonJs模块完全不同。
commonJs模块输出的是一个值的复制品,但ES6模块输出的是值的引用。

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

//lib.js
var counter = 3;
function incCounter(){
    counter++;
}
moudle.exports = {
    counter:counter,
    incCounter:incCounter,// 输出内部变量counter和改写这个变量的内部方法incCounter
};
//然后加载该模块:
//main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  //3
incCounter();
console.log(counter);  //3

这段代码就表示,在counter输出后,lib.js模块内部的变化就影响不到counter了。

其次:
ES6模块的运行机制与commonJs不一样,它遇到模块加载命令import时不会去执行模块,只会生成一个动态的只读索引,等到真的要用到这个值的时候,才会到模块中去取值,所以只要原值变了,输入值也是会跟着改变的。
因此,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);  //3

上面的代码表示,ES6模块输入的变量是活的,完全能够反应出模块lib.js内部的变化。

最后,由于ES6输入的模块变量只是一个“符号链接”,所以这个变量是只读的,对它进行重新赋值,是会报错的。
如:

//lib.js
export let obj = {};
//main.js
import {obj} from './lib';
obj.prop = 123;  //ok
obj = {}; //TypeError

上面的代码中,main从lib输入变量obj,可以对obj添加属性,但是重新赋值就会报错。
因为变量obj指向的地址是只读的,不能重新赋值,这样就好比main.js创建了一个名为obj的const变量。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值