目录

模块

导出

导入

ES6Module

模块

导出

导入

CommonJs与ES6Module的区别

对模块依赖的处理区别

导入模块值的区别

循环依赖的区别

结尾


模块

CommonJs规范中规定了每一个文件都是一个模块。使用require导入的文件会形成一个属于自身的模块作用域,这样就不会在进行变量以及函数声明时会污染全局作用域。所有的变量和函数都只有模块自身能访问,对外不可见的。

举例:

// foo.js
var name = 'foo';// bar.js
var name = 'bar';
require('./foo.js');
console.log(name); // bar

在bar.js中通过require函数加载foo.js。运行之后输出的结果是‘bar’,这就说明了foo.js中的变量声明并不会影响bar.js。每个文件都拥有自己的作用域。

导出

导出是一个模块对外暴露自身的唯一方式。在CommonJs中,通过module.exports可以导出模块中的内容。

举例:

module.exports = {name: 'foo'
}

CommonJs模块内部会有一个module对象用于存放当前模块的信息,可以理解为在每个模块的最开始中定义了以下对象:

var module = {};
// ...
module.exports = {};

CommonJs也支持另一种导出方式:exports。

exports.name = 'foo'

在实现上,这段代码与上面的module.exports没有不同,其内在机制是将exports指向了module.exports。可以简单的理解为CommonJs在每个模块的开头默认添加了以下代码:

var module = {exports: {}
}
var exports = module.exports;

因此,为export.name赋值相当于在module.exports对象上添加了一个name属性。

Ⅰ、也很容易看出exports与module.exports只是指向同一个对象。所以对exports进行赋值操作,使其指向新的对象,就会失效了。

ingenious:由上面可以知道   即使require 导出后也无法改变模块对象!!

举例:

exports = {name: 'foo'
}

此时name属性并不会被导出。

Ⅱ、另外这两个方法,并不能一起运用。因为使用module.exports赋值就相当于使其指向新的对象。之前的exports赋值都会失效。

导入

CommonJs使用require函数进行导入操作。

举例:

// foo.js
module.exports = {sayname: function () {console.log('foo');}
};// bar.js
var sayname = require('./foo.js').sayname;
sayname(); // foo

在bar.js中导入了foo.js,并调用了它的sayname函数。

当require一个模块时会有两种情况:

  • 模块是第一次被require加载。这时会首先执行该模块,然后导出内容。
  • 模块是曾经被require加载过。这时会直接导出执行得到的结果。

举例:

// foo.js
console.log('running foo.js')
exports.name = 'foo';// bar.js
var firstname = require('./foo').name;
console.log('firstname:', firstname); var lastname = require('./foo').name;
console.log('lastname:', lastname);

输出的是:

running foo.js
fistname:foolastname:foo

从上面代码看有两个地方都require了foo文件,但从结果看,只运行了一遍foo.js。

module对象中有一个loaded属性用于记录该模块是否被加载过。默认值为false,当模块第一次被加载时,会赋值为true,后面再次加载时会检查module.loaded是否为true,如果是,则直接返回结果,并不会再次执行代码。

require函数可以接受表达式,借助这个特性可以动态地制定模块的加载路径。

举例:

var path = ['foo.js', 'bar.js'];
path.forEach(name => {require('./' + name);
})

ES6Module

CommonJs可以说是比较好的解决了模块的问题,但这些都只是由社区提出的规范,并不能算语言本身的特性。

到了2015年,ECMAScript6.0正式定义了JavaScript模块标准。从此 JavaScript 语言才具备了模块这一特性。

模块

将前面CommonJs的例子,用ES6Module方式改写。

// foo.js
export default {sayname: function () {console.log('foo');}
};// bar.js
import foo from './foo.js'
foo.sayname(); // foo

ES6Module也是将每个文件作为一个模块,每个模块拥有自身的作用域,不同的是导入、导出语句。import和export也是作为保留关键字在ES6版本加入了进来,而且ES6Module会自动采用严格模式。

导出

在ES6Module中使用export命令来导出模块。export有两种形式:

  • 命名导出
  • 默认导出

1、命令导出
一个模块可以有多个命名导出,有两种不同的写法:

// 1
export const name = 'foo';// 2
const name = 'foo';
export { name }

可以通过as关键字对变量重命名。

例如:

const name = 'foo';
export { name as nickname }

