概述
时间:211206-211207
内容
JS模块化的理解
1.什么是模块/模块化?
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起。
块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与外部其它模块通信。
2.为什么要模块化?
- Web sites are turning into
Web Apps - Code
complexity(复杂度)grows as the site gets bigger - Highly
decoupled(解耦)JS files/modules is wanted Deployment(部署)wants optimized(优化) code in few HTTP calls
3.模块化的好处
- 避免命名冲突(减少命名空间污染)
- 更好的分离,按需加载
- 更高复用性
- 高可维护性
4.页面引入加载script
如果直接通过script标签引入多个js文件,会造成:
- 请求过多
- 依赖模糊
- 难以维护
那么,如何才能做到:在享受模块化带来的好处的同时,又能避免模块化带来的弊端呢?模块化规范 应运而生。
模块化的进化史
1 . 最早,我们这么写代码:
整个网站只有一个js文件,全部js代码都写在这一个js文件中。
所有变量/函数都直接定义在全局作用域中。
缺点:Global被污染,很容易命名冲突。
//xxx.js
function foo(){
//...
}
function bar(){
//...
}
2 . 简单封装: Namespace模式
-减少Global上的变量数目
-本质是对象,一点都不安全
var MYAPP = {
foo: function(){},
bar: function(){},
}
MYAPP.foo()
3 . 匿名闭包: IIFE模式
IIFE:立即执行函数
函数是JavaScript唯一的Local Scope
var Module = (function(){
var _private = "safe now"
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
4 . 再增强一点:引入依赖
这就是 模块模式
也是现代模块实现的基石
var Module = (function($){
var _$body = $('body') //we can use JQuery now?
var foo = function(){
console.log(_body) //特权方法
}
//Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo()
模块化规范
CommonJS
规范
说明
- wiki.commonjs.org/wiki/Module…
- 每个js文件都可当作一个模块
- 在服务器端: 模块的加载是运行时同步加载的
- 在浏览器端: 模块需要提前编译打包处理
基本语法
- 暴露模块
module.exports = value
exports.xxx = value
问题: 暴露的模块到底是什么?
- 引入模块
require(xxx) <br>
第三方模块: xxx为模块名
自定义模块: xxx为模块文件路径
实现
-
服务器端实现
Node.js
nodejs.cn/ -
浏览器端实现
Browserify
browserify.org/
也称为CommonJS的浏览器端的打包工具
案例练习1:Cmmonjs模块化规范在服务端(Nodejs)的应用。
-
下载安装node.js
查看node版本:
node -v -
创建项目结构
执行指令
npm init -y,可以快速生成一个项目依赖描述文件 package.json。
-
下载第三方模块
uniq:一个可以对数组去重的包。npm install uniq --save -
模块化编码
小细节:app.js中,先引入第三方库,再引入自定义的模块。
// module1.js
//暴露一个对象
module.exports = {
msg: 'module1',
foo(){
console.log(this.msg)
}
}
//module2.js
//暴露一个函数
module.exports = function(){
console.log('module2')
}
//module3.js
//在暴露的对象上添加两个函数foo、bar
exports.foo = function(){
console.log('foo() module3')
}
exports.bar = function(){
console.log('bar() module3')
}
exports.arr = [1, 2, 1, 2, 3, 4, 5, 5]
//app.js
//导入第三方模块
let uniq = require('uniq')
//导入自定义模块
let module1 = require('./modules/module1.js')
let module2 = require('./modules/module2.js')
let module3 = require('./modules/module3.js')
console.log(module1.msg)
module1.foo()
module2()
module3.foo()
module3.bar()
console.log(module3.arr)
console.log(uniq(module3.arr))
-
执行app.js
命令行通过node执行入口文件:
node app.js
小技巧:Tab键可以自动补全文件名。
输出的结果:
案例练习2:Cmmonjs模块化规范在浏览器端的应用。
Browserify模块化使用教程
1.创建项目结构
dist/或build/ 都表示打包输出目录。
src/ 表示源文件目录。
2.下载 browserify
-全局: npm install browserify@14.5.0 -g
-局部: npm install browserify@14.5.0 --save-dev
视频中browserify版本是:@14.5.0
注意:
1.browserify需要同时全局安装和局部安装才能使用,比较特别。
2.注意安装第三方包时,要区分开发依赖和运行依赖。
3.定义模块代码
//app.js
//导入第三方模块
let uniq = require('uniq')
//导入自定义模块
let module1 = require('./module1.js')
let module2 = require('./module2.js')
let module3 = require('./module3.js')
console.log(module1.msg)
module1.foo()
module2()
module3.foo()
module3.bar()
console.log(module3.arr)
console.log(uniq(module3.arr))
4.打包处理js
命令行执行指令: browserify ./src/app.js -o ./dist/bundle.js
./src/app.js 打包入口文件
./dist/bundle.js 打包输出文件
5.页面使用引入
<script src="./dist/bundle.js" type="text/javascript" charset="utf-8"></script>
在浏览器中打开index.html,F12打开控制台,输出如下:
AMD
规范
说明
- Asynchronous Module Definition(异步模块定义)
- github.com/amdjs/amdjs…
- 专门用于浏览器端,模块的加载是异步的。
基本语法
- 定义暴露模块
//定义没有依赖的模块
define(function(){
return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
return 模块
})
- 引入使用模块
require(['module1', 'module2'], function(m1, m2){
//使用m1/m2
})
实现(浏览器端)
- Require.js
- www.requirejs.cn/
- www.ruanyifeng.com/blog/2012/1…
案例1:不使用AMD规范,如何在浏览器端实现模块化?IIFE。
1.编写模块
//a.js
//定义一个没有依赖的模块
(function(window){
let name = 'a.js'
function getName(){
return name
}
window.a = { getName }
})(window)
//b.js
//定义一个有依赖的模块
(function(window, a){
let msg = 'b.js'
function showMsg(){
console.log(msg)
console.log(a.getName())
}
window.b = {showMsg}
})(window, a)
//app.js
(function(b){
b.showMsg()
})(b)
2.页面引入
//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./src/a.js" type="text/javascript" charset="utf-8"></script>
<script src="./src/b.js" type="text/javascript" charset="utf-8"></script>
<script src="./src/app.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
浏览器打开index.html,console控制台输出如下:
IIFE模块化 存在的缺点:a.js、b.js、app.js必须依次引入,顺序不能混乱,否则会报错。
案例2:使用AMD规范——自定义模块。
1.下载require.js,并引入
- 官网: www.requirejs.cn/
- github: github.com/requirejs/r…
- 将require.js导入项目: js/libs/require.js
2.创建项目结构
libs/ 存放第三方包的目录
3.编写自定义模块代码
//a.js
//定义一个没有依赖的模块
define(function(){
let name = 'a.js'
function getName(){
return name
}
//暴露模块
return {getName}
})
//b.js
//定义一个有依赖的模块
define(['a'], function(a){
let msg = 'b.js'
function showMsg(){
console.log(msg)
console.log(a.getName())
}
//暴露模块
return {showMsg}
})
4.编写入口文件 app.js 代码
(function(){
requirejs.config({
//baseUrl: 'src/modules', //基本路径 相对于根目录的路径
//配置 模块名和模块路径 之间的映射关系
paths: { //相对于入口文件的路径
a: './modules/a', //不要加.js后缀
b: './modules/b'
}
});
requirejs(['b'], function(b){
b.showMsg()
})
})()
requirejs.config()方法:用来配置自定义模块的模块名和模块路径的映射关系。
baseUrl选项:配置公共的基本路径。
该选项值一般设置成自定义模块的存放目录,从根路径出发。
如果配置了该选项,则会默认从该目录中寻找依赖。
paths选项:配置每个自定义模块的具体的映射关系。从相对于入口文件出发。如果配置了baseUrl选项,则会在paths选项路径前面拼接上baseUrl选项路径。
baseUrl选项、paths选项 都是用来配置依赖的查找路径的。可以单独使用其中一个,也可以配合在一起使用。
注意: RequireJS会自动拼接上.js后缀,所以paths选项路径只要写js文件名。
5.html中引入require.js,并通过data-main属性指定入口文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script src="libs/require.js" data-main="src/app.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
在浏览器中,打开index.html文件,输出如下:
案例3:使用AMD规范——第三方模块。
如何在AMD模块中,使用第三方模块,如jquery?
先将jquery源码js文件下载到项目中,再在入口文件app.js中配置jquery模块的路径映射关系,最后在需要使用的模块中引入和使用即可。
//app.js
(function(){
requirejs.config({
paths: {
a: './modules/a',
b: './modules/b',
jquery: '../libs/jquery-3.6.0.min' //配置jQuery库的路径映射关系
}
});
//导入、使用jQuery
requirejs(['b', 'jquery'], function(b, $){
b.showMsg()
$('body').css('backgroundColor', 'red')
})
})()
注意:jQuery内部会判断自己是否是在AMD模块中使用,是则暴露出去的名字是小写的jquery。所以在paths选项配置路径映射关系时,key不能写成jQuery。
jquery: '../libs/jquery-3.6.0.min' //正确
jQuery: '../libs/jquery-3.6.0.min' //错误
浏览器效果:
CMD(了解即可)
规范
说明
- Common Module Definition(通用模块定义)
- github.com/seajs/seajs…
- 专门用于浏览器端,模块的加载是异步的
- 模块使用时才会加载执行
基本语法
- 定义暴露模块
//定义没有依赖的模块
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()
})
实现(浏览器端)
- Sea.js
- www.zhangxinxu.com/sp/seajs/
案例1
1.下载Sea.js,并引入
- 官网: seajs.org/
- github: https:/lgithub.com/seajs/seajs
- 将sea.js导入项目: js/libs/sea.js
2.创建项目结构
3.使用CMD语法编写自定义模块
一共有4个自定义模块,module1.js、module2.js、module3.js都是无依赖模块,module4.js 依赖了module1.js、module2.js这两个模块。
入口文件 app.js中 引入了module3.js、module4.js这两个模块。
//module1.js
//定义没有依赖的模块
define(function(require, exports, module){
let msg = 'module1'
function foo(){
return msg
}
//暴露模块
module.exports = {foo}
})
//module2.js
//定义没有依赖的模块
define(function(require, exports, module){
let msg = 'module2'
function bar(){
console.log(msg)
}
//暴露模块
module.exports = bar
})
//module3.js
//定义没有依赖的模块
define(function(require, exports, module){
let msg = 'module3'
function func(){
console.log(msg)
}
//暴露模块
exports.func = func
})
//module4.js
define(function(require, exports, module){
let msg = 'module4'
function func2(){
console.log(msg)
}
//同步引入模块
let module1 = require('./module1')
console.log(module1.foo())
//异步引入模块
//async()方法 参数1:依赖的路径 参数2:依赖加载完成时的回调函数
require.async('./module2', function(module2){
module2()
})
//暴露模块
exports.func2= func2
})
入口文件 app.js
define(function(require){
let module3 = require('./module3')
module3.func()
let module4 = require('./module4')
module4.func2('')
})
4.html引入sea.js,执行seajs.use()方法
seajs.use()方法: 参数为入口文件的路径。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="js/libs/sea.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
seajs.use('./js/modules/main.js')
</script>
</body>
</html>
浏览器中打开index.html文件,控制台输出如下:
ES6(重点掌握)
规范
说明
- es6.ruanyifeng.com/#docs/modul…
- 依赖模块需要编译打包处理
基本语法
-
导出模块: export
-
引入模块: impors
实现(浏览器端)
- 使用
Babel将ES6编译为ES5代码 - 使用
Browserify编译打包js
初体验:ES6-Babel-Browserif使用教程
1.定义package.json文件
2.安装babel-cli, babel-preset-es2015和browserify
- npm install babel-cli browserify -g
- npm install babel-preset-es2015 --save-dev
- preset预设(将es6转换成es5的所有插件打包)
3.定义 .babelrc 文件
{
"presets": ["es2015"]
}
4.编码
//module1.js
//暴露模块 分别暴露
export function foo(){
console.log('foo() module1.js')
}
export function bar(){
console.log('bar() module1.js')
}
export let arr1 = [1,2,3]
//module2.js
//暴露模块 统一暴露
function func1() {
console.log('func1() module2.js')
}
function func2() {
console.log('func2() module2.js')
}
let arr2 = [1, 2, 3]
export {
func1,
func2,
arr2
}
//module3.js
//暴露模块 默认暴露
export default () => {
console.log('module3.js 默认暴露 的箭头函数')
}
main.js
//引入其它模块
//语法: import {...} from '路径'
import {
foo,
bar,
arr1
} from './module1.js'
import {
func1,
func2,
arr2
} from './module2.js'
import module3 from './module3.js'
foo()
bar()
console.log(arr1)
func1()
func2()
console.log(arr2)
module3()
5.编译
-
使用Babel将ES6编译为ES5代码(但包含CommonJs语法) :
babel src/js -d build/ -
使用Browserify编译js :
browserify build/main.js -o dist/bundle.js
6.页面中引入测试
<script src="../dist/bundle.js" type="text/javascript" charset="utf-8"></script>
7.在浏览器中打开index.html,控制台输出如下:
ES6 Module规范中,如何使用第三方模块?
1.安装jquery
npm install jquery@1
2.引入第三方模块
//app.js
import $ from 'jquery'
...
$('body').css('backgroundColor', 'red')
浏览器效果:
总结/问题
一些总结
-
在node端,可以直接使用Commonjs模块化规范进行模块化开发。开发完成后,直接通过node来执行入口js文件即可。
-
浏览器不认识Commonjs模块化语法,需要使用rowserify工具对源代码js进行打包处理后才能,通过script标签引入打包输出js文件到html页面中运行。 -
源代码 js文件可以使用Commonjs模块化语法进行编写,但是如果要在浏览器中运行,则需要先使用Browserify工具 对源代码进行处理,处理后输出的js文件就可以在浏览器中运行了。 -
区别Node与Browserify
Nodejs:可以直接执行 Commonjs模块化语法编写的js文件。
Browserify:将 Commonjs模块化语法编写的js文件进行打包处理,输出一个新的js文件,新js文件可以在浏览器中运行。 -
所有的第三方库都可以在AMD模块中使用吗?
不是,第三方库自身使用AMD语法进行开发,才行。
jQuery内部支持了AMD规范,而有的库不支持。不是所有的库都支持AMD规范。 -
CMD结合了CommonJS和AMD两种规范。 -
为什么需要全局按Babel-cli呢?
CLI: command line interface 命令行接口。
一般cli都是用来为第三方包提供全局指令的。
全局安装nodejs后,在任意目录都可以执行node指令,这是因为node内置了 node-cli,node-cli的作用是提供全局可用的node指令。
但是,babel-preset-es2015 内部没有内置CLI,所以需要额外全局安装Babel-cli,来提供全局可用的babel指令。
-
babel-preset-es2015 的作用?
Babel不仅可以将ES6语法转换为ES5语法,还可以转换其它语法(如React的JSX)。不同的语法需要使用不同的插件来转换。 而babel-preset-es2015的作用就是只将转换ES6语法的插件都下载下来使用。 -
为什么要定义
.babelrc文件?
.babelrc文件 本质是一个json文件。Babel工作时会先去读取
.babelrc这个配置文件,presets数组中的字符串"es2015"告诉Babel要去转换ES6的语法。即可以通过手动为presets数组添加不同元素的方式来告诉Babel它要去进行哪些处理。rc: run control 运行控制
rc后缀的文件:一般表示运行时控制文件,即运行时需要读的文件。 -
Babel将ES6模块化语法转换为CommonJS模块化语法,所以还需要使用Browserify将CommonJS模块化语法转换为浏览器认识的语法。 -
BabelvsBrowserify它们处理js文件时有什么区别?
Babel会一一处理每个js文件,然后一一单独输出。Browserify会从入口文件出发,将依赖的所有模块的代码都打包到一个文件中,最终只输出一个文件。相同点:如果指定的输出目录不存在,则会自动创建。
-
ES6 Module规范中,导入模块时的注意事项:
一个模块如果是通过
分别暴露或统一暴露语法向外暴露变量/函数时,在导入这个模块时必须使用对象解构赋值的方式来进行导入。import from表达式语法: import {...} from '路径'
//错误的导入方式:不能直接赋值为一个变量。
import module1 from './module1.js'
import module2 from './module2.js'
//正确的导入方式:通过对象解构赋值语法分别接收导出的变量/函数。
import {
foo,
bar,
arr1
} from './module1.js'
import {
func1,
func2,
arr2
} from './module2.js'
-
ES6模块化开发时,如果修改了源代码,则需要重新执行Babel和Browserify指令,重新进行转换、打包,才能看到最新的代码效果。
-
ES6 Module规范中,有三种方式可以向外暴露模块:分别暴露、统一暴露、默认暴露。
不同的暴露方式,引入的方式也有所不同。
默认暴露:可以暴露任意数据类型,暴露什么数据接收到的就是什么数据。
分别暴露、统一暴露:export xxx
默认暴露:export default xxx引入分别暴露、统一暴露的模块:import {xxx, xxx, ...} from 'xxx'
xxx不能随意写,必须使用依赖模块内部起的名字。引入默认暴露的模块:import xxx from 'xxx'
xxx可以随意起。 -
jQuery不同版本的区别?
jQuery2.x、3.x版本不支持低版本浏览器。 在开发中,一般使用jQuery 1.x版本,因为它兼容低版本的浏览器。
-
npm安装包时,如何指定版本?
npm安装第三方包时,如果不指定版本,则默认安装最新的版本。
npm install jquery
如何指定版本呢?可以通过@来指定版本。
npm install jquery@1 //安装1.x系列中最新的版本
npm install jquery@1.11.3 //安装1.11.3版本
-
package-lock.json 文件有什么作用?
使用npm下载第三方包时,npm会自动在项目根目录下创建一个文件package-lock.json。
package-lock.json:简单来说就是锁定安装模块的版本号。
参考:
学习资源
官方文档
-
CommonJS
官网主页Commonjs
wikiCommonJS
npm -
RequireJS
官网主页RequireJS
npmRequireJS
github -
Sea.js
官网主页Sea.js
npmSea.js
中文 -
Browserify
官网主页browserify
npm -
uniq
npm -
jQuery
官网主页 -
Babel
中文文档
视频教程
- 尚硅谷JS模块化教程(js模块化精讲含commonjs、AMD、ES6、CMD规范)
视频录制时间:2017/11/8
教程概述:
当项目功能越来越多,代码量便也会越来越多,后期的维护难度会增大,此时在JS方面就会考虑使用模块化规范去管理。
本视频内容涵盖:理解模块化,为什么要模块化,模块化的优缺点以及模块化规范。并且将带领大家学习开发中非常流行的commonjs, AMD, ES6、CMD规范。
建议同学们学习完模块化规范以后再学习项目构建,以更好的武装自己的技能。