赞
踩
CommonJS规范:https://javascript.ruanyifeng.com/nodejs/module.html
浅谈 JavaScript、ES5、ES6:https://www.cnblogs.com/lovesong/p/4908871.html
理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,Javascript 不是一种模块化编程语言,在 es6 以前,它是不支持 "类"(class),所以也就没有 "模块"(module)了。
Javascript 社区做了很多努力,在现有的运行环境中,实现 "模块" 的效果。
模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
- function m1(){
- //...
- }
- function m2(){
- //...
- }
上面的函数 m1() 和 m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染" 了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面
- var module1 = new Object({
- _count: 0,
- m1: function () {
- //...
- },
- m2: function () {
- //...
- }
- });
上面的函数 m1() 和 m2(),都封装在 module1 对象里。使用的时候,就是调用这个对象的属性:module1.m1();
这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值:module1._ count = 1;
使用 "立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的
- var module = (function () {
- var _count = 0;
- var m1 = function () {
- alert(_count)
- }
- var m2 = function () {
- alert(_count + 1)
- }
-
- return {
- m1: m1,
- m2: m2
- }
- })()
使用上面的写法,外部代码无法读取内部的 _count 变量:console.info(module._count); // undefined
基于以上,就出现了 "模块系统",来实现 "模块的 导入、导出" 功能
require()
函数进行模块引入,使用module.exports
或exports
进行模块导出。require()
时,会立即加载并执行所需的模块。在 Node.js 中,默认使用的是 CommonJS 模块系统,语法为module.exports和exports。
但是正在 Node.js 版本中(12.20.0及以上),可以使用 "type": "module" 配置启用 ES模块系统。
- 注意:由于语法和特性上的差异,CommonJS模块和ES模块之间并不完全兼容,所以在使用时需要注意相应的规范和限制。
- 总结:CommonJS适用于服务器端开发和旧版浏览器环境,而ES模块则适用于现代浏览器环境和某些模块打包工具。
nodejs 启用 ES 模块系统 后,一些 CommonJS 的特性在Node.js中将不再可用,例如,require()函数和 module.exports 语法无法直接使用。你需要更新你的代码以使用ES模块的语法。要在Node.js中使用ES模块系统(ESM),可以按照以下步骤进行操作:
- 确保 Node.js 版本在12.20.0及以上。ESM 在 Node.js 12.20.0版本中开始支持。
在项目根目录下创建一个 package.json 文件,并添加以下内容并保存:
{
"type": "module"
}现在就可以使用 ES模块的语法和特性来编写代码。例如:可以使用 import 和 export 关键字进行模块引入和导出。
import { foo } from './module.js';
console.log(foo);运行ES模块脚本。命令:node script.mjs
启用 ES模块系统后,模块的文件扩展名必须是 .mjs,否则会被视为CommonJS模块。如果仍然希望使用 .js 作为文件扩展名,可以在文件中的 ES模块代码之前使用 .js 作为文件类型指示符,例如:
// script.js import { foo } from './module.js'; console.log(foo);注意,使用 ESM 可能需要一些注意事项:
- ES 模块中的顶级
import
和export
语句只能在模块的顶层使用,不能在条件语句或函数内部使用。- 文件路径必须包含文件扩展名,并且相对路径必须以
./
或../
开头。- Node.js的全局对象不再被默认注入到每个模块中,你需要显式引用它们。例如,使用
import process from 'process'
引入process
对象。通过上述步骤,就可以在Node.js中成功使用ES模块系统。记得在文件名、package.json配置和语法上进行相应的调整。
- 使用 require 函数:const package = require('package-name');
- 使用 ES 模块的 import 语句:import * as package from 'package-name';
使用 ES 模块的 import 语句导入第三方包时,要求文件的扩展名为.mjs,或者在文件所在目录的package.json中设置"type": "module"。此外,使用 import 语句导入的模块是异步加载的,而不是像 require 函数一样是同步加载的。
无论是使用 require 函数还是 import 语句,导入的第三方包需要先通过 Node.js 的包管理工具(如npm或yarn)进行安装,然后才能在代码中导入和使用。例如,通过以下命令安装 package-name包:npm install package-name
"CommonJS模块系统" 和 "ES模块系统" 都是用于在 JavaScript 中组织和管理代码的模块系统,但它们在语法和一些特性上存在一些差异。
import
语句进行模块引入,使用export
关键字进行模块导出。Nodejs 中提供了 exports 和 require 两个对象,
exports 可以理解为 module.exports 的一个 快捷方式。
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个例子。
下面是一个模块文件lib.js
。
- // lib.js
- var counter = 3;
- function incCounter() {
- counter++;
- }
- module.exports = {
- counter: counter,
- incCounter: incCounter,
- };
上面代码输出内部变量counter
和改写这个变量的内部方法incCounter
。
然后,加载上面的模块。
- // main.js
- var counter = require('./lib').counter;
- var incCounter = require('./lib').incCounter;
-
- console.log(counter); // 3
- incCounter();
- console.log(counter); // 3
上面代码说明,counter
输出以后,lib.js
模块内部的变化就影响不到counter
了。
require
命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require
命令,而后者又调用Node的内部命令Module._load
。
- Module._load = function(request, parent, isMain) {
- // 1. 检查 Module._cache,是否缓存之中有指定模块
- // 2. 如果缓存之中没有,就创建一个新的Module实例
- // 3. 将它保存到缓存
- // 4. 使用 module.load() 加载指定的模块文件,
- // 读取文件内容之后,使用 module.compile() 执行文件代码
- // 5. 如果加载/解析过程报错,就从缓存删除该模块
- // 6. 返回该模块的 module.exports
- };
上面的第4步,采用module.compile()
执行指定模块的脚本,逻辑如下。
- Module.prototype._compile = function(content, filename) {
- // 1. 生成一个require函数,指向module.require
- // 2. 加载其他辅助方法到require
- // 3. 将文件内容放到一个函数之中,该函数可调用 require
- // 4. 执行该函数
- };
上面的第1步和第2步,require
函数及其辅助方法主要如下。
require()
: 加载外部模块require.resolve()
:将模块名解析到一个绝对路径require.main
:指向主模块require.cache
:指向所有缓存的模块require.extensions
:根据文件的后缀名,调用不同的执行函数一旦require
函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require
、module
、exports
,以及其他一些参数。
- (function (exports, require, module, __filename, __dirname) {
- // YOUR CODE INJECTED HERE!
- });
Module._compile
方法是同步执行的,所以Module._load
要等它执行完成,才会向用户返回module.exports
的值。
require 命令用于加载文件,后缀名默认为.js。
- var foo = require('foo');
- // 等同于
- var foo = require('foo.js');
根据参数的不同格式,require
命令去不同路径寻找模块文件。
require('/home/marco/foo.js')
将加载/home/marco/foo.js
。require('./circle')
将加载当前脚本同一目录的circle.js
。require('example-module/path/to/file')
,则将先找到example-module
的位置,然后再以它为参数,找到后续路径。.js
、.json
、.node
后,再去搜索。.js
件会以文本格式的JavaScript脚本文件解析,.json
文件会以JSON格式的文本文件解析,.node
文件会以编译后的二进制文件解析。require
命令加载的确切文件名,使用require.resolve()
方法。从文件加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js 会解析 require 方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。
require方法接受以下几种参数的传递:
通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让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
文件。
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports
属性。
require('./example.js');
require('./example.js').message = "hello";
require('./example.js').message
// "hello"
上面代码中,连续三次使用require
命令,加载同一个模块。第二次加载的时候,为输出的对象添加了一个message
属性。但是第三次加载的时候,这个message属性依然存在,这就证明require
命令并没有重新加载模块文件,而是输出了缓存。
如果想要多次执行某个模块,可以让该模块输出一个函数,然后每次require
这个模块的时候,重新执行一下输出的函数。
所有缓存的模块保存在require.cache
之中,如果想删除模块的缓存,可以像下面这样写。
// 删除指定模块的缓存
delete require.cache[moduleName];// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require
命令还是会重新加载该模块。
如果发生模块的循环加载,即A加载B,B又加载A,则B将加载A的不完整版本。
- // a.js
- exports.x = 'a1';
- console.log('a.js ', require('./b.js').x);
- exports.x = 'a2';
-
- // b.js
- exports.x = 'b1';
- console.log('b.js ', require('./a.js').x);
- exports.x = 'b2';
-
- // main.js
- console.log('main.js ', require('./a.js').x);
- console.log('main.js ', require('./b.js').x);
上面代码是三个JavaScript文件。其中,a.js加载了b.js,而b.js又加载a.js。这时,Node返回a.js的不完整版本,所以执行结果如下。
- $ node main.js
- b.js a1
- a.js b2
- main.js a2
- main.js b2
修改main.js,再次加载a.js和b.js。
- // main.js
- console.log('main.js ', require('./a.js').x);
- console.log('main.js ', require('./b.js').x);
- console.log('main.js ', require('./a.js').x);
- console.log('main.js ', require('./b.js').x);
执行上面代码,结果如下。
- $ node main.js
- b.js a1
- a.js b2
- main.js a2
- main.js b2
- main.js a2
- main.js b2
上面代码中,第二次加载a.js和b.js时,会直接从缓存读取exports属性,所以a.js和b.js内部的console.log语句都不会执行了。
require
方法有一个main
属性,可以用来判断模块是直接执行,还是被调用执行。
直接执行的时候(node module.js
),require.main
属性指向模块本身。
- if(require.main === module){
- console.log('本模块执行');
- }
调用执行的时候(通过require
加载该脚本执行),上面的表达式返回false。
在 es6 以前,还没有提出一套官方的规范,从社区和框架推广程度而言,目前通行的 javascript 模块规范有两种:CommonJS 和 AMD
CommonJS规范:https://javascript.ruanyifeng.com/nodejs/module.html
2009年美国程序员 Ryan Dahl 创造了node.js 项目,将 javascript 语言用于服务器端编程。这标志 "Javascript模块化编程" 正式诞生。前端的复杂程度有限,没有模块也是可以的,但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。
node 编程中最重要的思想之一就是模块,而正是这个思想,让JavaScript 的大规模工程成为可能。模块化编程在js界流行,也是基于此,随后在浏览器端,requirejs 和 seajs 之类的工具包也出现了,可以说在对应规范下,require 统治了ES6之前的所有模块化编程,即使现在,在 ES6 module 被完全实现之前,还是这样。
Node 应用由模块组成,采用 CommonJS 模块规范。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
上面代码中,变量x和函数addX,是当前文件example.js私有的,其他文件不可见。
如果想在多个文件分享变量,必须定义为global对象的属性。
global.warning = true;
上面代码的warning变量,可以被所有文件读取。当然,这样写法是不推荐的。
CommonJS 规范规定:每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
下面的代码通过 module.exports 输出变量x和函数addX。
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
require 方法的详细解释参见《Require命令》一节。
CommonJS 模块的特点如下。
Node 内部提供一个 Module 构建函数。所有模块都是 Module的实例。
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// ...
每个模块内部,都有一个module对象,代表当前模块。它有以下属性。
module.id
模块的识别符,通常是带有绝对路径的模块文件名。module.filename
模块的文件名,带有绝对路径。module.loaded
返回一个布尔值,表示模块是否已经完成加载。module.parent
返回一个对象,表示调用该模块的模块。module.children
返回一个数组,表示该模块要用到的其他模块。module.exports
表示模块对外输出的值。下面是一个示例文件,最后一行输出module变量。
// example.js
var jquery = require('jquery');
exports.$ = jquery;
console.log(module);执行这个文件,命令行会输出如下信息。
{ id: '.',
exports: { '$': [Function] },
parent: null,
filename: '/path/to/example.js',
loaded: false,
children:
[ { id: '/path/to/node_modules/jquery/dist/jquery.js',
exports: [Function],
parent: [Circular],
filename: '/path/to/node_modules/jquery/dist/jquery.js',
loaded: true,
children: [],
paths: [Object] } ],
paths:
[ '/home/user/deleted/node_modules',
'/home/user/node_modules',
'/home/node_modules',
'/node_modules' ]
}如果在命令行下调用某个模块,比如
node something.js
,那么module.parent
就是null
。如果是在脚本之中调用,比如require('./something.js')
,那么module.parent
就是调用它的模块。利用这一点,可以判断当前模块是否为入口脚本。if (!module.parent) {
// ran with `node something.js`
app.listen(8088, function() {
console.log('app listening on port 8088');
})
} else {
// used with `require('/.something.js')`
module.exports = app;
}
module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports 变量。
- var EventEmitter = require('events').EventEmitter;
- module.exports = new EventEmitter();
-
- setTimeout(function() {
- module.exports.emit('ready');
- }, 1000);
-
- 上面模块会在加载后1秒后,发出ready事件。其他文件监听该事件,可以写成下面这样。
-
- var a = require('./a');
- a.on('ready', function() {
- console.log('module a is ready');
- });
示例:
- // module.js
- function sayHello() {
- console.log("Hello!");
- }
-
- module.exports = {
- sayHello: sayHello
- };
exports 变量:为了方便,Node为每个模块提供一个exports变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令:var exports = module.exports; 这样造成的结果是,在对外输出模块接口时,可以向 exports 对象添加方法。
- exports.sayHello = function() {
- console.log("Hello!");
- };
-
- exports.area = function (r) {
- return Math.PI * r * r;
- };
-
- exports.circumference = function (r) {
- return 2 * Math.PI * r;
- };
注意:不能直接将 exports 变量指向一个值,因为这样等于切断了exports与module.exports 的联系。
// 错误示例 exports = { sayHello: function() { console.log("Hello!"); } };// 下面的写法是无效的,因为 exports 不再指向 module.exports了。
exports = function(x) {console.log(x)};
// 下面的写法也是无效的。
exports.hello = function() {
return 'hello';
};
module.exports = 'Hello world';
上面代码中,hello 函数是无法对外输出的,因为 module.exports 被重新赋值了。
这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用 exports 输出,只能使用 module.exports 输出。
module.exports = function (x){ console.log(x);};
如果觉得 exports 与 module.exports 之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。
Node 使用 CommonJS 模块规范,内置的全局方法 require 命令用于加载模块文件。
require 命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。
// example.js
var invisible = function () {
console.log("invisible");
}
exports.message = "hi";
exports.say = function () {
console.log(message);
}
运行下面的命令,可以输出exports对象。
var example = require('./example.js');
example
// {
// message: "hi",
// say: [Function]
// }
如果模块输出的是一个函数,那就不能定义在exports对象上面,而要定义在 module.exports变量上面。
module.exports = function () {
console.log("hello world")
}require('./example2.js')()
上面代码中,require命令调用自身,等于是执行module.exports,因此会输出 hello world。
正是由于 CommonJS 使用的 require 方式的推动,才有了后面的 AMD、CMD 也采用的 require 方式来引用模块的风格
在 ECMAScript 6 中又新增了语法 export
和 export default
:
- export default function () { }
- export const site = 'https://tasaid.com'
- export const name = 'linkFly'
到这里画风还比较正常,而大名鼎鼎的 JavaScript 转码编译器 babel 针对 ECMAScript 6 新增的 export default
语法,搞了个 babel-plugin-transform-es2015-modules-commonjs 的转换插件,用于将 ECMAScript 6 转码为 CommonJs 规范的语法:
源码:export default 42;
编译后:
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
-
- exports.default = 42;
到这里,我们看到有三种 export
默认值的语法:
- module.exports = function () {} // commonjs
- exports.default = function () {} // babel 转码
- export default function () {} // es6
关于 TypeScript 中不同 import 的含义,最典型的就是下面的 import 语法:
import * as xx from 'xx' // commonjs 模块
import xx from 'xx' // es6 模块
import xx = require('xx') // commonjs 模块,类型声明为 export = xx
const xx = require('xx') // 没有类型声明,默认导入 any 类型
import xx = require('xx') 是用来导入 commonjs 模块的库,特殊的地方在于这个库的类型声明是 export = xxx 这种方式导出的:
语法
- import defaultExport from "module-name";
- import * as name from "module-name";
- import { export } from "module-name";
- import { export as alias } from "module-name";
- import { export1 , export2 } from "module-name";
- import { foo , bar } from "module-name/path/to/specific/un-exported/file";
- import { export1 , export2 as alias2 , [...] } from "module-name";
- import defaultExport, { export [ , [...] ] } from "module-name";
- import defaultExport, * as name from "module-name";
- import "module-name";
-
- var promise = import("module-name"); //这是一个处于第三阶段的提案。
defaultExport
导入模块的默认导出接口的引用名。module-name
要导入的模块。通常是包含目标模块的.js
文件的相对或绝对路径名,可以不包括.js
扩展名。某些特定的打包工具可能允许或需要使用扩展或依赖文件,它会检查比对你的运行环境。只允许单引号和双引号的字符串。name
导入模块对象整体的别名,在引用导入模块时,它将作为一个命名空间来使用。export, exportN
被导入模块的导出接口的名称。alias, aliasN
将引用指定的导入的名称。导入整个模块的内容
import * as myModule from '/modules/my-module.js';
myModule.doAllTheAmazingThings();
导入单个接口
import {myExport} from 'xxx.js';
导入多个接口
import {foo, bar} from 'xxx.js';
例如:
// module.js
const firstName = 'John';
const lastName = 'Doe';
export { firstName, lastName };// main.js
import { firstName, lastName } from './module.js';
console.log(firstName); // 输出: John
console.log(lastName); // 输出: Doe
导入带有别名的接口 ( import {x as y} from 'zzz.js'; )
import {reallyLongModuleExportName as shortName} from '/modules/my-module.js';
export as 通过export关键字进行重命名导出时,使用as关键字指定新的名称
例如:
// module.js
const name = 'module';
export { name as moduleName };// main.js
import { moduleName } from './module.js';
console.log(moduleName); // 输出: module
export all 通过export *关键字导出所有模块时,可以将一个模块中的所有导出对象都导入到当前模块。
例如:
// module.js
export const firstName = 'John';
export const lastName = 'Doe';// main.js
import * as module from './module.js';
console.log(module.firstName); // 输出: John
console.log(module.lastName); // 输出: Doe
导入时重命名多个接口
使用别名导入模块的多个接口。
import {
reallyReallyLongModuleMemberName as shortName,
anotherLongModuleName as short
} from '/modules/my-module.js';
只运行全局代码,而不导入任何值:import 'xxx.js';
不导入模块中的任何内容(接口)。 这将运行模块中的全局代码, 但实际上不导入任何值。
import '/modules/my-module.js';
export default
通过 export default 导出模块时,可以直接指定文件或模块的默认导出值。
例如:// module.js
const name = 'module';
export default name;// main.js
import moduleName from './module.js';
console.log(moduleName); // 输出: module引入模块可能有一个 default export(无论它是对象,函数,类等)可用。然后可以使用 import 语句来导入这样的默认接口。
最简单的用法是直接导入默认值:import myDefault from '/modules/my-module.js';
default、命名空间导入、命名导入 联合使用
也可以同时将 default 语法与上述用法(命名空间导入或命名导入)一起使用。在这种情况下,default 导入必须首先声明。 例如:
import myDefault, * as myModule from '/modules/my-module.js';
// myModule used as a namespace
或者
import myDefault, {foo, bar} from '/modules/my-module.js';
// specific, named imports当用动态导入的方式导入默认导出时,其工作方式有所不同。你需要从返回的对象中解构并重命名 "default" 键。
(async () => {
if (somethingIsTrue) {
const {default: myDefault, foo, bar} = await import('/modules/my-module.js');
}
})();
nodejs 示例:
- const module_parse = require("@babel/parser")
- const module_generator = require("@babel/generator").default
- const module_fs = require('fs')
-
- const js_code = `
- const a = 3;
- let string = "hello";
- for (let i = 0; i < a; i++) {
- string += "world";
- }
- console.log("string", string);`;
-
- // const js_code = module_fs.readFileSync("code1.js","utf-8");
- let ast = module_parse.parse(js_code);
-
- // 返回值中的 code 属性赋值给变量 output。
- const { code: output } = module_generator(ast);
- console.log(output);

