一分钟了解模块化历程

1,117 阅读1分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

模块化

什么是模块?

  • 将JS按照一定的要求比如说功能拆分成多个JS,除了向外部暴露的数据以外,外部无法访问内部没有暴露的数据,我们这里称为私有数据。

全局function模式

最开始没有模块化标注的时候我们使用的是全局的function模式,例子如下

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  foo()
  bar()
    //foo() change data
    //module1.js:10 bar()
</script>
//module1.js
function foo() {
    console.log('foo()')
}
function bar() {
    console.log('bar()')
}

//module2.js
let data2 = 'change data'
function foo() {  //与另一个模块中的函数命名冲突
  console.log(`foo() ${data2}`)
}

缺点 :不同功能的代码在不同的js文件中,多人开发中容易出现与其他人的函数命名冲突

Namespace模式

为了防止命名冲突 提出了命名空间的模块化方法 具体使用方法如下

//module1.js
let myModule = {
  data: 'data1',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

//module2.js
let myModule2 = {
  data: 'data2',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  myModule.foo()
  myModule.bar()

  myModule2.foo()
  myModule2.bar()

  myModule.data = 'other data' //能直接修改模块内部的数据
  myModule.foo() //other data
</script>

通过保存函数到对象中形成的namespace可以高效率的减少命名冲突的问题,有可能此处几十个函数只会封装成俩个命名空间,但是namespace的缺点是内部的数据可能会被外部修改

IIFE

为了解决外部修改问题 又出现了可以引入依赖的IIFE的模块化方式,该方式为现代模块化的基石,既能引入依赖,又能解决外部修改内部值的问题

(function (window, $) {
  let data = 'data1'
  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)//引入依赖
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  myModule.foo()
  myModule.bar()
  myModule.otherFun()  //myModule.otherFun is not a function
  console.log(myModule.data) //undefined 不能访问模块内部数据
  myModule.data = 'xxxx' //不是修改的模块内部的data
  myModule.foo() //没有改变 无法访问也无法修改
</script>

但是多个script引入模块的方法会引起一些问题:多个scrpit发送多个请求、各个script之间的相互依赖会造成依赖模糊的现象,修改其中一个script可能会引起牵一发而动全身的效果,导致问题出现

为了对以上模块化进行改善,制定了模块化标准

模块化规范

CommonJs规范

//使用方法
1. 定义暴露模块:
  module.exports = value;
  exports.xxx = value;
2. 引入模块:
  var module = require(模块名或模块路径);
//modules1.js
module.exports = {
  foo() {
    console.log('moudle1 foo()')
  }
}
//modules2.js
module.exports = function () {
  console.log('module2()')
}
//modules3.js
exports.foo = function () {
  console.log('module3 foo()')
}

exports.bar = function () {
  console.log('module3 bar()')
}
//app.js
//引用模块
let module1 = require('./module1')
let module2 = require('./module2')
let module3 = require('./module3')

let uniq = require('uniq')//引入外部模块

//使用模块
module1.foo()
module2()
module3.foo()
module3.bar()

//html
<!--<script type="text/javascript" src="js/src/app.js"></script>>
//ReferenceError: require is not defined 浏览器无法识别语法


如何打包

npm install browserify -g //下载打包工具

browserify js/src/app.js -o js/dist/bundle.js   将我们的app.js路径打包到dist下面的js中
//html

//需要使用工具进行打包 
<script type="text/javascript" src="js/dist/bundle.js"></script>

AMD规范

AMD需要依赖require.js,声明两个模块,模块之间存在依赖,alerter.js返回一个showMsg是依赖了jq的函数,dataService.js抛出getMsg函数模块

//alerter.js
define(['dataService', 'jquery'], function (dataService, $) {
  let name = 'Tom2'

  function showMsg() {
    $('body').css('background', 'gray')
    alert(dataService.getMsg() + ', ' + name)
  }

  return {showMsg}
})
//dataService.js
define(function () {
  let msg = 'atguigu.com'

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

  return {getMsg}
})

在main.js中通过require.js这个基础的conifg中来配置引入我们已经写好的模块,配置好在后面我们可以看出alerter函数可以使用了

//main.js
(function () {
  //配置
  require.config({
    //基本路径
    baseUrl: 'js/',
    //映射: 模块标识名: 路径
    paths: {
      //自定义模块
      'alerter': 'modules/alerter',
      'dataService': 'modules/dataService',

      //库模块
      'jquery': 'libs/jquery-1.10.1',
      'angular': 'libs/angular'
      
    },

    //配置不兼容AMD的模块
    shim: {
      angular: {
        exports: 'angular'
      }

    }
  })

  //引入模块使用
  require(['alerter', 'angular'], function (alerter, angular) {
    alerter.showMsg()
    console.log(angular);
  })
})()
 <script type="text/javascript" src="js/libs/require.js" data-main="js/main.js"></script>

CMD

CMD也是模块化规范的一部分,写法比较类似于CommonJsAMD写法的结合,这里暂时不做具体使用

ES6

ES6模块语法

//modules1.js
export function foo() {
  console.log('module1 foo()');
}

export let bar = function () {
  console.log('module1 bar()');
}

export const DATA_ARR = [1, 3, 5, 1]
//modules2.js
let data = 'module2 data'

function fun1() {
  console.log('module2 fun1() ' + data);
}

function fun2() {
  console.log('module2 fun2() ' + data);
}
export {fun1, fun2}
//modules3.js
export default {
  name: 'Tom',
  setName: function (name) {
    this.name = name
  }
}
//app.js
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);

在此处我们的app.js仍然无法直接在浏览器中进行使用,因为浏览器识别不了ES6的写法,我们需要通过babel转换工具对ES6转化为ES5

image.png

 babel js/src -d js/lib 将src下的文件转换语法 打包到lib文件夹下`将ES6转换成ES5` 
 但转换完仍然包含require语法浏览器还是无法识别require

babel转换后的app.js仍然包含require语法 那么参考上面的模块化使用browserify来解决 image.png

browserify js/lib/app.js -o js/lib/bundle.js

//html
<script type="text/javascript" src="js/lib/bundle.js"></script>

至此通过最终转换出来的bundle.js 我们就可以在浏览器编译我们ES6模块化的代码