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.exports
与exports
的区别?(重点)
找出区别需要了解的知识点:
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简单使用教程
- 下载sea.js, 并引入
- 官网: http://seajs.org/
- github : https://github.com/seajs/seajs
- 将sea.js导入项目: js/libs/sea.js
- 创建项目结构
|-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>