理解import、require、export、module.export

48次阅读

共计 3368 个字符,预计需要花费 9 分钟才能阅读完成。

理解 import、require、export、module.export
ES6 的模块设计
模块设计的思想是尽量静态化,使得编译的时候就可以确定模块的一来关系,以及输入和输出的变量。
CommonJS 和 AMD 都只能在运行时确定这些东西,commonJS 模块就是对象,输入时需要查找对象属性
// CommonJS 模块
let {stat, exists, readFile} = require(‘fs’);

// 等同于
let _fs = require(‘fs’);
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
nodeJS 中模块化使用的就是 CommonJS 的规范,实质就是整体加载 fs 模块,生成 fs_对象,在对象上读取属性和方法,这种加载方式是“运行时加载”
ES6 模块
ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。
// ES6 模块
import {stat, exists, readFile} from ‘fs’;
上面代码的实质是从 fs 模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
export 命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。
// profile.js
export var firstName = ‘Michael’;
export var lastName = ‘Jackson’;
export var year = 1958;

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

export {firstName, lastName, year};
export 的语法,对外导出接口, 在接口名与模块内部变量之间,建立了一一对应的关系。
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
import 命令
注意,import 命令具有提升效果,会提升到整个模块的头部,首先执行。
目前阶段,通过 Babel 转码,CommonJS 模块的 require 命令和 ES6 模块的 import 命令,可以写在同一个模块里面,但是最好不要这样做。因为 import 在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。
require(‘core-js/modules/es6.symbol’);
require(‘core-js/modules/es6.promise’);
import React from ‘React’;
export default 命令
export default 命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此 export default 命令只能使用一次。所以,import 命令后面才不用加大括号,因为只可能唯一对应 export default 命令。
本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import {default as foo} from ‘modules’;
// 等同于
// import foo from ‘modules’;

// 正确
export var a = 1;

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

// 错误
export default var a = 1;
CommonJS 规范
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

// 使用
var example = require(‘./example.js’);

console.log(example.x); // 5
console.log(example.addX(1)); // 6
CommonJS 模块的特点

所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。

export var foo = ‘bar’;
setTimeout(() => foo = ‘baz’, 500);
ES6 模块化上面代码输出变量 foo,值为 bar,500 毫秒之后变成 baz。
这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新
module 对象
Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例
module.id 模块的识别符,通常是带有绝对路径的模块文件名。module.filename 模块的文件名,带有绝对路径。module.loaded 返回一个布尔值,表示模块是否已经完成加载。module.parent 返回一个对象,表示调用该模块的模块。module.children 返回一个数组,表示该模块要用到的其他模块。module.exports 表示模块对外输出的值。
module.exports 属性
module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。
为了方便,Node 为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令。
var exports = module.exports;
造成的结果是,在对外输出模块接口时,可以向 exports 对象添加方法。
exports.area = function (r) {
return Math.PI * r * r;
};

exports.circumference = function (r) {
return 2 * Math.PI * r;
};
注意,不能直接将 exports 变量指向一个值,因为这样等于切断了 exports 与 module.exports 的联系。
exports.hello = function() {
return ‘hello’;
};

module.exports = ‘Hello world’;
面代码中,hello 函数是无法对外输出的,因为 module.exports 被重新赋值了。
这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用 exports 输出,只能使用 module.exports 输出。
目录的加载规则
和 node 中模块加载规则一致
通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让 require 方法可以通过这个入口文件,加载整个目录。
在目录中放置一个 package.json 文件,并且将入口文件写入 main 字段。下面是一个例子。
// package.json
{“name” : “some-library”,
“main” : “./lib/some-library.js” }
require 发现参数字符串指向一个目录以后,会自动查看该目录的 package.json 文件,然后加载 main 字段指定的入口文件。如果 package.json 文件没有 main 字段,或者根本就没有 package.json 文件,则会加载该目录下的 index.js 文件或 index.node 文件。
参考
CommonJS 规范 Module 的语法

正文完
 0