【每日一题NO.51】JS模块化

前端印记

共 3904字,需浏览 8分钟

 · 2021-10-12

人生苦短,总需要一点仪式感。比如学前端~

请说出目前主流的js模块化实现的技术有哪些?他们的区别在哪儿?

答:目前流行的js模块化规范有CommonJSAMDCMD以及ES6的模块系统

CommonJS

CommonJS的出发点:

js没有完善的模块系统,标准库较少,缺少包管理工具。伴随着NodeJS的兴起,能让JS在任何地方运行(特别是服务端),也达到了具备开发大型项目的能力,所以CommonJS模块化应运而生。

NodeJS是CommonJS规范的主要实践者,有四个重要的环境变量为模块化的实现提供支持:moduleexportsrequireglobal

实际上使用时,用module.exports定义当前模块对外输出的接口,用require加载模块。

commonJS用同步的方式加载模块。

在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,由于网络原因限制,读取模块文件会耗费一定的时间,所以更合理的方法是使用异步加载。

暴露模块:

module.exports=valueexports.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模块功能主要由两个命令构成:exportimport
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
你也可以点击文末的 “阅读原文” 快速跳转


END
愿你历尽千帆,归来仍是少年。
浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报