动态 import
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。下面的是你可能会需要动态导入的场景:
请不要滥用动态导入(只有在必要情况下采用)。静态框架能更好的初始化依赖,而且更有利于静态分析工具和 tree shaking 发挥作用
关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise
- import('/modules/my-module.js')
- .then((module) => {
- // Do something with the module.
- });
这种使用方式也支持 await
关键字。
let module = await import('/modules/my-module.js');
使用 @符号 进行导入
在JavaScript中,使用@符号进行导入时,通常是用于指定模块的命名空间或别名。
一种常见的情况是,在使用一些JavaScript框架或库时,它们可能会使用@符号作为包的前缀来表示该包的特定命名空间。
例如,import { func } from '@mylibrary/module' 中
- @mylibrary 表示从名为 mylibrary 的包中导入module模块,并且@mylibrary是该包的命名空间。
另外一种情况是,在一些构建工具(例如Webpack)或模块解析器中,@符号可以用于定义模块的别名或路径映射。通过配置,我们可以将@符号映射到特定的文件夹路径,以简化长路径的书写。
例如,import module from '@/path/to/module' 中
- @表示某个指定的目录,例如项目的根目录或者其他约定的路径。
需要注意的是,@符号的具体含义和使用方式取决于项目的配置和约定,并没有固定的规定。因此,在具体的应用场景中,可以查看相关文档或配置文件以了解@符号的使用方式和含义。
以上不同的导出方式可以组合使用,以满足不同场景下的需求。
示例:标准导入
下面的代码将会演示如何从辅助模块导入以协助处理 AJAX JSON 请求。
模块:file.js
- function getJSON(url, callback) {
- let xhr = new XMLHttpRequest();
- xhr.onload = function () {
- callback(this.responseText)
- };
- xhr.open('GET', url, true);
- xhr.send();
- }
-
- export function getUsefulContents(url, callback) {
- getJSON(url, data => callback(JSON.parse(data)));
- }
主程序:main.js
- import { getUsefulContents } from '/modules/file.js';
-
- getUsefulContents('http://www.example.com',
- data => { doSomethingUseful(data); });
示例:动态导入
此示例展示了如何基于用户操作去加载功能模块到页面上,在例子中通过点击按钮,然后会调用模块内的函数。当然这不是能实现这个功能的唯一方式,import()
函数也可以支持await
。
- const main = document.querySelector("main");
- for (const link of document.querySelectorAll("nav > a")) {
- link.addEventListener("click", e => {
- e.preventDefault();
-
- import('/modules/my-module.js')
- .then(module => {
- module.loadPageInto(main);
- })
- .catch(err => {
- main.textContent = err.message;
- });
- });
- }
import 语法声明用于从已导出的模块、脚本中,导入函数、对象、指定文件(或模块)的原始值。
import 模块导入与 export 模块导出功能相对应,也存在两种模块导入方式:
- 命名式导入(名称导入)
- 默认导入(定义式导入)。
import 的语法跟 require 不同,而且 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";
- name-从将要导入模块中收到的导出值的名称
- member, memberN-从导出模块,导入指定名称的多个成员
- defaultMember-从导出模块,导入默认导出成员
- alias, aliasN-别名,对指定导入成员进行的重命名
- module-name-要导入的模块。是一个文件名
- as-重命名导入成员名称(“标识符”)
- from-从已经存在的模块、脚本文件等导入
命名式导入
我们可以通过指定名称,就是将这些成员插入到当作用域中。导出时,可以导入单个成员或多个成员:
注意,花括号里面的变量与 export 后面的变量一一对应
import {myMember} from "my-module"; import {foo, bar} from "my-module";通过 * 符号,我们可以导入模块中的全部属性和方法。当导入模块全部导出内容时,就是将导出模块("my-module.js")所有的导出绑定内容,插入到当前模块(’myModule’)的作用域中:
import * as myModule from "my-module";
导入模块对象时,也可以使用as对导入成员重命名,以方便在当前模块内使用:
import {reallyReallyLongModuleMemberName as shortName} from "my-module";
导入多个成员时,同样可以使用别名:
import {reallyReallyLongModuleMemberName as shortName, anotherLongModuleName as short} from "my-module";
导入一个模块,但不进行任何绑定:import "my-module";
默认导入
在模块导出时,可能会存在默认导出。同样的,在导入时可以使用 import 指令导出这些默认值。
直接导入默认值:import myDefault from "my-module";
也可以在命名空间导入和名称导入中,同时使用默认导入:
import myDefault, * as myModule from "my-module"; // myModule 做为命名空间使用 //或 import myDefault, {foo, bar} from "my-module"; // 指定成员导入import 使用示例
// --file.js-- function getJSON(url, callback) { let xhr = new XMLHttpRequest(); xhr.onload = function () { callback(this.responseText) }; xhr.open("GET", url, true); xhr.send(); } export function getUsefulContents(url, callback) { getJSON(url, data => callback(JSON.parse(data))); } // --main.js-- import {getUsefulContents} from "file"; getUsefulContents("http://itbilu.com", data => { doSomethingUseful(data); });default 关键字
// d.js export default function () { } // 等效于: function a() { }; export {a as default};在 import 的时候,可以这样用:
import a from './d'; // 等效于,或者说就是下面这种写法的简写,是同一个意思 import {default as a} from './d';这个语法糖的好处就是 import 的时候,可以省去花括号{}。
简单的说,如果 import 的时候,你发现某个变量没有花括号括起来(没有*号),那么你在脑海中应该把它还原成有花括号的 as 语法。
所以,下面这种写法你也应该理解了吧:import $,{each,map} from 'jquery';
import 后面第一个 $ 是 {defalut as $} 的替代写法。
as 关键字
as 简单的说就是取一个别名,export 中可以用,import 中其实可以用:
// a.js var a = function () { }; export {a as fun}; // b.js import {fun as a} from './a'; a();上面这段代码,export 的时候,对外提供的接口是 fun,它是 a.js 内部 a 这个函数的别名,但是在模块外面,认不到 a,只能认到 fun。
import 中的 as 就很简单,就是你在使用模块里面的方法的时候,给这个方法取一个别名,好在当前的文件里面使用。之所以是这样,是因为有的时候不同的两个模块可能通过相同的接口,比如有一个 c.js 也通过了 fun 这个接口:
// c.js export function fun() {};如果在b.js中同时使用a和c这两个模块,就必须想办法解决接口重名的问题,as就解决了。
CommonJS 规范不适用于浏览器环境。下面代码如果在浏览器中运行,会有一个很大的问题
- var math = require('math');
- math.add( 2,3); // 5
第二行 math.add(2, 3),在第一行 require("math") 之后运行,因此必须等 math.js 加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。因此,浏览器端的模块,不能采用 "同步加载"(synchronous),只能采用 "异步加载"(asynchronous)。这就是AMD规范诞生的背景。
AMD是 "Asynchronous Module Definition" 的缩写,意思就是 "异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
示例:
- define(['package/lib'], function(lib){
- function foo(){
- lib.log('hello world!');
- }
-
- return {
- foo: foo
- };
- });
用法:define(id?, dependencies?, factory)
如果一个模块不依赖其他模块,那么可以直接定义在 define() 函数之中。示例:
- // math.js
- define(function () {
- var add = function (x, y) {
- return x + y;
- };
- return {
- add: add
- };
- });
如果这个模块还依赖其他模块,那么 define() 函数的第一个参数,必须是一个数组,指明该模块的依赖性。
- define(['Lib'], function (Lib) {
- function foo() {
- Lib.doSomething();
- }
-
- return {
- foo: foo
- };
- });
上面代码中,当 require() 函数加载上面这个模块的时候,就会先加载 Lib.js 文件。
但是不同于 CommonJS,它要求两个参数:require([module], callback);
如果将前面的代码改写成AMD形式,就是下面这样:
- require(['math'], function (math) {
- math.add(2, 3);
- });
math.add() 与 math 模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD 比较适合浏览器环境。
目前,主要有两个 Javascript 库实现了AMD规范:require.js 和 curl.js。
AMD 规范允许输出的模块兼容CommonJS规范,这时define
方法需要写成下面这样:
- define(function (require, exports, module){
- var someModule = require("someModule");
- var anotherModule = require("anotherModule");
-
- someModule.doTehAwesome();
- anotherModule.doMoarAwesome();
-
- exports.asplode = function (){
- someModule.doTehAwesome();
- anotherModule.doMoarAwesome();
- };
- });
CMD (Common Module Definition), 是 seajs 推崇的规范,CMD 则是依赖就近,用的时候再 require。它写起来是这样的:
- define(function (require, exports, module) {
- var clock = require('clock');
- clock.start();
- });
CMD 与 AMD一样,也是采用特定的 define() 函数来定义,用 require 方式来引用模块:define(id?, dependencies?, factory)
- define('hello', ['jquery'], function (require, exports, module) {
- // 模块代码
- });
如果一个模块不依赖其他模块,那么可以直接定义在 define() 函数之中。
- define(function (require, exports, module) {
- // 模块代码
- });
注意:带 id 和 dependencies 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。
AMD 和 CMD 最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
- ES6 标准发布后,module 成为标准,标准使用是以 export 指令导出接口,以 import 引入模块,
- 但是在 node 模块中,依然采用的是 CommonJS 规范,使用 require 引入模块,使用 module.exports 导出接口。
export 语法声明用于导出函数、对象、指定文件(或模块)的原始值。
注意:
- 在 node 中使用的是 exports ,不是 export。不要混淆了
- ES6 标准发布后,module 成为标准,标准使用是以 export 导出接口,以 import 引入模块,
export 有两种模块导出方式:
- export { name1, name2, …, nameN };
- export { variable1 as name1, variable2 as name2, …, nameN };
- export let name1, name2, …, nameN; // also var
- export let name1 = …, name2 = …, …, nameN; // also var, const
-
- export default expression;
- export default function (…) { … } // also class, function*
- export default function name1(…) { … } // also class, function*
- export { name1 as default, … };
-
- export * from …;
- export { name1, name2, …, nameN } from …;
- export { import1 as name1, import2 as name2, …, nameN } from …;
模块可以通过 export 前缀关键词声明导出对象,导出对象可以是多个。这些导出对象用名称进行区分,称之为命名式导出。
- export { myFunction }; // 导出一个已定义的函数
- export const foo = Math.sqrt(2); // 导出一个常量
还可以使用 * 和 from 关键字来实现的模块的继承:
export * from 'article';
模块导出时,可以指定模块的导出成员。导出成员可以认为是类中的公有对象,而非导出成员可以认为是类中的私有对象:
- var name = 'IT笔录';
- var domain = 'http://itbilu.com';
-
- export {name, domain}; // 相当于导出 {name:name,domain:domain}
模块导出时,我们可以使用 as 关键字对导出成员进行重命名:
- var name = 'IT笔录';
- var domain = 'http://itbilu.com';
-
- export {name as siteName, domain};
注意,下面的语法有严重错误的情况:
- // 错误演示
- export 1; // 绝对不可以
-
- var a = 100;
- export a;
export 在导出接口的时候,必须与模块内部的变量具有一一对应的关系。直接导出1没有任何意义,也不可能在 import 的时候有一个变量与之对应
export a
虽然看上去成立,但是 a 的值是一个数字,根本无法完成解构,因此必须写成 export {a}
的形式。即使 a 被赋值为一个function,也是不允许的。而且,大部分风格都建议,模块中最好在末尾用一个 export 导出所有的接口,例如:
export { fun as default,a,b,c};
默认导出也被称做定义式导出。命名式导出可以导出多个值,但在在 import 引用时,也要使用相同的名称来引用相应的值。而默认导出每个导出只有一个单一值,这个输出可以是一个函数、类或其它类型的值,这样在模块 import 导入时也会很容易引用。
- export default function() {}; // 可以导出一个函数
- export default class(){}; // 也可以出一个类
默认导出可以理解为另一种形式的命名导出,默认导出可以认为是使用了 default 名称的命名导出。
下面两种导出方式是等价的:
- const D = 123;
-
- export default D;
- export { D as default };
使用名称导出一个模块时:
- // "my-module.js" 模块
- export function cube(x) {
- return x * x * x;
- }
-
- const foo = Math.PI + Math.SQRT2;
- export {foo};
在另一个模块(脚本文件)中,我们可以像下面这样引用:
- import {cube, foo} from 'my-module';
-
- console.log(cube(3)); // 27
- console.log(foo); // 4.555806215962888
使用默认导出一个模块时:
- // "my-module.js"模块
- export default function (x) {
- return x * x * x;
- }
在另一个模块(脚本文件)中,我们可以像下面这样引用,相对名称导出来说使用更为简单:
- // 引用 "my-module.js"模块
- import cube from 'my-module';
-
- console.log(cube(3)); // 27
TypeScript 中不同 import 的含义
JavaScript一种 动态类型、弱类型、基于原型 的客户端脚本语言,用来给HTML网页增加动态功能。
PS:新对象指函数,模版对象是实例对象,实例对象是不能继承原型的,函数才可以的。
JavaScript 是基于原型的客户端脚本语言,用来给 HTML 网页增加动态功能。JavaScript 由三部分组成:
ES 全称就是 ECMAScript,作为 JavaScript 语言的核心,规定了语言的组成部分:语法、类型、语句、关键字、保留字、操作符、对象。
作为 ECMAScript第五个版本(第四版因为过于复杂废弃了),浏览器支持情况可看第一副图,增加特性如下。
ECMAScript6 在保证向下兼容的前提下,提供大量新特性。
ES6 教程:https://wangdoc.com/es6/
:https://www.bookstack.cn/read/es6-3rd/sidebar.md
:https://es6.ruanyifeng.com/
:https://www.runoob.com/w3cnote/es6-tutorial.html
ES6从入门到精通系列:https://www.bilibili.com/video/BV1ay4y1r78B/
- var obj = {
- // __proto__
- __proto__: theProtoObj,
- // Shorthand for ‘handler: handler’
- handler,
- // Method definitions
- toString() {
- // Super calls
- return "d " + super.toString();
- },
- // Computed (dynamic) property names
- [ 'prop_' + (() => 42)() ]: 42
- };
- let singer = { first: "Bob", last: "Dylan" };
- let { first: f, last: l } = singer; // 相当于 f = "Bob", l = "Dylan"
- let [all, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec("2015-10-25");
- let [x, y] = [1, 2, 3]; // x = 1, y = 2
- //Default
- function findArtist(name='lu', age='26') {
- ...
- }
-
- //Rest
- function f(x, ...y) {
- // y is an Array
- return x * y.length;
- }
- f(3, "hello", true) == 6
-
- //Spread
- function f(x, y, z) {
- return x + y + z;
- }
- // Pass each elem of array as argument
- f(...[1,2,3]) == 6

- var name = "Bob", time = "today";
- `Hello ${name}, how are you ${time}?`
- // return "Hello Bob, how are you today?"
迭代器有个 next 方法,调用会返回:
- for (var n of ['a','b','c']) {
- console.log(n);
- }
- // 打印a、b、c
Class,有 constructor、extends、super,但本质上是语法糖(对语言的功能并没有影响,但是更方便程序员使用)。
- class Artist {
- constructor(name) {
- this.name = name;
- }
-
- perform() {
- return this.name + " performs ";
- }
- }
-
- class Singer extends Artist {
-
- constructor(name, song) {
- super.constructor(name);
- this.song = song;
- }
-
- perform() {
- return super.perform() + "[" + this.song + "]";
- }
- }
-
- let james = new Singer("Etta James", "At last");
- james instanceof Artist; // true
- james instanceof Singer; // true
-
- james.perform(); // "Etta James performs [At last]"

ES6的内置模块功能借鉴了CommonJS和AMD各自的优点:
- // lib/math.js
- export function sum(x, y) {
- return x + y;
- }
- export var pi = 3.141593;
-
- // app.js
- import * as math from "lib/math";
- alert("2π = " + math.sum(math.pi, math.pi));
-
- // otherApp.js
- import {sum, pi} from "lib/math";
- alert("2π = " + sum(pi, pi));
-
- Module Loaders:
- // Dynamic loading – ‘System’ is default loader
- System.import('lib/math').then(function(m) {
- alert("2π = " + m.sum(m.pi, m.pi));
- });
-
- // Directly manipulate module cache
- System.get('jquery');
- System.set('jquery', Module({$: $})); // WARNING: not yet finalized

四种集合类型,WeakMap、WeakSet 作为属性键的对象如果没有别的变量在引用它们,则会被回收释放掉。
- // Sets
- var s = new Set();
- s.add("hello").add("goodbye").add("hello");
- s.size === 2;
- s.has("hello") === true;
-
- // Maps
- var m = new Map();
- m.set("hello", 42);
- m.set(s, 34);
- m.get(s) == 34;
-
- //WeakMap
- var wm = new WeakMap();
- wm.set(s, { extra: 42 });
- wm.size === undefined
-
- // Weak Sets
- var ws = new WeakSet();
- ws.add({ data: 42 });//Because the added object has no other references, it will not be held in the set

一些新的 API
- Number.EPSILON
- Number.isInteger(Infinity) // false
- Number.isNaN("NaN") // false
-
- Math.acosh(3) // 1.762747174039086
- Math.hypot(3, 4) // 5
- Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
-
- "abcde".includes("cd") // true
- "abc".repeat(3) // "abcabcabc"
-
- Array.from(document.querySelectorAll('*')) // Returns a real Array
- Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
-
- [0, 0, 0].fill(7, 1) // [0,7,7]
- [1, 2, 3].find(x => x == 3) // 3
- [1, 2, 3].findIndex(x => x == 2) // 1
- [1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]
- ["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
- ["a", "b", "c"].keys() // iterator 0, 1, 2
- ["a", "b", "c"].values() // iterator "a", "b", "c"
-
- Object.assign(Point, { origin: new Point(0,0) })

使用代理(Proxy)监听对象的操作,然后可以做一些相应事情。
- var target = {};
- var handler = {
- get: function (receiver, name) {
- return `Hello, ${name}!`;
- }
- };
-
- var p = new Proxy(target, handler);
- p.world === 'Hello, world!';
可监听的操作: get、set、has、deleteProperty、apply、construct、getOwnPropertyDescriptor、defineProperty、getPrototypeOf、setPrototypeOf、enumerate、ownKeys、preventExtensions、isExtensible。
Symbol 是一种基本类型。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的 symbol 是唯一的。
- var key = Symbol("key");
- var key2 = Symbol("key");
- key == key2 //false
Promises 是处理异步操作的对象,使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观(类似 jQuery 的 deferred 对象)。
- function fakeAjax(url) {
- return new Promise(function (resolve, reject) {
- // setTimeouts are for effect, typically we would handle XHR
- if (!url) {
- return setTimeout(reject, 1000);
- }
- return setTimeout(resolve, 1000);
- });
- }
-
- // no url, promise rejected
- fakeAjax().then(function () {
- console.log('success');
- },function () {
- console.log('fail');
- });

初学者在 ES6 和 ES5 之间的取舍?:https://www.zhihu.com/question/30608934
ES5 中的引用需要先使用 require 导入包,成为对象,再去进行真正引用
- // ES5
- var React = require("react");
- var { Component,PropTypes } = React; //引用React抽象组件
-
- var ReactNative = require("react-native");
- var { Image,Text } = ReactNative; //引用具体的React Native组件
在 ES6 里,可以使用 import 直接实现系统库引用,不需要额外制作一个类库对象:
- //ES6
- import { Component,PropTypes } from 'react';
- import { Image,Text } from 'react-native'
ES5 中,要导出一个类给别的模块使用,一般通过 module.exports 来实现,引用时通过 require方法来获取:
- //ES5导出
- var MyComponent = React.createClass({
- ...
- });
- module.exports = MyComponent;
-
- //ES5导入
- var MyComponent = require('./MyComponent');
ES6 中,使用 export default 实现同样的功能,但要使用 import 方法来实现导入:
- //ES6导出(一般都继承于Component类)
- export default class MyComponent extends Component{
- ...
- }
-
- //ES6导入
- import MyComponent from './MyComponent';
- // ES5的继承
- function FatherClass(name) {
- this.family = ['father', 'mother', 'daughter'];
- this.name = name
- };
- FatherClass.prototype.getName = function () {
- console.log(this.name);
- };
- function ChilderClass(name, age) {
- // 子类继承父类(没涉及到父类原型)
- FatherClass.call(this, name)
- this.age = age;
- };
- function F() { };
- //过渡函数的原型继承父对象
- F.prototype = FatherClass.prototype;
- ChilderClass.prototype = new F();
- var c = new ChilderClass('lily', 18);

这个就是 es5 的类继承,构造函数继承和原型继承是分开的(两种不同的方式)。子类继承父类的时候,先创造子类的实例对象this,然后再将父类的属性和方法添加到this上面(FatherClass.call(this,name)),然后再去继承原型链。
- // ES6的继承
- class FatherClass {
- family = ['father', 'mother', 'daughter'];
- constructor(name) {
- this.name = name
- }
- getName() {
- console.log(this.name);
- }
- };
-
- class ChilderClass extends FatherClass {
- constructor(name, age) {
- super(name);
- this.age = age;
- }
- };
- var c = new ChilderClass('lily', 18);

这里类继承机制完全和 es5 不一样,是调用 super 方法将父类的属性和方法加到 this 上面。在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类的构建基于父类,只有 super 方法才能调用父类构造函数。
- ES5 的继承实质上是先创建子类的实例对象 this,然后再将父类的方法添加到 this 上(Parent.apply(this)),然后继承原型。
- ES6 的继承机制完全不同,它是在子类的构造器中先调用 super 方法,创建出父类实例对象 this,然后再去修改子类中的 this 完善子类。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。