ES6新特性之模块Module用法详解


Posted in Javascript onApril 01, 2017

本文实例讲述了ES6新特性之模块Module用法。分享给大家供大家参考,具体如下:

一、Module简介

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

历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能。

在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系(这种加载称为“编译时加载”),以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。

浏览器使用ES6模块的语法如下。

<script type="module" src="fs.js"></script>

上面代码在网页中插入一个模块fs.js,由于type属性设为module,所以浏览器知道这是一个ES6模块。

// ES6加载模块
import { stat, exists, readFile } from 'fs';

上面代码通过import去加载一个Module,加载其中的一些方法。

二、import 和 export

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个JS文件,里面使用export命令输出变量。

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

export的写法,除了像上面这样,还有另外一种。(推荐这种,因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。)

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};

export命令除了输出变量,还可以输出函数或类(class)。通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

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

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

// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面代码的import命令,就用于加载profile.js文件,并从中输入变量。import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

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

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

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

三、模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

有一个circle.js文件,它输出两个方法area和circumference。

现在,加载这个模块。

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

四、export default

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

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

上面代码是一个模块文件export-default.js,它的默认输出是一个函数。

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

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

需要注意的是,这时import命令后面,不使用大括号。

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。它后面不能跟变量声明语句。

// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;

五、ES6模块加载的实质

ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。

CommonJS模块输出的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。

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

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3

上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

// lib.js
var counter = 3;
function incCounter() {
 counter++;
}
module.exports = {
 get counter() {
  return counter
 },
 incCounter: incCounter,
};

上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了。

ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

还是举上面的例子。

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

上面代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。

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

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

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

最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。

// mod.js
function C() {
 this.sum = 0;
 this.add = function () {
  this.sum += 1;
 };
 this.show = function () {
  console.log(this.sum);
 };
}
export let c = new C();

上面的脚本mod.js,输出的是一个C的实例。不同的脚本加载这个模块,得到的都是同一个实例。

// x.js
import {c} from './mod';
c.add();
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';

现在执行main.js,输出的是1。这就证明了x.js和y.js加载的都是C的同一个实例。

希望本文所述对大家ECMAScript程序设计有所帮助。

Javascript 相关文章推荐
javascript 处理HTML元素必须避免使用的一种方法
Jul 30 Javascript
Firefox和IE兼容性问题及解决方法总结
Oct 08 Javascript
jQuery实现按钮只点击一次后就取消点击事件绑定的方法
Jun 26 Javascript
AngularJS HTML DOM详解及示例代码
Aug 17 Javascript
JavaScript基于DOM操作实现简单的数学运算功能示例
Jan 16 Javascript
javascript自执行函数
Feb 10 Javascript
Vue filters过滤器的使用方法
Jul 14 Javascript
iscroll.js滚动加载实例详解
Jul 18 Javascript
jquery 给动态生成的标签绑定事件的几种方法总结
Feb 24 jQuery
Angular 利用路由跳转到指定页面的指定位置方法
Aug 31 Javascript
使用js实现单链解决前端队列问题的方法
Feb 03 Javascript
如何在 Vue 中使用 JSX
Feb 14 Vue.js
Vue.js实战之组件之间的数据传递
Apr 01 #Javascript
ES6新特性之解构、参数、模块和记号用法示例
Apr 01 #Javascript
jQuery UI Grid 模态框中的表格实例代码
Apr 01 #jQuery
前端自动化开发之Node.js的环境搭建教程
Apr 01 #Javascript
ES6新特性之数组、Math和扩展操作符用法示例
Apr 01 #Javascript
手机端转换rem适应
Apr 01 #Javascript
ES6新特性之变量和字符串用法示例
Apr 01 #Javascript
You might like
html中select语句读取mysql表中内容
2006/10/09 PHP
PHP中用接口、抽象类、普通基类实现“面向接口编程”与“耦合方法”简述
2011/03/23 PHP
基于PHP读取TXT文件向数据库导入海量数据的方法
2013/04/23 PHP
PHP与Java进行通信的实现方法
2013/10/21 PHP
PHP生成二维码的两个方法和实例
2014/07/01 PHP
php可应用于面包屑导航的递归寻找家谱树实现方法
2015/02/02 PHP
用javascript替换URL中的参数值示例代码
2014/01/27 Javascript
jQuery搜索同辈元素方法
2015/02/10 Javascript
jQuery在ul中显示某个li索引号的方法
2015/03/17 Javascript
自己动手写的jquery分页控件(非常简单实用)
2015/10/28 Javascript
简单实现的JQuery文本框水印插件
2016/06/14 Javascript
jquery dialog获取焦点的方法
2017/02/09 Javascript
JS判断键盘是否按的回车键并触发指定按钮点击操作的方法
2017/02/13 Javascript
AngularJS实现表格的增删改查(仅限前端)
2017/07/04 Javascript
es6+angular1.X+webpack 实现按路由功能打包项目的示例
2017/08/16 Javascript
Vue.set()实现数据动态响应的方法
2018/02/07 Javascript
vue中本地静态图片路径写法
2018/03/06 Javascript
手写Vue弹窗Modal的实现代码
2019/09/11 Javascript
react MPA 多页配置详解
2019/10/18 Javascript
Vue指令实现OutClick的示例
2020/11/16 Javascript
Django中URLconf和include()的协同工作方法
2015/07/20 Python
在Ubuntu系统下安装使用Python的GUI工具wxPython
2016/02/18 Python
Python 实现简单的shell sed替换功能(实例讲解)
2017/09/29 Python
浅谈flask截获所有访问及before/after_request修饰器
2018/01/18 Python
Python continue继续循环用法总结
2018/06/10 Python
python入门教程之基本算术运算符
2020/11/13 Python
HTML5有哪些新特征
2015/12/01 HTML / CSS
优秀应届毕业生推荐信
2014/02/18 职场文书
小班秋游活动方案
2014/02/22 职场文书
优秀本科生求职推荐信
2014/02/24 职场文书
大学优秀班主任事迹材料
2014/05/02 职场文书
同学会感言
2015/07/30 职场文书
五年级语文教学反思
2016/03/03 职场文书
用Python提取PDF表格的方法
2021/04/11 Python
Oracle设置DB、监听和EM开机启动的方法
2021/04/25 Oracle
详解PHP Swoole与TCP三次握手
2021/05/27 PHP