https://juejin.im/post/6844903744518389768
通常一个模块会有各自的作用域,会向外界暴露特定的变量或者函数
将一个复杂的程序,依据规范封装成一块块的文件,即是模块
模块就是一组特定功能的文件,以下foo 与bar组合成的module1.js 也可被称为模块
// module1.js
function foo() {
...
}
function bar() {
...
}
产生问题:
假设今天我们有好几个模块需要引入到同一个地方会发现,假设不同的模块都有一个foo函数不就出事情(这个情况其实就是污染了全局变量)
// module1.js
let module1 = new Object({
_count: 0,
foo: function () {
...
},
bar: function () {
...
}
})
这样只要我们调用 foo函数 就可以利用 module1.foo ,如果有其他模块也有foo就不会发生冲突。
产生问题:
可是到这里我们又发现,_count可以被任意访问,他明明就是计数器怎麽可以任意被外部改变。
有私人的变量,外界只能透过暴露的方法获取变量
// module1.js
(function (window) {
let _count = 0
function foo() {
_count += 1
}
function getCount() {
foo()
console.log(_count);
}
// 暴露给全局
window.module1 = {
// ES6 增强语法
getCount,
}
})(window)
到目前为止都还不错,不过我们还忘了引入依赖(这里拿JQuery当例子)
// body部分
<body>
<script
src="https://code.jquery.com/jquery-3.5.1.js"
integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
crossorigin="anonymous"
></script>
<script src="./module1.js"></script>
</body>
js部分
// module1.js
(function (window, $) {
let _count = 0
function foo() {
_count += 1
}
function getCount() {
foo()
console.log(_count);
}
function changeColor() {
console.log(++_count);
$('body').css('background', 'red')
}
window.module1 = {
// ES6 增强语法
getCount,
changeColor
}
})(window, jQuery)
原因有两个,导致难以维护
请求过多
有一堆 ,而且依赖过多需要发发送过多请求
依赖模糊
从HTML来看根本看不出来谁依赖谁
Node.js 为主要实践者,每个模块有各自的作用域,变量或函数都是私有,外界不可视
服务器端:模块的加载是运行时同步加载的
浏览器端:模块需要提前编译打包处理
特点:
- 模块可以多次加载,但只会在第一次加载运行一次,会将运行结果缓存,下次从其他地方导入会从缓存读取,如果想再一次运行模块需要清除缓存
- 加载的顺序,按照其在代码中出现的顺序。
module.exports 或是 exports
require(xxx)
第三方模块(比方说npm install xxx): xxx即是模块名子
自定义模块: xxx是路径
例子:
CommonJS模块的加载机制是,输入的是被输出的值的浅拷贝。
注意跟ES6 差很多
// module1.js
let counter = 5;
let objCounter = {
value: 5
}
function addCounter() {
++counter;
}
function addObjCounter(params) {
++objCounter.value
}
module.exports = {
counter,
objCounter,
addCounter,
addObjCounter,
};
// module2.js
// 注意这里要用node
// 所以要再控制台输入 node module2.js (注意先cd到放module2.js的资料夹)
const module1 = require('./module1');
console.log(module1.counter); // 5
console.log(module1.objCounter.value); // 5
module1.addCounter()
module1.addObjCounter()
console.log(module1.counter); // 5
console.log(module1.objCounter.value); // 6(因为是浅拷贝,所以会增加)
建议使用module.exports进行导出,因为最终导出的绝对是module.exports 指向的内存地址的对象
epxorts比较像是node给你的语法糖
如果今天exports 指向新的对象
导出是module.exports 指向的内存地址的对象,所以当然还是
{name: 'Mike', age: 15}
AMD (Asynchronous Module Definition),如同他的名子,处理异步加载模块
如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范
顺带一提Commonjs处理同步,因为Node.js主要用於服务端编成,模块文件储存在本地硬碟,加载快速。所以通常不会造成阻塞
导出模块:
// 不依赖其他模块
define(function(){
return 模块
})
// 依赖其他模块
define(['module1', 'module2'], function(m1, m2){
return 模块
})
导入模块:
require(['module1', 'module2'], function(m1, m2){
// 使用m1和m2
})
目录结构
├─alert.js
├─index.html
├─main.js
└store.js
// store.js文件
(function (window) {
let msg = '我在store.js里'
function getMsg() {
return msg
}
window.store = {
getMsg,
}
})(window)
// alerter.js文件
(function (window, store) {
let addMsg = '我被alert添加了'
function showMsg() {
alert(store.getMsg() + ', ' + addMsg)
}
window.alerter = {
showMsg
}
})(window, store)
// main.js文件
(function (alerter) {
alerter.showMsg()
})(alerter)
<!-- index.html -->
<body>
<script src="./store.js"></script>
<script src="./alert.js"></script>
<script src="./main.js"></script>
</body>
缺点:
- 会发送多个请求(一堆)
- 只看index.html根本看不出依赖谁
- 引入顺序完全不能有错
require载点
https://github.com/requirejs/requirejs/blob/master/require.js
目录结构
├─index.html
├─main.js
├─lib
| └require.js
├─js
| ├─alerter.js
| └store.js
// store.js文件
// 定义无依赖模块
define(function () {
let msg = '我在store.js里'
function getMsg() {
return msg
}
// 暴露模块
return {
getMsg
}
})
// alerter.js文件
// 定义有依赖模块
define([
'store',
], function(store) {
let addMsg = '我被alert添加了'
function showMsg() {
alert(store.getMsg() + ', ' + addMsg)
}
return {
showMsg
}
});
// main.js文件
(function () {
// 配置require
require.config({
baseUrl: 'js/',
paths: {
// 映射,标注模块名子(依赖时的数组里的名子就是这个)
alerter: './alerter', // 不能写成alter.js会报错误
store: './store'
// 导入第三方库
// jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
}
})
require(['alerter'], function (alerter) {
alerter.showMsg()
})
})()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script data-main="main" src="lib/require.js"></script>
</body>
</html>
注意事项
- 入口文件(main)定义在index.html
- 入口文件配置所有导出模块的名称(映射关系)
整合AMD以及CommonJS
导出模块:
// 不依赖其他模块
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载点
目录结构
├─index.html
├─result.txt
├─lib
| └sea.js
├─js
| ├─main.js
| ├─moduleA.js
| ├─moduleB.js
| ├─moduleC.js
| └moduleD.js
// moduleA.js文件
define(function (require, exports, module) {
console.log('我(A)被加载了')
//内部数据
var data = ' 我在ModuleA里面'
//内部函数
function show() {
console.log('moduleA show() ' + data)
}
//向外暴露
exports.show = show
// 上面那样用跟module.exports.show依样意思
})
// moduleB.js文件
define(function (require, exports, module) {
//内部数据
var data = ' 我在ModuleB里面'
//向外暴露
console.log('我(B)被加载了')
exports.data = data
})
// moduleC.js文件
// 这个会被异步加载
define(function (require, exports, module) {
const TOKEN = 'abc123'
exports.TOKEN = TOKEN
})
// moduleD.js文件
define(function (require, exports, module) {
//引入依赖模块(同步)
var moduleB = require('./moduleB')
function show() {
console.log('moduleD show() ' + moduleB.data)
}
exports.show = show
// 异步引入依赖模块
require.async('./moduleC', function (moduleC) {
console.log('异步引入moduleC ' + moduleC.TOKEN)
})
})
// main.js文件
define(function (require) {
var moduleA = require('./moduleA')
var moduleD = require('./moduleD')
moduleA.show()
moduleD.show() // moduleD引入
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript" src="lib/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/main')
</script>
</body>
</html>
注意事项
- 入口文件(main)也是定义在index.html
- 在各个模块透过require导入,不像AMD集体定义在main.js
- 跟CommonJS一样有exports跟module.exports
export var a = 125
export const _b = 'Mike'
export let c = 2222
var a = 125
const _b = 'Mike'
let c = 2222
export {a, _b, c};
var a = 125
const _b = 'Mike'
let c = 2222
export {
a as var1,
_b as var2,
c as var3 };
注意:
export命令规定要处於模块顶层, 假如出现在块级作用域( { } ) 就会报错,import同理
// module2.js
export default function(){
console.log('foo')
}
// 相当于
function a(){
console.log('foo')
}
export {a as default};
import可以指定任意名字
import Foo from './module2'
// 相当于
import {default as Foo} from './module2'
import {a, _b ,c} from './profile'
import {stream1 as firstVal} from './profile'
import { foo } from './module1'
import { bar } from './module1'
// 相当于
import {foo,bar} from './module1'
import * as circle from './module1'
circle.foo();
circle.bar();
- CommonJS模块输出的是一个值的浅拷贝,ES6模块输出的是值的引用(赋值)
- CommonJS模块是运行时加载,ES6模块是编译时加载
第一个差异:
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4 (会指向lib模块的counter(内存地址))
第二个差异:
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然後再从这个对像上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6模块不是对象,而是通过export
命令显式指定输出的代码,import
时采用静态命令的形式。即在import
时可以指定加载某个输出值(可能会导致变量提升),而不是加载整个模块,这种加载称为“编译时加载”。
<<: Day 24 [编程03] [译文] 如何在JavaScript 中更好地使用数组
>>: Day 26 [其他04] ES6的Symbol竟然那么强大,面试中的加分点啊
荀子劝学篇中有一段是这样的: 「积土成山,风雨兴焉;积水成渊,蛟龙生焉;.....。故不积蹞步,无以...
今天要来教大家for回圈,for回圈在Python也是常常会用到的一种语法,有时候我们会希望让程序中...
分散资料库(Distributed Database, DDB) VS 集中式资料库(Central...
照片铅笔素描效果 教学原文参考:照片铅笔素描效果 这篇文章会介绍使用 GIMP 图层的混合模式,搭配...
续上篇 Day1 理解 golang slice 用法及原理 I 什麽是 slice 是的容量 (c...