JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解

目标:
1. 理解模块与模块化
2. 了解各种模块化规范及其实现
3. 区别各个模块化规范之间的区别
4. 掌握基于CommonJS和ES6模块化规范的编码

一. 模块化的理解

1). 什么是模块?
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

2). 一个模块的组成
私有的数据—>内部的变量
私有的行为(操作数据)—>内部的函数
向外暴露n个行为

3). 模块化
描述一种特别的编码项目JS的方式: 以模块为单元一个一个编写的
模块化的项目: JS编码时是按照模块一个一个编码的

4). 模块化的进化过程

  1. 全局function模式:
    编码: 全局变量/函数
    问题: 污染全局命名空间, 容易引起命名冲突/数据不安全
/**
 * 全局函数模式: 将不同的功能封装成不同的全局函数
 * 问题: Global被污染了, 很容易引起命名冲突
 */
//数据
let data = 'atguigu.com'
function foo() {
    console.log('foo()')
}
function bar() {
    console.log('bar()')
}
  1. namespace模式:
    编码: 将数据/行为封装到对象中
    解决: 命名冲突(减少了全局变量)
    问题: 数据不安全(外部可以直接修改模块内部的数据)
/**
 * namespace模式: 简单对象封装
 * 作用: 减少了全局变量
 * 问题: 不安全(数据不是私有的, 外部可以直接修改)
 */
let myModule = {
  data: 'atguigu.com',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}
  1. IIFE模式/增强
    IIFE: 立即调用函数表达式—>匿名函数自调用
    编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口
    引入依赖: 通过函数形参来引入依赖模块
    1) IIFE
js文件中:

/**
 * IIFE模式: 匿名函数自调用(闭包)
 * IIFE : immediately-invoked function expression(立即调用函数表达式)
 * 作用: 数据是私有的, 外部只能通过暴露的方法操作
 * 问题: 如果当前这个模块依赖另一个模块怎么办?
 */

(function (window) {
  //数据
  let data = 'atguigu.com'

  //操作数据的函数
  function foo() { //用于暴露有函数
    console.log(`foo() ${data}`)
  }

  function bar() {//用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }

  function otherFun() { //内部私有的函数
    console.log('otherFun()')
  }

  //暴露行为
  window.myModule = {foo, bar}
})(window)
//html文件中

  myModule.foo()
  myModule.bar()
  //myModule.otherFun()  //myModule.otherFun is not a function
  console.log(myModule.data) //undefined 不能访问模块内部数据
  myModule.data = 'xxxx' //不是修改的模块内部的data
  myModule.foo() //没有改变

2). IIFE增强版

js文件中:

/**
 * IIFE模式增强 : 引入依赖
 * 这就是现代模块实现的基石
 */
(function (window, $) {
	  //数据
  	let data = 'atguigu.com'

	  //操作数据的函数
	    function foo() { //用于暴露有函数
	    console.log('foo() ${data}')
	    $('body').css('background', 'red')
  }

  function bar() {//用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }

  function otherFun() { //内部私有的函数
    console.log('otherFun()')
  }

  //暴露行为
  window.myModule = {foo, bar}
})(window, jQuery)
html文件中
<!--引入的js必须有一定顺序-->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript">
  myModule.foo()

问题:

  1. 一个页面需要引入多个js文件
  2. 问题:
    1). 请求过多
    2). 依赖模糊
    3). 难以维护
  3. 这些问题可以通过现代模块化编码和项目构建来解决
-->

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript" src="../04_IIFE模式增强/jquery-1.10.1.js"></script>
<script type="text/javascript" src="../04_IIFE模式增强/test4.js"></script>
<script type="text/javascript">
  module.foo()

二、CommonJS

在这里插入图片描述
缺点:在服务器端可能会发生阻塞;在浏览器端可能耗时长,用户体验不好,并且require语法浏览器端不认识,要提前编译打包处理
在这里插入图片描述暴露的本质是exports对象,module1和3种,都是将空对象赋给exports,module2是将一个函数赋给export

1.commonjs基于服务器端(node)应用

