【每日一题NO.51】JS模块化
人生苦短,总需要一点仪式感。比如学前端~
请说出目前主流的js模块化实现的技术有哪些?他们的区别在哪儿?
答:目前流行的js模块化规范有CommonJS
、AMD
、CMD
以及ES6的模块系统
。
CommonJS
CommonJS的出发点:
js没有完善的模块系统,标准库较少,缺少包管理工具。伴随着NodeJS的兴起,能让JS在任何地方运行(特别是服务端),也达到了具备开发大型项目的能力,所以CommonJS模块化应运而生。
NodeJS是CommonJS规范的主要实践者,有四个重要的环境变量为模块化的实现提供支持:module
、exports
、require
、global
。
实际上使用时,用module.exports
定义当前模块对外输出的接口,用require
加载模块。
commonJS用同步的方式加载模块。
在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,由于网络原因限制,读取模块文件会耗费一定的时间,所以更合理的方法是使用异步加载。
暴露模块:
module.exports=value
或exports.xxx=value
引入模块:
require(xxx)
CommonJS规范
一个文件就是一个模块,拥有单独的作用域 普通方式定义的变量、函数、对象都属于该模块内容 通过require来加载模块,nodejs只有执行到require函数时才会加载并执行模块 通过exports和module.exports来暴露块中的内容
注意事项
当exports和module.exports同时存在,module.exports会覆盖exports 当模块内全是exports时,就等同于module.exports exports就是module.exports的子集 所有代码都运行在模块作用域,不会污染全局作用域 模块可以多次加载,但只会在第一次加载,运行时结果就会被缓存,以后再加载的时候就直接读取缓存结果 模块加载顺序按照代码出现的顺序同步加载 __dirname
代表当前文件所在的文件夹路径__filename
代表当前模块文件所在的文件夹路径+文件名
AMD
AMD:Asynchronous Module Definition
,异步加载模块。
它是一个在浏览器端模块化开发的规范,不是原生js的规范,使用AMD规范进行页面开发需要用到对应的函数库:RequireJS
。
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
使用require.js实现AMD规范的模块化:
用 require.config()
指定引用路径等;用 define()
定义模块;用 require()
加载模块.
//定义模块
define('moduleName',['a','b'],function(a,b){
return someExportValue
})
//引入模块
require(['a','b'],function(a,b){
//执行代码逻辑
})
Require JS主要解决的问题
文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器 js加载的时候浏览器会停止页面渲染,加载文件愈多,页面相应时间就越长 异步前置加载
语法 define(id,dependencies,factory)
id
:可选参数,用来定义模块的标识,如果没有提供该参数则表示定义一个匿名模块,且模块的标识就是模块文件的文件名(去掉扩展名,比如文件名叫a.js,不定义id模块标识就是a);dependencies
:是一个当前模块所依赖的所有模块名称的集合;factory
:工厂方法,模块初始化要执行的函数或对象,如果为函数,应该只被执行一次,如果是对象,此对象应该为模块的输出值。
CMD
CMD
是另一种js模块化方案,它与AMD很类似。
不同点在于:AMD推崇依赖前置,提前执行,CMD推崇依赖就近,延迟执行。
此规范其实是在sea.js
推广过程中产生的。
define(id,deps,factory)
因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id
;
CMD推崇依赖就近,所以一般不在define
的参数中写依赖,而是在factory中写factory
有三个参数:function(require,exports,module){}
require是factory函数的第一个参数,require是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口 exports是一个对象,用来向外提供模块接口 module是一个对象,上面存储了与当前模块相关联的一些属性和方法
示例代码
定义没有依赖的模块
define(function(require,exports,module){
export.xxx=val
module.exports=val
})
定义有依赖的模块
define(function(require,exports,module){
//同步引入模块
var module1=reuqire('./module1.js')
//异步引入模块
require.async('./module2.js',function(val){
//代码逻辑
})
exports.xxx=value
})
引入模块
define(function(require){
const val1=require('./module1.js')
val1.show()
})
UMD通用模块规范
一种整合CommonJS和AMD规范的方法,希望能解决跨平台模块方案。
运行原理
UMD先判断是否支持NodeJS的模块(exports)是否存在,存在就是用nodejs模块模式; 再判断是否支持AMD(define是否存在),存在则使用AMD方式加载
// 【一些类库打包出来就是这样的样子】
(function (window,factory){
if(typeof exports === 'Object'){
module.exports=factory();
}else if(typeof define === 'function' && define.amd){
define(factory);
}else{
window.eventUtil = factory()
})
})(this,function(){
//执行代码
})
ES6模块化
一种从根本上的变革。
ES6在语言标准的层面上,实现了模块功能,这意味着原生JS拥有了模块化的支持,而且实现的相当简单,主要为浏览器和服务器通用的模块解决方案。
ES6模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口import
命令用于输入其他模块提供的功能
其实ES6还提供了export default
命令,为模块指定默认输出,对应的import语句不需要使用大括号,这类似AMD的引用写法。
ES6的模块不是对象,import命令会被JS引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因此,使得静态分析成为可能。
❝当然,ES2020提案 引入import()函数,支持动态加载模块。
❞
export
export
可以导出的是一个对象中包含的多个属性及方法。export default
只能导出一个可以不具名的函数,我们可以通过import
进行引用
import
import
将外部模块导入。
import {fn} from './xxx' // export导出的方式,fn名字是模块内部固定名称
import fn from './xxx' // export default导出的方式,fn名字可以自定义
总结
CommonJS
是同步加载的。主要是应用在nodejs服务端的模块化机制。
通过module.export 导出声明 通过require() 加载 每一个文件都是一个模块 它有自己的作用域,文件内的变量,属性函数等不能被外界访问 node会将模块缓存,第二次加载会直接在缓存中获取
AMD
是异步加载的。主要应用在浏览器环境下。
requireJS函数库是遵循AMD规范化的模块化工具。
它是通过define() 定义声明 通过 require(['a','b'],function(a,b){}) 加载
ES6的模块
的运行机制与CommonJS的不一样:
js引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用,等到脚本真正执行的时候才会通过该引用去对应模块中获取值; 在引用到执行的过程中,如果模块中的值发生了变化,导入的这里也会跟着变化; ES6模块是动态引用,并不会缓存值,模块里总是绑定其所在的模块。
所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转