Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javaScript经典系列之前端模块化 #16

Open
wangzhenggui opened this issue Mar 4, 2019 · 1 comment
Open

javaScript经典系列之前端模块化 #16

wangzhenggui opened this issue Mar 4, 2019 · 1 comment

Comments

@wangzhenggui
Copy link
Owner

wangzhenggui commented Mar 4, 2019

前言

在写这篇文章之前,前端模块化这个词在我身边出现了很多次,但是也仅仅知道这个词的表面意思,从来没有深入的去探索一下他们,今天我要从头开始研究下这个模块化的过程和这期间的产物!

什么是模块化

将一个复杂的程序拆分成不同的功能模块,并进行组合在一起实现一个功能。模块的数据是私有的,通过暴露接口和属性与其他模块通信

进化过程

  • 全局function方式:在JS早期的代码中我们可以看到大量的全局变量和函数,这样当项目大了,功能比较多的时候,这样维护起来就非常难维护,会造成命名重复,数据污染等一系列问题。
  • 闭包的方式:函数自调用,内部暴露成员绑定在window对象上供其他模块共享,这样当方法多了,可能造成在window上绑定的对象被覆盖
  • 函数式的参数传递方式:比如我这个模块需要引用到Jquery中的对象,那么把Jquery对象通过参数的形式传递进去才可以,这样就需要在每次引用这个模块的时候都需要引用Jquery模块,造成资源浪费

模块化的好处

  • 避免命名冲突和数据污染
  • 按需加载
  • 代码可读性和可维护性
  • 模块的复用性

引入<Script></Script>标签后出现的问题

1、加载多个模块需要引入多个script标签,这样会发送多个请求,造成资源浪费
2、多个模块直接的依赖关系不明确,不知道之间引用关系如何
3、难以维护,由于不知道依赖之间的关系如何,所以我们项目代码中可能会出现牵一发而动全身的情况

前端模块化规范

CommonJS
1、简介
我们经常使用的Node.js的使用规范就是CommonJS模块规范。以文件为单位,每个文件就是一个模块,
有自己的作用域,文件里面定义的的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

2、特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

3、基本语法
暴露模块: module.exports = XXX; XXX 是模块名称
引入模块: require('XXX'); XXX是模块名称
⚠️注意:这里的模块分为第三方模块和自定义模块,自定义模块就直接写相对路径名称进行引用

4、不同环境下实现
服务端实现(Node): 直接写一个入口JS,JS中引用模块,然后执行node xx.js 即可以运行
客户端实现(浏览器):需要通过编译之后才可以在浏览器上运行,我们可以使用Browserify这个插件来编译代码,然后html中引用编译后的js文件即可运行。

AMD
1、简介
CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要来着早。
2、基本语法
暴露模块:

//定义没有依赖的模块
define(function(){
   return 模块
})

//定义有依赖的模块 module1 module2 是模块名称
define(['module1', 'module2'], function(m1, m2){
   return 模块
})

引用模块:

require(['module1', 'module2'], function(m1, m2){
   使用m1/m2
})

3、使用方式

  • 下载require.js, 并引入 require.js
  • 创建项目结构
|-js
  |-libs
    |-require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html
  • 定义require.js的模块代码
// dataService.js文件
// 定义没有依赖的模块
define(function() {
  let msg = 'www.baidu.com'
  function getMsg() {
    return msg.toUpperCase()
  }
  return { getMsg } // 暴露模块
})
//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
  let name = 'Tom'
  function showMsg() {
    alert(dataService.getMsg() + ', ' + name)
  }
  // 暴露模块
  return { showMsg }
})

// main.js文件
(function() {
  require.config({
    baseUrl: 'js/', //基本路径 出发点在根目录下
    paths: {
      //映射: 模块标识名: 路径
      alerter: './modules/alerter', //此处不能写成alerter.js,会报错
      dataService: './modules/dataService'
    }
  })
  require(['alerter'], function(alerter) {
    alerter.showMsg()
  })
})()
  • 页面引入require.js模块:
    在index.html引入 <script data-main="js/main" src="js/libs/require.js"></script>

4、小结
AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。

CMD
1、简介
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

2、基本用法
暴露模块:

//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})

//定义有依赖的模块
define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖模块(异步)
    require.async('./module3', function (m3) {
    })
  //暴露模块
  exports.xxx = value
})

引用模块:

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})

3、使用方式

  • 下载sea.js, 并引入seajs
  • 创建项目结构
|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html
  • 定义sea.js的模块代码
// module1.js文件
define(function (require, exports, module) {
  //内部变量数据
  var data = 'atguigu.com'
  //内部函数
  function show() {
    console.log('module1 show() ' + data)
  }
  //向外暴露
  exports.show = 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')
  function show() {
    console.log('module4 show() ' + module2.msg)
  }
  exports.show = show
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
})
// main.js文件
define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})
  • 在index.html中引入
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>

ES6模块化
1、简介
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

2、基本用法
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

3、使用方法
再使用ES6的模块化的时候,我们需要babel将ES6的代码编译成ES5的代码,然后浏览器才能执行。
如果使用babel,这里不做陈述来,可以自行百度下。

总结

1、CommonJS主要用于服务端编程,加载模块是同步的,但这并不适合浏览器,浏览器加载是异步的,所以出现了AMD、CMD等异步加载方案
2、AMD规范是异步加载模块的,可以并行加载多个模块,但是AMD的开发成本高,代码阅读和书写都比较麻烦,语意化不够顺畅
3、CMD规范与AMD规范很相似,都用于浏览器编程,但是模块的加载逻辑偏重
4、ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

@wangzhenggui
Copy link
Owner Author

本文参考前端工匠的博文: ljianshu/Blog#48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant