JS模块化CommonJS、ES6模块化 、AMD、CMD知识总结

1. 什么是模块化?

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

2.模块的进化史

1.最早的代码风格,全局被污染,容易造成命名冲突。

function foo(){
    //...
}

function bar(){
    //...
}

2.简单封装:namespace模式。虽然减少了全局变量的数目,但本质是对象,不安全。

var myApp={
    foo:function(){
        //...
    },
    bar:function(){
        //...
    }
}

myApp.foo()

3.匿名闭包:IIFE模式,立即执行函数是局部作用域,不污染全局作用域。

var module=(function(){
    var _private="安全作用域";
    var foo=function(){
        console.log(_private)
    }
    return {
        foo
    }
})()

module.foo();
module._private;//undefined

4.在立即执行函数中引入依赖:这也是现代模块实现的基石。

var module = (function ($) {
  var _$body = $('body')
  var foo = function () {
    console.log(_$body)
  }
  return {
    foo,
  }
})(jQuery)

module.foo()

模块化的好处:

  • 避免命名冲突,减少命名空间污染
  • 更好的分离,按需加载
  • 更高的复用性
  • 更容易维护

缺点:

  • 由于一个文件被分割成多个文件,造成页面加载时需要更多的请求数,而往往我们需要减少发起请求的数量。
  • 依赖模糊,拆分的文件往往需要按照一定的顺序引入,这容易造成犯错。
  • 难以维护

3.CommonJS

说明:

  • 服务器端:模块的加载是运行时同步加载
  • 浏览器端:模块需要提前编译打包处理

基本语法

暴露模块:

  • module.exports=value
  • exports.xxx=value

引入模块:

  • require(xxx)

注意:

  • 当引入的模块是第三方模块,则xxx是模块名,比如(const jQuery = require('jQuery'))
  • 当引入的模块是自定义模块,则xxx是自定义模块的路径,比如(let myModule = require('./myModule.js'))

CommonJS常用于NodeJs中。

示例:

创建四个文件:module1.js、 module2.js、module3.js、app.js文件

module1.js

function foo() {
  console.log('module1  foo()')
}