2、默认导出

默认导出只能有一个:

export default {name: 'foo'
}

可以将export default理解为对外输出一个名为default的变量。

导入

ES6Module使用import语法导入模块。

举例:

// foo.js
export const name = 'foo';
// bar.js
import { name } from './foo'
console.log(name)

加载带有命令导出的模块时,import后面要跟一对大括号来将导入的变量名包裹起来,并且这写变量名需要与导出的变量名完全一致。导入变量的效果相当于在当前作用域下声明了这些变量,并且不可以对齐进行修改。

与命令导出类似,也可以通过as关键字对导入的变量重命名。

举例:

// foo.js
export const name = 'foo';
// bar.js
import { name as nickname } from './foo'
console.log(nickname)// 也可以通过整体导入的方法
import * as name from './foo'
console.log(name.name)

默认导入的例子:

// foo.js
export default {name: 'foo'
}// bar
import name from './foo'
console.log(name.name)

CommonJs与ES6Module的区别

对模块依赖的处理区别

CommonJs与ES6Module最本质的区别在于前者对模块依赖的解决是动态的,而后者是静态的。

  • 动态:模块依赖关系的建立是发生在代码运行阶段;
  • 静态:模块依赖关系的建立是发生在代码编译阶段;

在CommonJs中,当模块A加载模块B时,会执行B的代码,将其module.exports对象作为require函数的返回值进行返回。并且requrie的模块路径可以动态指定,支持传入一个表示式,甚至可以使用if语句判断是否加载某个模块。所以CommonJs模块被执行前,并没有办法确定明确的依赖关系,模块的导入,导出发生在代码的运行阶段。

ES6Module的导入、导出语句都是声明式的,不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域。在ES6代码的编译阶段就可以分析出模块的依赖关系。

导入模块值的区别

在导入一个模块时,对于CommonJs来说是得到了一个导出值的拷贝;而在ES6Module中则是值的动态映射,并且这个映射是只读的。

举例:

// foo
var count = 0;
module.exports = {count: count,add: function (a, b) {count++;return a+b;}
}// bar
var count = require('./foo').count;
var add = require('./foo').add;console.log(count); // 0
add(2,3);
console.log(count); // 0count += 1;
console.log(count); // 1(拷贝值可以更改)

bar中的count是对foo中count的一份值拷贝,因此在调用add函数时,虽然更改了foo中count的值,但是并不会对bar中导入值造成更改。

另一方面拷贝值可以进行更改。

使用ES6Module进行改写

// foo
let count = 0;
const add = function (a,b) {count++;return a+b;
}
export { count, add }// bar
import { count, add } from './foo';
console.log(count); // 0
add(2,3);
console.log(count); // 1(实时反映foo中count的值)count++; // 报错 count is read-only

可以将映射关系理解为一面镜子,从镜子中可以实时观察到原有的事物,但不能操作镜子中的影像。

循环依赖的区别

CommonJs中循环依赖的例子:

// foo
const bar = require('./bar')
console.log('来自bar:', bar);
module.exports = 'foo';// bar
const foo = require('./foo');
console.log('来自foo:', foo);
module.exports = 'bar;// index
require('./foo')

在控制台输出:

来自foo:()
来自bar:bar

首先来梳理执行流程:

  • index文件中引入了foo,此时开始执行foo中的代码;
  • foo第一句导入了bar,这是foo不会继续向下执行,而是进入了bar的内部。
  • 在bar中又引入了foo,这里产生了循环依赖。但并不会再次执行foo,而是直接导出返回值,也就是module.exports。但由于foo未执行完,导出值是默认的空对象,因此当bar执行到console.log时,打印出来的是空对象。
  • bar执行完毕,foo继续向下执行直到流程结束。

使用ES6Module重写上面例子:

// foo
import bar from './bar';
console.log('来自bar:', bar);
export default 'foo'// bar
import foo from './foo'
console.log('来自foo:', foo);
export default 'bar'// index
import foo from './foo'

结果是: 来自foo: undefined 来自bar:bar

在bar中同样无法得到foo正确的导出值,只不过和CommonJS默认导出一个空对象不同,这里获取到的是undefined。

结尾

模块是程序设计的重要概念,希望上述内容能让你了解到前端模块的概念。详细的用法搜索官网或者书籍进行学习。书籍推荐