Node.js模块化教程

  1. 下载安装node.js
  2. 创建项目结构
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
  |-app.js
  |-package.json   //可以不手写,用npm init
    {
      "name": "commonJS-node",
      "version": "1.0.0"
    }
  1. 下载第三方模块
  • npm install uniq --save //去重排序的第三方模块
  1. 模块化编码
  • module1.js
* 使用module.exports = value向外暴露一个对象
  module.exports = {
    foo() {
      console.log('moudle1 foo()')
    }
  }
  • module2.js
 使用module.exports = value向外暴露一个函数
 module.exports = function () {
   console.log('module2()')
 };
//    module.exports = {  };再写一个不可以,会把上面的覆盖,module3.js可以
  • module3.js
 * 使用module.exports = value向外暴露一个对象
 *  //使用这种方法可以无限添加属性,module2.js不可以
 exports.foo = function () {
   console.log('module3 foo()')
 };
 
 exports.bar = function () {
   console.log('module3 bar()')
 };

exports.arr = [1,5,2,3,4,4];
  • app.js
  将其他模块汇集到主模块
  /**
    1. 定义暴露模块:
      module.exports = value;
      exports.xxx = value;
    2. 引入模块:
      var module = require(模块名或模块路径);
   */
  "use strict";
  //引用模块
   let uniq = require('uniq') ; 
   //默认引入第三方库的时候要放在自定义库的上面,右边的uniq表示库,左边的表示函数
  let module1 = require('./modules/module1');
  let module2 = require('./modules/module2');
  let module3 = require('./modules/module3');
  

  
  //使用模块
  module1.foo();
  module2();
  module3.foo();
  module3.bar();
  
  console.log(uniq([module3.arr]));
  1. 通过node运行app.js
  • 命令: node app.js
  • 工具: 右键–>运行

2.commonjs基于浏览器端应用服务

Browserify模块化使用教程

  1. 创建项目结构
|-js
  |-dist //打包生成文件的目录
  |-src //源码所在的目录
    |-module1.js
    |-module2.js
    |-module3.js
    |-app.js //应用主源文件
|-index.html  //在浏览器端就要在浏览器页面上
|-package.json
  {
    "name": "browserify-test",
    "version": "1.0.0"
  }
  1. 下载browserify
  • 全局: npm install browserify -g
  • 局部: npm install browserify --save-dev //–save表示局部安装把依赖写进去,加上–dev是开发依赖的包,运行依赖不用加dev,这个是在开发时帮助编译打包用的。
  • 在这里插入图片描述
    package.json中上面是开发依赖,下面是运行依赖。但是实际上这里只是个说明,写错地方也没关系,因为用包的时候会去node_module中去找
  1. 定义模块代码
  • module1.js
    module.exports = {
      foo() {
        console.log('moudle1 foo()')
      }
    }
    
  • module2.js
    module.exports = function () {
      console.log('module2()')
    }
    
  • module3.js
    exports.foo = function () {
      console.log('module3 foo()')
    }
    
    exports.bar = function () {
      console.log('module3 bar()')
    }
    
  • app.js (应用的主js)
    //引用模块
    let module1 = require('./module1')
    let module2 = require('./module2')
    let module3 = require('./module3')
    
    let uniq = require('uniq')
    
    //使用模块
    module1.foo()
    module2()
    module3.foo()
    module3.bar()
    
    console.log(uniq([1, 3, 1, 4, 3]))
    
  • 打包处理js:
    • browserify js/src/app.js -o js/dist/bundle.js
  • 页面使用引入:
    <script type="text/javascript" src="js/dist/bundle.js"></script>   
    //引入app.js会出错,需要用browserify编译打包,即上一步的处理,
    //才能将commonjs规范应用到浏览器端
    

    三、AMD

在这里插入图片描述定义有依赖的模块是显式声名依赖注入

1.noAMD
不用AMD自己写
文件结构
在这里插入图片描述

  • dataService.js
//定义一个没有依赖的模块
(function (window) {
  let msg = 'atguigu.com'

  function getMsg() {
    return msg.toUpperCase()
  }

  window.dataService = {getMsg}
})(window)
  • alert.js
//定义一个有依赖的模块
(function (window, dataService) {
  let name = 'Tom'

  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }

  window.alerter = {showMsg}
})(window, dataService)
  • main.js
(function (alerter) {
  alerter.showMsg()
})(alerter)
  • test1.html
