举个例子:一个公司需要正常运转,就有市场部,技术部,人事部等等,这每个部门就相当于一个模块,在前端项目中也就有比如专门网络请求的模块,错误处理的模块,专门渲染的模块。
传统做法会引入多个js脚本,它们共处于全局作用域下,就容易导致全局作用域变量冲突(例如同名变量冲突),而发生一些不可预测的事情。 例如:
代码语言:txt复制/*moduleA.js里*/
代码语言:txt复制var a=10;
代码语言:txt复制/*moduleB.js里*/
代码语言:txt复制var a=11;
代码语言:txt复制/*index.html里*/
代码语言:txt复制<body>
代码语言:txt复制 <script src="./moduleA.js"></script>
代码语言:txt复制 <script src="./moduleB.js"></script>
代码语言:txt复制 <script src="./moduleC.js"></script>
代码语言:txt复制</body>
代码语言:txt复制复制代码
当出现上面得冲突后,a的值还能确定吗?——不能!
然后就有人想出,每个js脚本里都使用一个对象包裹,形成一个局部作用域。
代码语言:txt复制// 定义模块内的局部作用域,以moduleA为例
代码语言:txt复制 var Susan = {
代码语言:txt复制 name: "susan",
代码语言:txt复制 sex: "female",
代码语言:txt复制 tell: function(){
代码语言:txt复制 console.log("im susan")
代码语言:txt复制 }
代码语言:txt复制 }
代码语言:txt复制复制代码
但是这样又有各严重的问题,就是对象里值我们能更改,无法保证模块属性内部安全性,对于比如说用户名密码等数据的情景就很严重了。
于是又改进到了立即执行函数和闭包的形式。
代码语言:txt复制// 定义模块内的闭包作用域(模块作用域),以moduleA为例
代码语言:txt复制 var SusanModule = (function(){
代码语言:txt复制 var name = "susan"
代码语言:txt复制 var sex = "female"
代码语言:txt复制 functioon tell(){//这样就不能更改其中的数据了
代码语言:txt复制 console.log("I'm ", this.name)
代码语言:txt复制 }
代码语言:txt复制 })()
代码语言:txt复制复制代码
我们再改进下写法,为立即执行函数写入参数为window
。
// 定义模块内的闭包作用域(模块作用域),以moduleA为例
代码语言:txt复制 (function(window){
代码语言:txt复制 var name = "susan"
代码语言:txt复制 var sex = "female"
代码语言:txt复制 functioon tell(){
代码语言:txt复制 console.log("I'm ", this.name)
代码语言:txt复制 }
代码语言:txt复制 window.susanModule = {tell}
代码语言:txt复制 })(window)// window作为参数传给
代码语言:txt复制//////////////////////
代码语言:txt复制//测试
代码语言:txt复制window.susanModule.tell(); //im susan
代码语言:txt复制复制代码
这样大概就是早期的模块化的形式了。
现在的模块化方案有 :
- AMD (Asynchronous Module Definition 异步模块定义)
//大概形式如下
代码语言:txt复制//定义
代码语言:txt复制define("mymodule", ["dep1", "dep2"], function(d1, d2) {
代码语言:txt复制});
代码语言:txt复制// 加载
代码语言:txt复制require(["module", "../file"], function(module, file) {
代码语言:txt复制});
代码语言:txt复制复制代码
- CommonJs :Node.js 专用, 该方案的核心思想就是允许模块通过require方案同步加载依赖的其他模块,通过exports或module.exports来暴露出需要的接口。
// 通过require函数来引用
代码语言:txt复制const math = require("./math");
代码语言:txt复制// 通过exports将其导出
代码语言:txt复制exports.getSum = function(a,b){
代码语言:txt复制 return a b;
代码语言:txt复制}
代码语言:txt复制复制代码
- ES6 Module :该方案最大的特点就是静态化,静态化的优势在于可以在编译的时候确定模块的依赖关系以及输入输出的变量。上面提到的CommonJs和AMD都只能在运行时确定这些东西。
// 通过import函数来引用
代码语言:txt复制import math from "./math";
代码语言:txt复制// 通过export将其导出
代码语言:txt复制export function sum(a, b){
代码语言:txt复制 return a b;
代码语言:txt复制}
代码语言:txt复制复制代码
此外说说ES6
模块化和CommonJs
的模块化的区别:
CommonJS
模块输出的是一个值的拷贝,ES6
模块输出的是值的引用。
注意:CommonJS
模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
ES6
模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
CommonJS
模块是运行时加载,ES6
模块是编译时输出接口。
原因:CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 `ES6
模块`不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
CommonJS
模块的require()
是同步加载模块,ES6
模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
前端模块化主要解决了两个问题: “命名空间冲突”,“文件依赖管理” 。
和介绍webpack又有什么关系呢?
在webpack中,一切皆模块。我们在模块化开发的时候,通常会使用`ES
Module或者
CommonJS规范导出或引入依赖模块,webpack打包编译的时候,会统一替换成自己的
webpack_require`来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。