module.exports = {
  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用于引入模块

const module1 = require('./module1') //const { foo } = require('./module1');//使用了es6语法,进行对象解构
const module2 = require('./module2')
const module3 = require('./module3') //const {foo,bar} = require('./module3')

module1.foo()
module2()
module3.foo()
module3.bar()

面试:module.exportsexports的区别?(重点)

找出区别需要了解的知识点:

1.在js模块中,有一个全局对象module,该module对象有个exports属性,exports属性初始默认值是一个空对象。即初始化时:module.exports = { }。
2.那exports是什么呢?在底层源码中,只是简单的exports = module.exports = { } ,即exports和module.exports是指向同一个堆地址。
3. 那么exports.foo = function() { xxx },实际上就是往module.exports指向的空对象中添加foo属性。
4.如果在一个模块中只是使用exports去暴露模块,那么module.exports与exports是相同的,没有什么区别,即exports是module.exports的一个引用。

特别注意:const xxx = require('./module.js') 中的xxx指的是什么呢?

无论模块中是使用exports.foo 暴露模块,还是使用module.exports去暴露模块,上面的xxx都只能是module.exports所指的内容,而不会是exports所指的内容。

思考问题

  • 问题一:为什么前面说一个模块中只是使用exports去暴露模块,exports与module.exports才会相同,难道使用module.exports去暴露模块,exports就不等于module.exports了吗?
  • 问题二:上面不是说exports与module.exports指向相同的地址吗?那么为什么还要关心require引入的xxx只能是module.exports。

答案:

在前面三个模块中,module1与module2两个模块,exports !== module.exports。而module3模块,exports === module.exports。

原因:(关键看module.exports有没有改变指针指向)

  • module1中:明显module.exports 使用了=号指向了一个新的对象,对象有个foo属性,而exports还是指向原本的堆地址,那么exports !== module.exports
	module.exports = {
	  foo,
	}
  • module2中:同理,module.exports指向了一个函数,函数也是一个对象,也改变了地址
  • module3中:只是使用了exports.xxx,即module.exports没有改变指向,即exports === module.exports

4.ES6模块化

支撑ES6模块的两个主要新关键字是import和export。它们在语法上有许多微妙之处,所以我们来深入了解一下。

注意:

这里有一个很容易被忽略的重要细节:import和export都必须出现在使用它们的最顶层作用域。举例来说,不能把imort或export放在if条件中;它们必须出现在所有代码块和函数的外面。

4.1 导出export

export关键字或者是放在声明的前面,或者是作为一个操作符与一个要导出的绑定列表一起使用。

命名导出:

//export放在声明前面
export function foo() {
	//...
}
export var awesome = 42;

//export 与要导出的绑定列表一起使用
var bar = [1,2,3];
export { bar }

下面是同样导出的另外一种表达形式:

function foo() {
	//...
}
var awesome = 42;
var bar = [1,2,3];
export { foo,awesome,bar } ;//导出绑定列表

命名导出时还可以“重命名”(即别名)一个模块的成员:

function foo(){
}
export { foo as bar};

注意

  • 导入这个模块时,只有成员名称bar可以导入;foo还是隐藏在模块内部,即import { foo }是不可行的。
  • 命名导出可以取别名,在导入时也可以取别名,在导入时必须与你导出时的名称一一对应。

考虑(注意)

var awesome = 42;
export { awesome };

awesome = 100

导入这个模块时,不管是在awesome = 100 之前还是之后,一旦赋值发生,导入的绑定就会决议到100而不是42.

默认导出:

每个模块只能有一个default,使用默认导出可以在导入模块时重命名。

关于默认导出有一个微妙的细节需要格外小心。比较下面两端代码:

function foo(){}
export default foo;
--------------------------------------
function foo(){}
export { foo as default }  //可以把它当成命名导出时取别名

微妙的细节:

  • 上面说了使用命名导出:导入这个模块时,不管是在awesome = 100 之前还是之后,一旦赋值发生,导入的绑定就会决议到100而不是42.即新值覆盖旧值.
  • 默认导出:如果之后你再给foo附一个不同的值,模块导入得到的仍然是原来导出的函数,而不是新的值。即新值不会覆盖旧值
  • 命名导出可以使用export var foo = … (或者 let 或者 const) ,但是默认导出只能export default function foo(){},而不能是export default var foo = … (或者 let 或者 const)

因为一个模块只能有一个default,所以你可能会忍不住把模块设计成默认导出一个对象

export default {
	foo,
	bar
}

但这是官方不建议采用的。如果你想导出多个你可以这样:

export default function foo(){}
export function bar(){}
export function baz(){}

即一个单独的默认导出,其它采用命名导出。

你也可以再次导出某个模块的导出,就像这样:

export {foo , bar} from "baz";
export {foo as FOO,bar as BAR } from "baz";
export * from "baz";

即先从“baz”模块导入,然后显示列出它的成员再从你的模块导出。但这些形式中,“baz”模块的成员不会导入到你的模块的局部作用域中,它们就像不留痕迹地穿过。

4.2 导入import

对于命名导出:

import {foo , bar , baz} from "foo";import {foo as FOO,bar as BAR,baz as BAZ} from "foo"

注意:对于命名导出的模块,导入时需要用{ }包裹。

对于默认导出:

export default function foo(){}
import foo from "foo" || import xxx from "foo" xxx可以是任意名,即别名

当你的模块同时使用命名导出和默认导出时:

export default function foo(){}

export function bar(){}
export function baz(){}


导入这个模块的默认导出和它的两个命名导出:
import FOOFN, { bar , baz as BAZ } from “foo”;

FOOFN(); //FOOFN是默认导出foo的别名
bar();
BAZ();

命名空间导入:

export function bar() {}
export var x =42;
export function baz() {}
export default function foo() {}

//全部导入可以用命名空间导入方式,那么就不需要一个个全导入
import fooFun , * as hello from "foo"
fooFun();
hello.default()//fooFun === hello.default
hello.x;
hello.bar();
hello.baz();import * as hello from "foo"也引入了默认导出,hello.default()调用即可。

5.AMD(Asynchronous Module Definition 异步模块定义)

说明:

  • 专门用于浏览器端,模块的加载是异步的。
  • require.js实现

基本语法

定义模块

  • 定义没有依赖的模块:
define(function(){
	return 模块
})
  • 定义有依赖的模块:
define(['module1','module2'],function(m1,m2){
	return 模块
})

引入模块

require(['module1','module2'],function(m1,m2){
	使用m1/m2模块
})
require.js使用教程

1.下载require.js, 并引入

  • 官网: http://www.requirejs.cn/
  • github : https://github.com/requirejs/requirejs
  • 将require.js导入项目: js/libs/require.js

2.创建项目结构

  |-js
    |-libs
      |-require.js
    |-modules
      |-alerter.js
      |-dataService.js
    |-main.js
  |-index.html

3.定义require.js的模块代码

  • dataService.js
   define(function () {
      let msg = 'atguigu.com'
    
      function getMsg() {
        return msg.toUpperCase()
      }
    
      return {getMsg}
    })
  • alerter.js
    define(['dataService', 'jquery'], function (dataService, $) {
      let name = 'Tom2'
    
      function showMsg() {
        $('body').css('background', 'gray')
        alert(dataService.getMsg() + ', ' + name)
      }
    
      return {showMsg}
    })

4.应用主(入口)js: main.js

  (function () {
    //配置
    requirejs.config({
      //基本路径
      baseUrl: "js/",
      //模块标识名与模块路径映射
      paths: {
        "alerter": "modules/alerter",
        "dataService": "modules/dataService",
      }
    })
    //引入使用模块
    requirejs( ['alerter'], function(alerter) {
      alerter.showMsg()
    })
  })()

5.index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AMD示例</title>
  </head>
  <body>
    <script data-main="js/main.js" src="js/libs/require.js"></script>
  </body>
</html>

6.CMD (Common Module Definition 通用模块定义)–少用

说明:

  • 专门用于浏览器端,模块的加载是异步的。
  • sea.js实现
  • CMD是common.js与AMD的集合

基本语法

定义暴露模块:

  • 定义没有依赖的模块
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 m3 = require('./module3')
	//使用m1/m3
})
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) {
      //内部变量数据
      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()
    })
  1. index.html:
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
    seajs.use('./js/modules/main')
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值