<script type='text/javascript' src='js/modules/dataService.js'></script>
<script type='text/javascript' src='js/modules/alerter.js'></script>
<script type="text/javascript" src="js/main.js"></script>

缺点:发三次请求,且依赖关系顺序不能出错

2.AMD规范

require.js使用教程

  1. 下载require.js, 并引入

A.自定义的模块

  1. 创建项目结构
|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html
  1. 定义require.js的模块代码
  • dataService.js
    //定义没有依赖的模块
    define(function () {
      let name = 'dataService.js'
    
      function getName() {
        return name;
      }
    //暴露模块
      return {getName}
    })
    
  • alerter.js
    //定义有依赖模块
    //右边是形参
    define(['dataService'], function (dataService) {
      let msg = 'alert.js'
    
      function showMsg() {
        alert(dataService.getMsg() + ', ' + name)
      }
    //暴露的模块,是个对象,因为对象能够暴露多个属性
      return {showMsg}
    })
    
  1. 应用主(入口)js: main.js
(function () {
  //配置
  requirejs.config({
    //基本路径
    baseUrl: "js/", //出发点在根目录下
    //模块标识名与模块路径映射
    paths: {
      "alerter": "modules/alerter", //后面不加.js
      "dataService": "modules/dataService",
    }
  })
  
  //引入使用模块
  requirejs( ['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
  1. 页面使用模块:
//先加载src的文件,然后去看data-main的属性,指向当前的主模块

B.引入第三方模块

  1. 使用第三方基于require.js的框架(jquery)
  • 将jquery的库文件导入到项目:
    • js/libs/jquery-1.10.1.js
  • 在main.js中配置jquery路径
    paths: {
              'jquery': 'libs/jquery-1.10.1' 
              //jquery不可以写成jQuery,如下图所示
          }
    
  • 在alerter.js中使用jquery
    define(['dataService', 'jquery'], function (dataService, $) {
    //这里的jquery也用小写
            let msg = 'alert.js'
    
            function showMsg() {
    
            $('body').css({background : 'red'})
           
            alert(name + ' '+dataService.getMsg())
        }
        return {showMsg}
    })
    
    在这里插入图片描述
    jQuery支持AMD规范,库中写:若是使用AMD规范,则模块名字用jquery

C.不支持AMD的第三方库

  1. 使用第三方不基于require.js的框架(angular)
    • 将angular.js导入项目
    • js/libs/angular.js
      angular需要单独的配置
  • 在main.js中配置
    (function () {
      require.config({
        //基本路径
        baseUrl: "js/",
        //模块标识名与模块路径映射
        paths: {
          //第三方库
          'jquery' : './libs/jquery-1.10.1',
          'angular' : './libs/angular',
          //自定义模块
          "alerter": "./modules/alerter",
          //地址中最前面的点不能去,表示当前目录,去掉点是表示一个层级
          "dataService": "./modules/dataService"
        },
        /*
         配置不兼容AMD的模块
         exports : 指定与相对应的模块名对应的模块对象
         */
        shim: {//就是一个单独的配置
          'angular' : { //指向path中的angular模块
            exports : 'angular' //暴露出的angular对象
          }
        }
      })
      //引入使用模块
      require( ['alerter', 'angular'], function(alerter, angular) {
        alerter.showMsg()
        console.log(angular);
      })
    })()
    

三、CMD

在这里插入图片描述在这里插入图片描述
CMD兼具了AMD和commonJS的一些特点

sea.js简单使用教程

  1. 下载sea.js, 并引入
  1. 创建项目结构
|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html
  1. 定义sea.js的模块代码
  • module1.js
    定义没有依赖的模块
    define(function (require, exports, module) {
      //内部变量数据
      let msg = 'module1';
      //内部函数
      function show() {
        return msg;
      }
    
      //向外暴露
      module.exports = {show};
     
    })
    
  • module2.js
    define(function (require, exports, module) {
      module.exports = {
        msg: 'I Will Back'
      }
    })
    
    
  • module3.js
    define(function (require, exports, module) {
      const API_KEY = 'abc123'
      exports.API_KEY = API_KEY
    })
    
  • module4.js
    define(function (require, exports, module) {
      //引入依赖模块(同步)
      var module2 = require('./module2')
      console.log(module2.msg);
    
      //引入依赖模块(异步)
      require.async('./module3', function (m3) {
        console.log('异步引入依赖模块3  ' + m3.API_KEY)
      })
    
      function show() {
        console.log('module4 show());
      }
    
      exports.show = show
    
    })
    
  • main.js : 主(入口)模块
    define(function (require) {
      var m1 = require('./module1')
      var m4 = require('./module4')
      m1.show()
      m4.show()
    })
    //输出的顺序为模块1.2.4.3,因为注意有异步引入依赖模块
    
  1. index.html:
<!--
使用seajs:
  1. 引入sea.js库
  2. 如何定义导出模块 :
    define()
    exports
    module.exports
  3. 如何依赖模块:
    require()
  4. 如何使用模块:
    seajs.use()
-->
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>

四、 ES6

**
在这里插入图片描述
ES6-Babel-Browserify使用教程
在这里插入图片描述
上图为目录结构

  1. 定义package.json文件
{
  "name" : "es6-babel-browserify",
  "version" : "1.0.0"
}
  1. 安装babel-cli, babel-preset-es2015和browserify
    //cli:command line interface ,babel-cli用来使用babel的命令的库
    //babel-preset-es2015,es6打包成es5的库,babel有很多库,而我们目前用到的是这个
  • npm install babel-cli browserify -g
  • npm install babel-preset-es2015 --save-dev
  • preset 预设(将es6转换成es5的所有插件打包)
  1. 定义.babelrc文件 //在根目录下,运行babel之前先要读这个文件 run control运行时需要读的文件
    {
    "presets": ["es2015"] //读到这个知道了自己要做的事是转换es6到es5
    

}
```
4. 编码

  • js/src/module1.js 分别暴露

    export function foo() {
      console.log('module1 foo()');
    }
    export function bar() {
      console.log('module1 bar()');
    }
    export const DATA_ARR = [1, 3, 5, 1]
    
  • js/src/module2.js 统一暴露

    let data = 'module2 data'
    
    function fun1() {
      console.log('module2 fun1() ' + data);
    }
    
    function fun2() {
      console.log('module2 fun2() ' + data);
    }
    
    export {fun1, fun2}
    
  • js/src/module3.js 默认暴露,可以暴露任意数据类型,暴露什么数据接收到的就是什么数据

    //export default()=>{
    //	console.log("我是默认暴露的箭头函数");
    //}
    //此种写法调用:import module3 from(./module3.js);
    //左边module3为形参,可以写xxx
    //module3();//因为此时module3是一个函数
    //默认暴露只能写一次,要暴露多个数据 用对象
    
    export default {
      name: 'Tom',
      setName: function (name) {
        this.name = name
      }
    }
    
  • js/src/app.js

    //引入其他的模块
    //语法: import xxx from '路径'
    
    import {foo, bar} from './module1'
    import {DATA_ARR} from './module1'
    import {fun1, fun2} from './module2' 
    //分别暴露和统一暴露在引入的时候,必须用对象结构赋值的形式。因为模块里的东西很多,只取自己需要的
    //统一暴露和分别暴露被统一称为常规暴露
    
    import person from './module3'
    
    import $ from 'jquery'//第七步中详细解释
    
    $('body').css('background', 'red')
    
    foo()
    bar()
    console.log(DATA_ARR);
    fun1()
    fun2()
    
    person.setName('JACK')
    console.log(person.name);
    
  1. 编译
  • 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/build //会自动形成build文件夹
  • 使用Browserify编译js : browserify js/build/app.js -o js/dist/bundle.js
    //因为执行上步之后会有require,某些浏览器仍然需要编译,不能自动生成dist文件夹,需要自己新建
  1. 页面中引入测试
<script type="text/javascript" src="js/lib/bundle.js"></script>
  1. 引入第三方模块(jQuery)
    1). 下载jQuery模块:
    • npm install jquery@1 --save
      2). 在app.js中引入并使用
    import $ from 'jquery'
    $('body').css('background', 'red')
    

本文为尚硅谷模块化教学总结
几种模块化规范的区别可看https://blog.youkuaiyun.com/Real_Bird/article/details/54869066
commonjs和ES6及requirejs模块循环引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值