ES6,CommonJS,AMD,CMD,UMD模块化规范介绍及使⽤
前⾔
在项⽬开发过程中,引⼊模块有时使⽤require(),有时使⽤import…from..,导出模块有时⽤export或export default,⼀直会这么使⽤,但是没有太在意区别,以及它们分别遵循的是哪个模块规范。接下来就直接介绍⼏种模块的使⽤。
js arguments具体内容
⼀、模块概念介绍
模块通常是指编程语⾔所提供的代码组织机制,利⽤此机制可将程序拆解为独⽴且通⽤的代码单元;
所谓模块化主要是解决代码分割、作⽤域隔离、模块之间的依赖管理以及发布到⽣产环境的⾃动化打包与处理等多⽅⾯;
模块规范主要是进⾏模块加载,⼀个单独的⽂件也算是⼀个模块,⼀个模块就是⼀个单独的作⽤域,不会污染全局作⽤域;
⼀个模块就是⼀个对其他模块暴露⾃⼰的属性或者⽅法的⽂件。
⼆、模块的优点
(1)可维护性。因为模块是独⽴的,⼀个设计良好的模块会让外⾯的代码对⾃⼰的依赖越少越好,这样⾃⼰就可以独⽴去更新和改进。
(2)命名空间。在 JavaScript ⾥⾯,如果⼀个变量在最顶级的函数之外声明,它就直接变成全局可⽤。因此,常常不⼩⼼出现命名冲突的情况。使⽤模块化开发来封装变量,可以避免污染全局环境。
(3)重⽤代码。有时从之前写过的项⽬中拷贝代码到新的项⽬,这没有问题,但是更好的⽅法是,通过模块引⽤的⽅式,来避免重复的代码库。
三、ES6、CommonJS、AMD、CMD、UMD模块介绍
(1)ES6模块(Module)
ES6的模块⾃动采⽤严格模式,不管是否在模块头部加上"use strict";
严格模式主要有以下这些限制:
变量必须声明后再使⽤
函数的参数不能有同名属性,否则报错
不能使⽤with语句
不能对只读属性赋值,否则报错
不能使⽤前缀0表⽰⼋进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作⽤域引⼊变量
eval和arguments不能被重新赋值
arguments不会⾃动反映函数参数的变化
不能使⽤arguments.callee
不能使⽤arguments.caller
禁⽌this指向全局对象
不能使⽤fn.caller和fn.arguments获取函数调⽤的堆栈
增加了保留字(⽐如protected、static和interface)
•导出模块 export
作为⼀个模块,可以选择性地给其他模块暴露⾃⼰的属性和⽅法,供其他模块使⽤
1//profile.js
2 export var firstName = "wang";
3 export var lastName = "qin";
4 export var born = "1996";
5//等价于
6var firstName = "wang";
7var lastName = "qin";
8var born = "1996";
9 export {firstName,lastName,born}
1、通常情况下,export输出的变量就是本来的名字,但是可以使⽤as关键字对输出的变量进⾏重命名
1function v1(){}
2function v2(){}
3
4 export{
5 v1 as stream1,
6 v2 as stream2,
7 v2 as streamLatestVersion
8 };
9//使⽤as关键字,重命名函数v1和v2的对外接⼝;重命名之后,v2可以⽤不同的名字输出2次
2、需要特别注意的是,export命令规定的是对外的接⼝,必须与模块内部的变量建⽴⼀⼀对应关系
1//注意:以下2种写法报错是因为没有提供对外的接⼝;第1种写法直接输出1;
2//第2种写法通过变量m,还是直接输出1,1只是⼀个值,不是对外接⼝;
3
4//报错
5 export 1;
6
7//报错
8var m = 1;
9 export m;
10
11//以下3种写法都是正确的,规定了对外的接⼝m;其他脚本通过访问这个接⼝,可以取到1;
12//实质就是在接⼝名与模块内部变量之间建⽴了⼀⼀对应关系;
13//⽅法⼀
14 export var m = 1;
15//⽅法⼆
16var m = 1;
17 export {m};
18//⽅法三
19var n = 1;
20 export {n as m};
3、export命令和import命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作⽤域内,就会报错
1function foo(){
2 export default 'bar' //语法错误
3 }
4 foo();
•默认导出模块 export default
每个模块⽀持我们导出⼀个没有名字的变量,使⽤关键语句export default来实现
1//module.js
2 export default function(){ //使⽤export default关键字对外导出⼀个匿名函数
3 console.log('I am default Fn');
4 }
5
6//导⼊上⾯模块时,可以为该匿名函数取任意名字
7 import defaultFn from './module.js'
8 defaultFn(); //输出"I am default Fn"
1、默认输出和正常输出的⽐较
1 export default function diff(){} //默认导出
2 import diff from 'diff'; //导⼊
3
4
5 export function diff(){} //正常导出
6 import {diff} from 'diff'; //导⼊
7
8//注意:使⽤export default默认导出时,对应的import语句不需要使⽤⼤括号;
9//⽽使⽤export正常导出时,对应的import语句需要使⽤⼤括号;
注意:export default命令⽤于指定模块的默认输出。显然,⼀个模块只能有⼀个默认输出,因此export default命令只能使⽤⼀次。所以,import命令后⾯才不⽤加⼤括号,因为只可能对应⼀个⽅法。
2、export default本质是将该命令后⾯的值,赋给default变量以后再默认,所以直接将⼀个值写在export default之后
1//正确;指定对外接⼝为default
2 export default 66;
3
4//报错;因为没有指定对外的接⼝
5 export 66;
3、在⼀条import语句中,同时导⼊默认⽅法和其他变量,可以写成下⾯这样
1 export default function(){} //默认导出
2 export function each(obj,iterator,context){} //正常导出
3
4//同时导⼊默认⽅法和其他变量
5 import _,{each} from 'loadsh'
•导⼊模块 import
作为⼀个模块,可以根据需要,引⼊其他模块的提供的属性或者⽅法,供⾃⼰模块使⽤
1、 import命令接受⼀对⼤括号,⾥⾯指定要从其他模块导⼊的变量名。⼤括号⾥⾯的变量名,必须与
被导⼊模块(profile.js)对外接⼝的名称相同。如果想为输⼊的变量重新取⼀个名字,import命令要使⽤as关键字,将输⼊的变量重命名
1 import {lastName as realName} from './profile.js'
2、import后⾯的from指定模块⽂件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。如果只是模块名,不带有路径,那么必须有配置⽂件,告诉 JavaScript 引擎该模块的位置
3、import命令具有提升效果,会提升到整个模块的头部,⾸先执⾏
1 foo();
2 import { foo } from 'my_module';
3//先调⽤foo()不会报错,原因是import的执⾏早于foo()的调⽤;
4//本质是因为import命令是在编译阶段执⾏,在代码运⾏之前;
4、由于import是静态执⾏,所以不能使⽤表达式和变量,这些只有在运⾏时才能得到结果的语法结构
1//报错
2 import { 'f' + 'oo' } from 'my_module';
3
4//报错
5 let module = 'my_module';
6 import { foo } from module;
7
8//报错
9if (x === 1) {
10 import { foo } from 'module1';
11 } else {
12 import { foo } from 'module2';
13 }
5、import语句会执⾏所加载的模块
1 import 'loadsh' //仅仅执⾏loadsh模块,但不输⼊任何值
(2)CommonJS
CommonJS 最开始是 Mozilla 的⼯程师于 2009 年开始的⼀个项⽬,它的⽬的是让浏览器之外的 JavaScript (⽐如服务器端或者桌⾯端)能够通过模块化的⽅式来开发和协作。
在 CommonJS 规范中,每个 JavaScript ⽂件就是⼀个独⽴的模块上下⽂(module context),在这个上下⽂中默认创建的属性都是私有的。也就是说,在⼀个⽂件定义的变量(还包括函数和类),都是私有的,对其他⽂件是不可见的。
需要注意的是,CommonJS 规范的主要适⽤场景是服务器端编程,采⽤同步加载模块的策略。
如果某个⽂件依赖3个模块,代码会⼀个⼀个依次加载它们。
CommonJS模块化规范实现⽅案主要包含require 与ports( exports ) 这两个关键字,允许某个模块对外暴露部分接⼝并且由其他模块导⼊使⽤。
1//======sayHello.js=======
2function sayHello(){
3this.hello = function(){
4 console.log('hello');
5 };
6this.hi = function(){
7 console.log('hi');
8 }
9 }
11
12//=======main.js===========
13var say = require('./sayHello.js');
14var sayPlay = new say();
15 sayPlay.hello(); //hello
作为服务器端的解决⽅案,CommonJS需要⼀个兼容的脚本加载器(即NodeJS)作为前提条件,该脚本加载器必须⽀持名为require和ports的函数,它们可以将模块相互导⼊导出。
==================补充说明:Node.js=======================
1、Node是对CommonJS模块规范的实现。
2、Node.js模块分为两⼤类:⼀类是核⼼模块,⼀类是⽂件模块;
核⼼模块:就是Node.js标准API中提供的模块,如fs/http/path等,这些由Node.js官⽅提供的模块,编译成了⼆进制代码,可以直接通过require获取核⼼模块,例如require('fs'),核⼼模块拥有最⾼的加载优先级,如果有模块与核⼼模块命名冲突,Node.js总是会加载核⼼模块。
⽂件模块:是存储为单独的⽂件(或⽂件夹)的模块,可能是JavaScript代码、JSON或编译好的C/C++代码。在不显式指定⽂件模块扩展名的时候,Node.js会分别试图加上.js、.json、.node(编译好的C/C++代码)。
Node.js会根据后缀名来决定3类⽂件模块的加载⽅法:
.js 通过fs模块同步读取js⽂件并编译执⾏。
.node 通过C/C++进⾏编写的Addon。通过dlopen⽅法进⾏加载。
.json 读取⽂件,调⽤JSON.parse解析加载。
1/* js后缀的编译过程:【Node.js在编译js⽂件的过程中实际完成的步骤有对js⽂件内容进⾏头尾包装】*/
2//==========circle.js===========
3var PI = Math.PI;
4 exports.area = function (r) {
5return PI * r * r;
6 };
7 exports.circumference = function (r) {
8return 2 * PI * r;
9 };
10
11//============app.js============
12var circle = require('./circle.js');
13 console.log( 'The area of a circle of radius 4 is ' + circle.area(4));
14
15
16//==============app包装后=========
17 (function (exports, require, module, __filename, __dirname) {
18var circle = require('./circle.js');
19 console.log('The area of a circle of radius 4 is ' + circle.area(4));
20 });
21
22//这段代码会通过原⽣模块vm的runInThisContext⽅法执⾏
23//(类似eval,只是具有明确上下⽂,不污染全局),返回为⼀个具体的function对象。
24//最后传⼊module对象的exports,require⽅法,module,⽂件名,⽬录名作为实参并执⾏
3、加载⽅式
按路径加载模块
如果require参数以" / "开头,那么就以绝对路径的⽅式查模块名称,如果参数以" ./ "、" ../ "开头,那么则是以相对路径的⽅式来查模块。
查node_modules⽬录加载模块
如果require参数不以" / "、" ./ "、" ../ "开头,⽽该模块⼜不是核⼼模块,那么就要通过查node_modules加载模块了。使⽤npm⼯具获取的模块/包通常就是以这种⽅式加载的。
4、加载缓存
Node.js模块不会被重复加载,因为Node.js通过⽂件名缓存所有加载过的⽂件模块,之后再访问相同⽂件模块时就不会重新加载了。
注意: Node.js是根据实际⽂件名缓存的,⽽不是require()提供的参数缓存的,也就是说即使分别通过require('express')和
require('./node_modules/express')加载两次,也不会重复加载,因为尽管两次参数不同,解析到的⽂件却是同⼀个。
Node.js 中的模块在加载之后是以单例化运⾏,并且遵循值传递原则:如果是⼀个对象,就相当于这个对象的引⽤。
5、模块载⼊过程(需进⼀步理解)
加载⽂件模块的⼯作,主要由原⽣模块module来实现和完成,该原⽣模块在启动时已经被加载,进程直接调⽤到runMain静态⽅法。
1//例如运⾏: node app.js
2
3 Module.runMain = function () {
4// Load the main module--the command line argument.
5 Module._load(process.argv[1], null, true);
6 };
7
8//_load静态⽅法在分析⽂件名之后执⾏
9var module = new Module(id, parent);
10
11//根据⽂件路径缓存当前模块对象,该模块实例对象则根据⽂件名加载。
12 module.load(filename);
6、require 函数
require 引⼊的对象主要是函数。当 Node 调⽤ require() 函数,并且传递⼀个⽂件路径给它的时候,Node 会经历如下⼏个步骤:Resolving:到⽂件的绝对路径;
Loading:判断⽂件类型;
Wrapping:打包,给⽂件赋予⼀个私有作⽤范围。这是使 require 和 module 模块在本地引⽤的⼀种⽅法;
Evaluating:VM 对加载的代码进⾏处理;
Caching:当再次需要⽤这个⽂件时,不需要重复⼀遍上⾯步骤。
7、sions 来查看对三种⽂件的⽀持情况
Node 对每种扩展名所使⽤的函数及其操作:
对 .js ⽂件使⽤ module._compile;
对 .json ⽂件使⽤ JSON.parse;
对 .node ⽂件使⽤ process.dlopen。
8、⽂件查策略
从⽂件模块缓存中加载
尽管原⽣模块与⽂件模块的优先级不同,但是优先级最⾼的是从⽂件模块的缓存中加载已经存在的模块。
从原⽣模块加载
原⽣模块的优先级仅次于⽂件模块缓存的优先级。require⽅法在解析⽂件名之后,优先检查模块是否在原⽣模块列表中。以http模块为例,尽管在⽬录下存在⼀个http、http.js、de、http.json⽂件,require(“http”)都不会从这些⽂件中加载,⽽是从原⽣
模块中加载。原⽣模块也有⼀个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调⽤原⽣模块的加载⽅式进⾏加载和执⾏。
从⽂件加载
当⽂件模块缓存中不存在,⽽且不是原⽣模块的时候,Node.js会解析require⽅法传⼊的参数,并从⽂件系统中加载实际的⽂件,加载过程中的包装和编译细节在前⾯说过是调⽤load⽅法。
1 ======当 Node 遇到 require(X) 时,按下⾯的顺序查并加载===========
2
3(1)如果 X 是内置模块(⽐如 require('http'))
4 a. 返回该模块。
5 b. 不再继续执⾏。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论