当前位置: 首页 > 后端技术 > Node.js

【采访话题】Javascript社区在模块AMD、CMD、import

时间:2023-04-04 00:40:17 Node.js

js的require时代下了很多功夫,以达到现有运行环境下“模块”的效果。对象写法是把模块写成一个对象,所有的模块成员都放在这个对象中varmodule1=newObject({_count:0, m1:function(){  //... }, m2:函数(){  //... }});以上函数m1()和m2()封装在module1对象中。使用时,就是调用这个对象的属性module1.m1();这种写法会暴露所有模块成员,内部状态可以被外部改写。例如,外部代码可以直接更改内部计数器的值。模块._count=1;Immediately-InvokedFunctionWriting使用“Immediately-InvokedFunctionExpression(IIFE),可以达到不暴露私有成员的目的。varmodule=(function(){var_count=0;varm1=function(){alert(_count)}varm2=function(){alert(_count+1)}return{m1:m1,m2:m2}})()模块是Javascript模块的基本编写方式,主流的模块规范还没有提出一套es6之前的官方规范,从社区和框架推广的角度来看,目前流行的javascript模块规范有两种:CommonJS和AMDCommonJS规范,node编程中最重要的思想之一就是模块,正是这种思想让JavaScript变得庞大规模化工程成为可能,服务端使用CommonJS模块规范,在CommonJS中,有一个全局方法require()用于加载模块,假设有一个数学模块math.js,可以按如下方式加载。 varmath=require('math'); math.add(2,3);//5正是因为CommonJS使用的require方法的推广,后来AMD和CMD也采用了require方法来引用模块的样式。AMD规范Common.js起源于node,因此广泛应用于服务器端。对于服务器端,所有模块都存储在本地硬盘上,可以同步加载。等待时间就是硬盘的读取时间。但是,对于浏览器来说,这是一个很大的问题,因为模块是放在服务器端的,等待时间取决于网速,可能需要很长时间,浏览器处于“假死”状态.因此,浏览器端的模块不能使用“同步加载”(synchronous),只能使用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。AMD是“AsynchronousModuleDefinition”的缩写,意思是“异步模块定义”。它异步加载模块,模块的加载不影响后面语句的运行。所有依赖该模块的语句都定义在一个回调函数中,回调函数只有在加载完成后才会运行。模块必须用特殊的define()函数定义。define(id?,dependencies?,factory)id:string,模块名称(可选)dependencies:是我们要加载的依赖模块(可选),使用相对路径,注意是数组格式factory:factory方法,返回一个模块函数。如果一个模块不依赖其他模块,可以直接在define()函数中定义。//math.js  define(function(){    varadd=function(x,y){      returnx+y;    };    return{      add:添加    };  });如果这个模块还依赖于其他模块,那么define()函数的第一个参数必须是一个数组,表示模块的依赖关系。define(['Lib'],function(Lib){    functionfoo(){      Lib.doSomething();    }    return{      foo:foo    };  });当require()函数加载上述模块时,会先加载Lib.js文件。AMD也使用require()语句来加载模块,但与CommonJS不同的是,它需要两个参数:require([module],callback);第一个参数[module]是一个数组,里面的成员是要加载的模块;第二个参数callback为加载成功后的回调函数。如果将之前的代码改写成AMD形式,则为:require(['math'],function(math){ math.add(2,3);});math.add()和math模块加载不同步,浏览器不会卡顿。所以很明显,AMD更适合浏览器环境。目前,有两个主要的Javascript库实现了AMD规范:require.js和curl.js。CMD规范CMD(CommonModuleDefinition),是seajs推崇的规范,CMD依赖于最接近的,使用时需要。它是这样写的:define(function(require,exports,module){varclock=require('clock');clock.start();});CMD和AMD一样,也是使用特定的define()函数来定义,使用require方法引用模块define(id?,dependencies?,factory)id:string,modulename(optional)dependencies:是依赖模块我们要加载(可选),使用相对路径,注意数组格式工厂:工厂方法,返回一个模块functiondefine('hello',['jquery'],function(require,exports,module){//模块代码});如果一个模块不依赖于其他模块,那么它可以直接在define()函数中定义。define(function(require,exports,module){//模块代码});注意:definewithid和dependencies参数的使用不属于CMD规范,而是属于Modules/Transport规范。CMD和AMD的区别AMD和CMD最大的区别是依赖模块的执行时机不同,而不是加载的时机或方式。两者都是异步加载模块。AMD依赖前端,js可以轻松知道依赖的模块是谁,并立即加载;而CMD依赖最近的,需要使用module将其转成字符串解析才能知道依赖了哪些模块。这也是很多人诟病CMD的一点,牺牲性能来带来开发的方便,其实解析模块使用的时间短到可以忽略不计。当前标准ES6标准发布后,module成为了标准。标准用法是用export命令导出接口,导入模块。但是,在我们平时的node模块中,还是使用CommonJS规范,使用require导入模块,使用module.exports导出接口。importimportmoduleimport语法语句用于从导出的模块和脚本中导入指定文件(或模块)的函数、对象和原始值。import模块导入对应export模块导出函数,模块导入方式也有命名导入(nameimport)和defaultimport(定义导入)两种。注意:import必须放在文件的最开头。导入命令在编译阶段执行。在代码运行之前,表达式和变量只能得到运行时结果的语法结构。import命令会被JavaScript引擎静态分析,先于模块中的其他模块执行(叫“连接”更合适),所以import不能包含表达式或变量,所以无法实现动态加载。因此,import和export命令只能在模块的顶层使用,而不能在代码块内使用(例如,在if块内,或在函数内)。这样的设计有助于编译器提高效率,但也导致无法在运行时加载模块。从语法上讲,条件加载是不可能的。如果import命令要替换Node的require方法,这会形成一个障碍。因为require是在运行时加载模块,所以import命令并不能替代require的动态加载功能。ES6模块和CommonJS模块的区别来自阮一峰ES6教程CommonJS模块输出一个值的副本,而ES6模块输出一个值的引用。CommonJS模块在运行时加载,ES6模块是编译时的输出接口。ES6模块的运行机制与CommonJS不同。JS引擎静态分析脚本时,遇到模块加载命令import时会生成一个只读引用。当脚本真正执行时,根据只读引用去加载模块获取值。也就是说,ES6的导入有点像Unix系统的“符号链接”。当原值改变时,import加载的值也会改变。因此,ES6模块是动态引用,不会缓存值。模块中的变量绑定到它们所在的模块。