js模块化的发展

169 阅读5分钟

无模块化

  1. 原始写法-简单罗列
   function m1(){
    //...
  }

  function m2(){
    //...
  }

缺点: 污染了全局变量,无法保证不与其它模块之前发生变量名冲突,而且模块之前看不出直接关系

  1. 对象写法-类似命名空间

把模块写成一个对象,所有模块成员放在这个对象里,调用的时候通过module1.m1的方式调用

   var module1 = new Object({

    _count : 0,

    m1 : function (){
      //...
    },

    m2 : function (){
      //...
    }
  });

优点: 一定程度上解决了变量名的冲突问题 缺点: 会暴露素有的模块成员,内部状态可以被外部改写,比如外部代码可以直接改变内部计数器的值。module1._count=5

  1. 立即执行函数写法(IIFE)
   var module1 = (function(){

    var _count = 0;

    var m1 = function(){
      //...
    };

    var m2 = function(){
      //...
    };

    return {
      m1 : m1,
      m2 : m2
    };

  })();
   <script src="1.js"></script>
  <script src="2.js"></script>
  <script src="3.js"></script>
  <script src="4.js"></script>
  <script src="5.js"></script>
  <script src="6.js"></script>

优点: 使用这种方法,可以达到不暴露私有成员的目的 缺点: 多个一起使用时,需要保证调用顺序

  1. 增强版立即执行函数
(function(window,$){
  let msg = 'modules3',
  function foo(){
    console.log('foo()')
  }
  window.module4=foo
  $(body).css(background,'#f00')
})(window,jQuery);

优点: 当在函数内部需要使用全局变量的时候,显式的将变量输入了模块,保证了模块的独立性,使得模块间的依赖关系变的明显 缺点: 同上,当然这个式所有无模块阶段共同的问题

为什么出现模块化

  • 代码复用
  • 避免全局作用域污染
  • 可维护性:当依赖关系很复杂的时候,以上代码的编写和维护都会变得困难
  • 更好的分离,按需加载

commonJS规范

09年出现commonJs,每个文件都可以看作是一个模块,内部定义的变量就属于这个模块,只有暴露出的变量外部引入后才能访问

使用方法
  • 通过require引入模块
  • 通过module.exports或者exports导出模块
//a.js
var num = 100;
var add = function(val){
   return val + num
}
module.exports={
  num,
  add
}
// exports.num2 = 200

//b.js
var moduleA = require('./a.js')
var fn = moduleA.add;

优点: 解决了依赖、全局变量污染的问题 缺点:同步加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS不适合浏览器端模块加载,更合理的方案是使用异步加载

深入了解CommonJS

在任意一个js中打印以下代码

console.log(this)
console.log(arguments)
console.log(module.exports,exports,require, module, __filename,__dirname)
console.log(module.exports=== exports)
// true

1.jpg

module.exports和exports的区别
  • 未赋值前,module.exports和exports指向同一个引用对象{},他们是全等的
  • module.exports赋值后,指向了新的对象,他们俩之后就不相等了,建议,同一个文件中,只使用一种,避免造成混乱
commonJs在浏览器端是怎么使用的
  • 上边我们说过commonJs不适用于浏览器,但我们在写代码的时候不少见到这种写法的使用,其实是因为webpack等构建工具,帮我们进行打包转化之后以script标签的形式引在了html页面中

AMD规范

承接上文,AMD规范支持异步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 AMD标准中,定义了下面三个API:

  1. require([module], callback)
  2. define(id, [depends], callback)
  3. require.config()

即通过define来定义一个模块,然后使用require来加载一个模块, 使用require.config()指定引用路径。 先到require.js官网下载最新版本,然后引入到页面,如下:

 // html页面入口,requirejs加载之后开始执行main.js
 <script src="js/require.js" data-main="js/main"></script>
// main.js头部配置
(function(){
  requirejs.config({
    baseUrl: "js/lib",
    paths: {
      "jquery": "jquery.min",
      "dataService": "./modules/dataService",
      "alerter": "./modules/alerter",
    }
  })
  requirejs(['alerter'],function(alerter){
    alerter.showMsg()
  })
})()

// 定义没有依赖的模块
//dataService.js
define(function(){
  let name = 'dataService.js'
  function getName(){
    return name
  }
  return {getName}
})
// alerter.js
// 定义有依赖的模块
define(['dataService'],function(dataService){
  let msg = 'alerter.js'
  function showMsg(){
    console.log(msg,dataService,getName)
  }
  return {showMsg}
})

引用第三方库的时候

  • 如果库本身支持AMD,比如jquery
  • 库本身不支持,需要用shim暴露一个变量出来 优点:适合在浏览器环境中异步加载模块、并行加载多个模块

缺点:不能按需加载、开发成本大

CMD规范

支持按需加载,是 SeaJS 在推广过程中对模块定义的规范化

wecom-temp-4254131bbefe02cf87e30ac7891fcec8.png 使用方法

1. 引入sea.js2. 如何定义导出模块
    define()
    exports
    module.exports
3. 如何定义依赖模块
    require()
4. 如何使用模块
   seajs.use('./js.moduels/main.js')
// html页面引用
<script type='text/javascript' src='js/libs/sea.js'></script>
<script type='text/javascript'>
    seajs.use('./js.moduels/main.js')
</script>

image.png

ES6规范

  • es6采用了静态话的编译思想,使得在编译时就能确定模块的依赖关系,以及输入输出的变量。
  • es6的模块自动采用严格模式,不管有没有在模块头部加上‘use strict’

基本使用方法

  1. 导出
  • 导出单个变量
export const a
export function b(){}
  • 导出整个模块
 export {
    a,
    b
  }
  • 导出模块重命名
export {a,b as func} 
  1. 引入
  • 引入几个变量
import {a,b} from './a.js'
  • 引入模块别名方式
import * as obj from './a.js'
  • 其中某个变量重命名
import {a,b as func} from './a.js'
  1. default语法
  • 导出匿名函数
export default function(){}
// 导入可以给其它字段赋值
import test from './a.js'
  • 导出具名函数
function c(){}
export default c
// 导出函数重命名
export {c as default}

动态加载方式

  • import()返回一个Promise对象
import('./module/a.js')
    .then(module=>{
       ……
    })
    .catch(err=>{
        console.log(err.message)
    })
  • 同时想加载多个模块,可以用以下写法
Promise.all([
    import('./module1.js')
    import('./module2.js')
    import('./module3.js')
])
.then(([module1,module2,module3])=>{
    ……
})
  • import()也可以用在async函数中
async function main(){
    const myModule = await import('./module1.js')
}
main()

CommonJs和ES6区别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

参考文档

Javascript模块化编程(一):模块的写法

js模块化