一、基本语法
require用于读取并执行js文件,并返回该模块的exports对象。若无指定模块,会报错。
Node使用CommonJS模块规范,require
属于node的内置命令。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
// example.js
var invisible = function () {
console.log("invisible");
}
//在另一个文件中引入example.js
var example = require('./example.js');
console.log(example);
import用于引入外部模块、其他脚本等的函数、对象或者基本类型。
import属于ES6的命令,ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import
时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";
二、require的静态编译与import的动态编译
静态编译
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports
属性。例如:
require('./example.js');
require('./example.js').message = "hello Lily";
var test = require('./example.js').message
console.log(test);// "hello"
可以看到test的打印结果为:hello。
这是因为即使./example.js模块引用了三次,但是第一次加载该模块时,Node会缓存该模块。以后两次再加载该模块,就直接从缓存取出该模块的module.exports
属性。所以,require(‘./example.js’).message = “hello”;只是往已缓存的模块添加一个属性,当再次取引用require(‘./example.js’)时会发现message属性任然存在,说明example.js模块没有被重新加载,与上一次的引用使用的是同一缓存。
动态编译
ES6模块的动态编译:遇到模块加载命令import
时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。
即,ES6模块是动态引用,不存在缓存值的问题,而且模块里面的变量,绑定其所在的模块。例如:
// base.js
export var foo = 'bar';
setTimeout(() => foo = 'baz change', 500);
// import.js
import { foo } from './baseEs6';
console.log(foo); // bar
setTimeout(() => console.log(foo), 600);// baz changed
上面代码中,base.js
的变量foo
,在刚加载时等于bar
,过了500毫秒,又变为等于‘baz changed’
。依据动态加载的原理,m2.js
正确地读取到了这个变化。
使用babel编译文件import.js
var _baseEs = require('./baseEs6');
console.log(_baseEs.foo);
setTimeout(function () {
return console.log(_baseEs.foo);
}, 600);
我们可以清楚地看到遇到import文件./baseEs6的foo变量时,会先不会去执行模块,而是只生成一个引用_baseEs。等到真的需要用到变量foo时,再到模块里面去取值(_baseEs.foo)。所以在第一次打印foo时输出的是foo的初始值。等到模块中的setTimeout(() => foo = ‘baz change’, 500);执行完毕,foo的值变为’baz change’,此时import.js文件再打印foo就发现foo确实变为’baz change’。
但这只能保证不会出现引用的异常,还是会可能出现模块没有完全加载完就在另一个文件中掉用这个模块的情况,此时会出现undefined。
// baseEs6.js
export var fish;
setTimeout(() => fish = 'fish', 500);
// es6.js
import { fish } from './baseEs6';
console.log(fish); //undefined
babel对于Import命令的转码
babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。
在我们的项目中,可以使用的语言特性有环境区分,对 server 端运行的代码,webpack将src/*下的es6代码 转换成 lib/* 下的符合commonJS规范的es5,然后node执行lib/* 的代码。这里,我们主要来了解一下babel对于Import命令的转码。
代码分析
// es6Test.js
import * as actions from './searchAccount.actions';
import kdbTheme from '../../../common/Dialog/panguTheme.css';
import { createCommonAccount } from './createDialog.actions';
console.log('createCommonAccount###', createCommonAccount);
// babel编译es6Test.js
/* import * as actions from './searchAccount.actions' */
var _searchAccount = require('./searchAccount.actions');
var actions = _interopRequireWildcard(_searchAccount);
/* import kdbTheme from '../../../common/Dialog/panguTheme.css' */
var _panguTheme = require('../../../common/Dialog/panguTheme.css');
var _panguTheme2 = _interopRequireDefault(_panguTheme);
/* import { createCommonAccount } from './createDialog.actions'*/
var _createDialog = require('./createDialog.actions');
console.log('createCommonAccount###', _createDialog.createCommonAccount);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
对于import XXX from ‘a’,a模块中首先会生成一个object来存储这个模块的所有要export出去的变量:Object.defineProperty(exports, “__esModule”, {value: true});这里给模块引用添加__esModule属性是为了方便babel识别当前import的模块是否是已经被babel编译过的模块还是第三方模块。
在经历一系列的属性添加后,import会首先返回这个模块的引用,然后根据具体的import的命令来处理这个引用。
Import A from ‘./module’
如果import的目标是一个default的export,import会先获取目标模块的引用,再调用_interopRequireDefault函数处理这个引用。
_interopRequireDefault的作用就是判断require的模块是否是已经被babel编译过的模块,如果是,则当前require的引用一定存在一个default属性;否则为他加一个default属性,这样便不会调用模块的default为undefined的情况了。
Import * as A from ‘./module’
如果import的目标是一个整个模块所有export出去的属性,import会先获取目标模块的引用,再调用_interopRequireWildcard函数处理这个引用。_interopRequireWildcard首先判断require的模块引用视为已经被babel正确编译过,当判断结果为FALSE且在当前引用不为空的情况下,会对当前引用做浅度克隆,并为其添加一个default属性。防止调用模块的属性时出现undefined的情况了。
Import { A } from ‘./module’
如果import的目标是一个整个模块的普通export出去的属性,import会先获取目标模块的引用,并不立即读取模块对象的A属性,等到真正调用目标属性时再去读取模块对象的A属性。
参考文档