JS模块化
一、理解:
1、什么是模块化?
(1)、一个js就是一个模块
模块化进化史
(1)、GO被污染,容易命名冲突
foo() {}
bar() {}
(2)、nameSpace 模式、减少GO上变量数目,虽然实现了命名冲突,但是一点也不安全,可以随意修改
var obj = {
foo: function() {},
bar: function() {}
}
obj.foo()
(3)、IIFE 现在模块实现的基石,可以引入依赖,函数是javaScript唯一的 Local Scope
(function(window, $){
}(window, jquery) )
2、为什么要模块化?
(1)、复杂性
(2)、提高解藕
(3)、部署(功能点明确)
3、模块化的好处?
(1)、避免命名冲突
(2)、更好的分离,按需加载
(3)、更高的复用性
(4)、可维护性
4、页面引入加载script
可能会同时引入10个 script标签
(1)、可能会为浏览器发4次请求,
依赖模糊,2依赖1,3依赖2,4依赖3,有一个报错都无法继续执行
二、规范
动态加载和静态加载指的是什么时候加载,动态加载是运行时加载(CommonJS、node),静态加载是编译(预编译)时加载(ES6)。当然es6也提供的动态加载,即import() 。
对于模块的依赖,何为动态?何为静态?
动态:是指对于模块的依赖关系建立在代码执行阶段;
静态:是指对于模块的依赖关系建立在代码编译阶段;
上文提到,CommonJS导入时,require 的路径参数是支持表达式的,例如
// A.js
let fileName = 'example.js'
const bModule = require('./' + fileName)
因为该路径在代码执行时是可以动态改变的,所以如果在代码编译阶段就建立各个模块的依赖关系,那么一定是不准确的,只有在代码运行了以后,才可以真正确认模块的依赖关系,因此说CommonJS是动态的。
那么现在你也应该也知道为什么 ES6 Module 是静态的了吧
举例: 在有热更新的项目中,同时引入module/module1 两个文件:
module.js
alert(‘我是import from我是静态的(编译时)加载’)
module1.js
export default function (){
alert(‘我是import函数,我是动态时加载的’)
}
import module from ‘./module’ // (静态的)页面保存module文件就会执行
const module1 = () => import(‘./module1’) // (动态的)运行时加载,页面添加一个延时器,import返回的是一个 promise
setTimeout(() => {
console.log('module1: ', module1().then(value => console.log(value.default(), ‘value’)));
}, 3000)
同步加载还是异步加载指的是加载的方式。 静态加载中都是同步加载的。动态加载中CommonJS(node)的require是同步的。而es6import()是异步的。
同步加载:CommonJS(node) (C、同、运、动、module.exports(导出内存)、require(缓存引入模块))
(commonJs、同步加载、运行时加载、动态的)
1.CommonJS,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。
var math = require(‘math‘);
然后,就可以调用模块提供的方法:
var math = require(‘math‘);
math.add(2,3); // 5
第二行math.add(2, 3),在第一行require(‘math‘)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。
异步加载:(A、异、预加载)
2:AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
require([‘math‘], function (math) {
math.add(2, 3);
});//这里是否意味着可以有多个require异步执行,谁先加载完成就先执行自身回调?
math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
参考链接1:https://www.jianshu.com/p/0fef8b7ca222
参考链接2: https://blog.csdn.net/goutinga/article/details/123923505
参考链接3: https://www.jianshu.com/p/b82623c8c8a0
1、common
(1)、node-common
module1.js文件
module.exports = {
msg:'module1',
foo() {
console.log(this.msg);
}
}
app.js 文件
let module1 = require('./module1')
module1.foo()
(2)、 browserify ,可以实在在浏览器访问 common.js
module1.js文件
module.exports = {
msg:'module1',
foo() {
console.log(this.msg);
}
}
app.js 文件
let module1 = require('./module1')
module1.foo()
npm init
npm install browserify
package.json文件修改打包后的出口
“scripts”: {
“build”:“browserify js/src/app.js -o js/dist/bundle.js”
// -o 左边为大包入口文件, 右边为大包后的文件
},
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="./js/dist/bundle.js"></script>
</head>
<body>
</body>
</html>
2、AMD / RequireJS 在推广过程中对模块定义的规范化产出。
记忆法:AMD requirejs 静编译,前(因为A在前)
定义模块:define([文件名称], dependencies(依赖), factory)
使用模块:require(dependencies(依赖), callback)
amd 类似 import 静态,编译时(编写代码时有错误就会抛出)
1. 定义模块:define(id?, dependencies?, factory)
依赖有三个默认的,即"require", “exports”, “module”。顺序个数均可视情况
如果忽略则factory默认此三个传入参数
id一般是不传的,默认是文件名
语法一:不使用 amd 写法
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./module1.js"></script>
<script src="./module2.js"></script>
</body>
</html>
module1.js
// 定义一个没有依赖的模块
(function(window) {
const msg = '我是module1模块,被module2引入了'
function module1Fun () {
console.log(msg);
}
window.module1 = { module1Fun }
}(window))
module2.js
// 定义一个有依赖的模块
(function(module1){
const msg = '我是module2模块'
console.log('我是module2模块,我来来执行module1模块', module1);
module1.module1Fun()
}(module1))
语法二:使用 amd 写法
加载模块:require([module], factory)
文件层级
requireAMD
js
libs
require.js
modules
alerter.js
dataService.js
index.html
main.js
alert.js 文件
//定义有依赖的模块
define(['dataService'],function (dataService) {
let msg = 'alerter.js'
function showMsg() {
console.log(msg,dataService.getName());
}
// 暴露模块
return {showMsg}
})
dataService.js 文件
// 定义没有依赖的模块
// 使用AMD语法
define(function () {
let name = 'dataService.js'
function getName() {
return name
}
// 暴露模块
return {getName}
})
main.js 文件
(function () {
requirejs.config({
baseUrl:'/js', // 基本的路径 出发点正在根目录下 不配置时从main.js出发去找
paths:{
dataService:'./modules/dataService', //不要加.js 默认会添加后缀
alerter:'./modules/alerter'
}
})
requirejs(['alerter'],function(alerter) {
alerter.showMsg()
})
})()
index.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script data-main="js/main.js" src="./js/libs/require.js"></script>
</body>
</html>
AMD 依赖前置,require中会执行 依赖中所有的文件,一期返回给 callback
function define(name, dependencies, factory) {
debugger
// console.log(101010, dependencies)
if(!dependencies || !dependencies.length) {
factories[name] = factory
}else {
// console.log('14--->', factory)
// bind 与 call 语法一致,区别在于立即执行还是等待执行
// fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
// fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行
factories[name] = factory.bind(this, ...dependencies.map( moduleName => factories[moduleName]() ))
// console.log(12, factories[name])
}
}
function require(dependencies, callback) {
// amd 和 cmd 最大区别 amd 依赖执行前置
let dependencyResults = dependencies.map( moduleName => factories[moduleName]() )
callback(...dependencyResults)
}
3、CMD/ SeaJS 在推广过程中对模块定义的规范化产出
记忆法:CMD SeaJS 异 ,近, 缓存(跟common)
定义模块:define(dependencies(依赖), factory(require, exports, module))
使用模块:require(dependencies(依赖), callback(require, exports, module))
定义模块:define(factory)
require, exports, module参数顺序不可乱
暴露api方法可以使用exports、module.exports、return
不同:
CMD与AMD不同:与requirejs不同的是,若是未暴露,则返回{},requirejs返回undefined
// amd cmd 区别在哪: amd 依赖前置 cmd 依赖就近
加载模块:require
定义模块无需列依赖,它会调用factory的toString方法对其进行正则匹配以此分析依赖
预先下载,延迟执行
例子如下:
文件地址
libs
sea.js
modules
main
module1
module2
modlue3
module4
sea.js
index.html
libs
1、可以使用sea.js文件,也可以使用
zhangxinxu.com/sp/seajs/docs/zh-cn/module-definition.html#module-definition
2、CDN:https://cdn.bootcdn.net/ajax/libs/seajs/3.0.3/sea-debug.js
mian.js
define(function (require, exports, module){
const module3 = require('./module3')
module3.foo3()
//依赖就近,什么时候需要这个模块什么时候引入
const module4 = require('./module4')
console.log(module4.module4.foo4(), '4444')
})
module1.js
define(function(require, exports, module){
const msg = 'module1--导出对象.module1'
function foo () {
console.log(msg)
}
exports.module1 = foo
})
module2.js
define(function(require, exports, module){
const msg = 'module2-导出函数'
function foo2 () {
console.log(msg)
}
module.exports = foo2
})
// module使用return也可以导出
define(function(require, exports, module){
const msg = 'module3--导出对象'
function foo3 () {
console.log(msg)
}
return { foo3 }
})
// module不导出,默认为空对象
define(function(require, exports, module){
const msg = 'module4'
// 同步执行
const module1 = require('./module1')
module1.module1()
// 异步引入
require.async('./module2', function(m2){
console.log('m2', m2())
})
function foo4 () {
console.log(msg)
}
})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./libs/sea.js"></script>
<script>
seajs.use('./modules/main.js')
</script>
</body>
</html>
好处:1、是异步的 希望像node靠齐 他的源码 就类似于 commonjs webpack
2、模块缓存
cmd.js // cmd的核心是交换函数执行权
let fatories = {}
let modules = {}
function define(name, factory) {
fatories[name] = factory
}
function require(name) {
let script = document.createElement('script')
script.src = name // './modules/moduleA.js'
script.onload = function() {
if(modules[name]) {
return modules[name]
}
let exports = {}
// 递归作用 交换函数的执行权 cmd 依赖就近 关键点就在这
// require('moduleA')
fatories[name](require, exports)
modules[name] = exports
return exports
}
}
4、es6/import
导出
export default (x, y) => x + y // 导出默认模块
export function moduleB() {} // 导出具名模块
导入
import moduleA from "./modulePath" // 默认模块
import { moduleB } from "./modulePath" // 具名模块
区别项 | es模块化 | commonJS | AMD | CMD |
---|---|---|---|---|
可用于服务端还是浏览器 | 服务端和浏览器 | 服务端 / 浏览器端(npm i browserify) | 浏览器 | 浏览器 |
模块依赖关系何时确定(即:何时加载模块) | 编译时 | 运行时 | 编译时 | 运行时 |
设计思想 | 静态的 | 静态的 | ||
模块导出 | export / export default | return value(任意值) | return value(任意值) | exports / module.exports (return) |
是否整体加载模块(即加载的所有方法) | 否 | 是 | 是 | 否 |
是否是动态更新(即通过接口,可以取到模块内部实时的值) | 是。es module输出的是值的引用 | 不是。commonJS模块输出的是值的拷贝,不存在动态更新 | 是 | 否 |
模块变量是否是只读的 | 是。原因:ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。 |
差异
AMD 与 CMD:
AMD是 RequireJS 在推广过程中对模块定义的规范化产出。
AMD在浏览器端异步加载,AMD推崇依赖前置,加载完模块之后就会立即执行它。
1、模块的加载不影响后面语句的执行;
2、所有依赖于这些模块的语句都写在一个回调函数中;
3、而加载内部是同步的(加载完毕后,这个回调函数才运行)
CMD是 SeaJS 在推广过程中对模块定义的规范化产出。
CMD在浏览器端异步加载,CMD推崇依赖就近,加载完模块不会立即执行,只是加载,等到需要的时候才会执行。
ES Module与CommonJS:
CommonJS模块是对象,是运行时加载,运行时才把模块挂载在exports之上(加载整个模块的所有),加载模块其实就是查找对象属性。
ES Module不是对象,是使用export显示指定输出,再通过import输入。此法为编译时加载,编译时遇到import就会生成一个只读引用。等到运行时就会根据此引用去被加载的模块取值。所以不会加载模块所有方法,仅取所需。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
CommonJS与AMD/CMD:
AMD/CMD是CommonJS在浏览器端的解决方案。
CommonJS是同步加载(代码在本地,加载时间基本等于硬盘读取时间)。
AMD/CMD是异步加载(浏览器必须这么做,代码在服务端)
UMD与AMD/CMD
UMD(Universal Module Definition)是AMD和CommonJS的糅合,跨平台的解决方案。
AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
参考链接:https://juejin.cn/post/6844903663404580878
参考链接2: https://juejin.cn/post/6896397110078504973