作者:zhangwinwin
链接: 理解前端模块概念:CommonJs与ES6Module
来源:github

两种模块化语法(module.exports,exports,require export,import)相关推荐

  1. springboot 的两种配置文件语法||配置文件占位符||@Value 读取配置文件及验证处理

    [掌握]springboot 的两种配置文件语法 导入配置文件自动提示的包 创建 Student 类 创建修改 application.properties 配置文件占位符 ${random.int} ...

  2. Python导入模块(包)的两种方式 TypeError: 'module' object is not callable

    Python编程时明明在开始处import了相关包,但是调用函数时就报错如下: TypeError: 'module' object is not callable Python中有两种导入包(模块, ...

  3. oss 部署前端项目报错 Cannot find module ‘@/views/.....‘(require和import区别)

    使用框架:项目使用的是若以框架前后端分离. 我们把项目下载下来后,可以看到若依框架里的这样一段代码: export const loadView = (view) => {if (process ...

  4. CANoe测试的两种方式Test Module 和 Test Unit对比,你常用哪种呢?

  5. ES Module 和 Commonjs | require和import的区别

    1. 语法1.1 ES Module导出:export / export default 导入: import * from 'module'1.2 Commonjs导出:module.exports ...

  6. java 深克隆_Java实现深克隆的两种方式

    序列化和依次克隆各个可变的引用类型都可以实现深克隆,但是序列化的效率并不理想 下面是两种实现深克隆的实例,并且测试类对两种方法进行了对比: 1.重写clone方法使用父类中的clone()方法实现深克 ...

  7. GP两种连接方式性能测试

    GP两种连接方式性能测试 Pivotal java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedSta ...

  8. c++ 显示三维散点图_Python数据可视化,Matplotlib绘制“散点图”的两种方法!

    前言 散点图是Matplotlib常用图形之一,与线形图类似.但是这种图形不再由线段连接,而是由独立的点.圆圈或其他形状构成.那么怎么画散点图呢?Matplotlib给出了两种不同的方法,去画散点图. ...

  9. python 取域名的两种方式

    前提 在处理域名的时候,有时候需要根据需求整理.如取整个域名或者是取顶级域名.故记录两种取域名方式 代码 from urllib.parse import urlparse import tldext ...

最新文章

  1. 【Java】数据结构---二叉树 详解
  2. 从电脑传PDF到IPad的阅读器上
  3. 转载:Linux kernel SPI驱动解释
  4. 移植开源QT软件-SameGame
  5. 【网络协议】TCP中的四大定时器
  6. pipeline python,Python-什么是sklearn.pipeline.Pipeline?
  7. w7系统计算机里没有摄像头,win7系统没有摄像头不能视频的几种原因和解决方法...
  8. python colormap_Matplotlib python更改colormap中的单色
  9. php面向对象的接口,PHP面向对象之接口编程
  10. pycharm远程调试python_使用PyCharm进行Python远程调试
  11. flask(五) cookie 和session
  12. java aes加密 cbc_AES加密,CBC模式,0填充
  13. 毕设题目:Matlab数字信号去噪
  14. vc与三菱PLC编程口通信C语言源代码,三菱PLC通讯与编程实例!
  15. 尚品汇笔记——尚硅谷
  16. 网易镜像 mysql_Docker的常用镜像及使用方式
  17. 概念+实战讲解,一文带你了解RFM模型【kaggle项目实战分享】数据分析
  18. 用MATLAB绘图 等边三角形,信标节点位于等边三角形顶点的MATLAB仿真
  19. 群表示论之Able群的不可约表示
  20. 名帖131 梁诗正 小楷《谢恩折》

热门文章

  1. 信息论小课堂:纠错码(海明码在信息传输编码时,通过巧妙的信道编码保证有了错误能够自动纠错。)
  2. 图像压缩之基于神经网络压缩(BP)
  3. 熵(entropy)的定义
  4. 算一串数字的entropy_什么是熵的计算机科学定义?
  5. java calendar 增加年_java 使用Date类、Calendar类,实现增加日期
  6. JS 调用打印机,打印HTML中的部分内容
  7. funcode游戏实训,java及C/C++,网上整理
  8. 软件的成本与定价如何决策?PMP软件开发规模估算和成本估算方法详解
  9. Rstudio(4.0.5 )安装Rtool40
  10. 分享几个免费书籍的网站