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文件中









   
//引入app.js会出错,需要用browserify编译打包,即上一步的处理,
//才能将commonjs规范应用到浏览器端

三、AMD

JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第4张图片定义有依赖的模块是显式声名依赖注入

1.noAMD
不用AMD自己写
文件结构
JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第5张图片

  • 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



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

2.AMD规范

require.js使用教程

  1. 下载require.js, 并引入
  • 官网: http://www.requirejs.cn/
  • github : https://github.com/requirejs/requirejs
  • 将require.js导入项目: js/libs/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}
    })
    
    JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第6张图片
    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

JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第7张图片JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第8张图片
CMD兼具了AMD和commonJS的一些特点

sea.js简单使用教程

  1. 下载sea.js, 并引入
  • 官网: http://seajs.org/
  • github : https://github.com/seajs/seajs
  • 将sea.js导入项目: js/libs/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:



四、 ES6

**
JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第9张图片
ES6-Babel-Browserify使用教程
JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解_第10张图片
上图为目录结构

  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. 页面中引入测试

  1. 引入第三方模块(jQuery)
    1). 下载jQuery模块:
    • npm install jquery@1 --save
      2). 在app.js中引入并使用
    import $ from 'jquery'
    $('body').css('background', 'red')
    

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

你可能感兴趣的:(JS中的模块化,CommenJS、AMD、CMD和ES6中的模块化详解)