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 相关文章推荐
JS 实现双色表格实现代码
Nov 24 Javascript
基于jquery的仿百度搜索框效果代码
Apr 11 Javascript
JS 在指定数组中随机取出N个不重复的数据
Jun 10 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
Dec 01 Javascript
jQuery操作动态生成的内容的方法
May 28 Javascript
详解Angular中的自定义服务Service、Provider以及Factory
Apr 22 Javascript
解决bootstrap中下拉菜单点击后不关闭的问题
Aug 10 Javascript
JavaScript面向对象程序设计创建对象的方法分析
Aug 13 Javascript
微信小程序自定义组件封装及父子间组件传值的方法
Aug 28 Javascript
js核心基础之构造函数constructor用法实例分析
May 11 Javascript
原生JS无缝滑动轮播图
Oct 22 Javascript
vue-resource 拦截器interceptors使用详解
Jan 18 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
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
wiki-shan写的php在线加密的解密程序
2008/09/07 PHP
PHP初学者最感迷茫的问题小结
2010/03/27 PHP
php class中self,parent,this的区别以及实例介绍
2013/04/24 PHP
解析PHP中如何将数组变量写入文件
2013/06/06 PHP
深入for,while,foreach遍历时间比较的详解
2013/06/08 PHP
php实现的Cookies操作类实例
2014/09/24 PHP
ThinkPHP打开验证码页面显示乱码的解决方法
2014/12/18 PHP
PHP书写格式详解(必看)
2016/05/23 PHP
PHP的Json中文处理解决方案
2016/09/29 PHP
php封装的表单验证类完整实例
2016/10/19 PHP
php给数组赋值的实例方法
2019/09/26 PHP
CL vs ForZe BO5 第五场 2.13
2021/03/10 DOTA
js实现运动logo图片效果及运动元素对象sportBox使用方法
2012/12/25 Javascript
js中把JSON字符串转换成JSON对象最好的方法
2014/03/21 Javascript
javascript中的括号()用法小结
2014/04/14 Javascript
javascript for-in有序遍历json数据并探讨各个浏览器差异
2015/11/30 Javascript
学习 NodeJS 第八天:Socket 通讯实例
2016/12/21 NodeJs
Angular2中如何使用ngx-translate进行国际化
2017/05/21 Javascript
JavaScript面试出现频繁的一些易错点整理
2018/03/29 Javascript
vue中过滤器filter的讲解
2019/01/21 Javascript
8 个有用的JS技巧(推荐)
2019/07/03 Javascript
js实现简单进度条效果
2020/03/25 Javascript
Vant 在vue-cli 4.x中按需加载操作
2020/11/05 Javascript
[19:26]TNC vs EG (BO3)
2018/06/07 DOTA
python将文本转换成图片输出的方法
2015/04/28 Python
python&amp;MongoDB爬取图书馆借阅记录
2016/02/05 Python
分分钟入门python语言
2018/03/20 Python
numpy下的flatten()函数用法详解
2019/05/27 Python
python实现贪吃蛇双人大战
2020/04/18 Python
Python3 pyecharts生成Html文件柱状图及折线图代码实例
2020/09/29 Python
打架检讨书2000字
2014/02/22 职场文书
竞聘书模板
2014/03/31 职场文书
竞选班长演讲稿500字
2014/08/22 职场文书
新生儿未入户证明
2015/06/23 职场文书
2015年秋季校长开学典礼致辞
2015/07/29 职场文书