关于 import
和 require
的不同,其实可以理解成 CommonJs 和 ES Module 的区别。这两者都是前端模块化的规范。
我们在 node 里使用的是 CommonJs,在前端页面的时候,用的是 ES Module,这两者的区别,还是很容易混淆的,所以整理一下 CommonJs 和 ES Moudule 的相关知识点,把这里好好的整理一下。
一、CommonJs
1.1 概述
Nodejs 是 CommonJS 规范的主要实践者,在 CommonJs 里每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。CommonJs 提供了四个重要的环境变量为模块化的实现提供支持:module
、exports
、require
、global
。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;
而使用 require 方法来引入并加载模块
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
1.2 CommonJS 模块的特点
- 所有代码都运行在模块作用域,不会污染全局作用域。
- CommonJS 用同步的方式加载模块。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
关于变量:
1、module
对象是 node 里的 Module 构造函数的实例,代表当前模块。具有以下属性:
- module.id 模块的识别符,通常是带有绝对路径的模块文件名。
- module.filename 模块的文件名,带有绝对路径。
- module.loaded 返回一个布尔值,表示模块是否已经完成加载。
- module.parent 返回一个对象,表示调用该模块的模块。
- module.children 返回一个数组,表示该模块要用到的其他模块。
- module.exports 表示模块对外输出的值。
2、exports
NodeJs 为每个模块提供一个 exports
变量,指向 module.exports
在使用的时候,可以直接给 exports
添加属性,就会指向 module.exports
。但是不能给 exports
直接赋值一个变量,这样会切断 exports
和 module.exports
之间的联系。
如果有 exports
和 module.exports
,exports
就会失效,只会输出 module.exports
的,因为 module.exports
被重新赋值了。
3、global
这是一个全局变量声明方式,就可以全局用这个 warning 变量了。
global.warning = true;
4、require
require
命令用于加载模块文件。
require
命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports
对象
加载规则:
- 如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。
- 如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。
- 如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于 Node 的系统安装目录中),或者一个位于各级 node_modules 目录的已安装模块(全局安装或局部安装)。
- 如果参数字符串不以“./“或”/“开头,而且是一个路径,比如 require(‘example-module/path/to/file’),则将先找到 example-module 的位置,然后再以它为参数,找到后续路径。
- 如果指定的模块文件没有发现,Node 会尝试为文件名添加.js、.json、.node 后,再去搜索。.js 件会以文本格式的 JavaScript 脚本文件解析,.json 文件会以 JSON 格式的文本文件解析,.node 文件会以编译后的二进制文件解析。
- 如果想得到 require 命令加载的确切文件名,使用 require.resolve() 方法。
require 函数及其辅助方法主要如下。
- require(): 加载外部模块
- require.resolve():将模块名解析到一个绝对路径
- require.main:指向主模块
- require.cache:指向所有缓存的模块
- require.extensions:根据文件的后缀名,调用不同的执行函数
关于缓存:
在多次加载某个相同的模块时,如果前面的模块已经操作了,后面调用时,拿到就不再是最初时的数据了,就是经过前面操作过后的数据了。
所有缓存的模块保存在 require.cache
之中,如果想删除模块的缓存,可以像下面这样写。
// 删除指定模块的缓存
delete require.cache\[moduleName\];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {delete require.cache\[key\];
})
注意:缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require
命令还是会重新加载该模块。
1.3 模块的加载机制
CommonJS 模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
二、ES Module
2.1 概述
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以 ES6 模块不是对象,而是通过 export
命令显式指定输出的代码,再通过 import
命令输入。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
2.2 ES Module 的特点:
ES6 的模块功能主要由两个命令构成:export
和 import
。export
命令用于规定模块的对外接口。import
命令用于输入 其他模块提供的功能。
- ES6 模块必须用 export 导出
- export 必须与模块内部的变量建立一一对应关系
1、export 命令
- 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。
- export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
2、import 命令
- import 命令输入的变量都是只读的
- import 命令具有提升效果
- import 是静态执行,所以不能使用表达式和变量
- import 语句是 Singleton 模式,如果多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。
3、export default 命令
- export default 就是输出一个叫做 default 的变量或方法
- export default 所以它后面不能跟变量声明语句
三、CommonJs 和 ES Module 的区别
- ES6 模块输出的是值的引用,CommonJS 模块输出的是一个值的拷贝
- ES6 模块是编译时输出接口,CommonJS 模块是运行时加载。
- ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。而 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。
es6 {export :‘可以输出多个,输出方式为 {}’,export default :‘只能输出一个,可以与 export 同时输出,但是不建议这么做’,解析阶段确定对外输出的接口,解析阶段生成接口,模块不是对象,加载的不是对象,可以单独加载其中的某个接口(方法),静态分析,动态引用,输出的是值的引用,值改变,引用也改变,即原来模块中的值改变则该加载的值也改变,this 指向 undefined
}
commonJS {
module.exports = … :‘只能输出一个,且后面的会覆盖上面的’,exports. … :‘可以输出多个’,运行阶段确定接口,运行时才会加载模块,模块就是对象,加载的是该对象,加载的是整个模块,即将所有的接口全部加载进来,输出的是值的拷贝,即原来模块中的值改变不会影响已经加载的该值,this 指向当前模块
}