玩过FPS游戏的朋友应该知道,一把装配完整的M4步枪,通常是枪身+消音器+倍镜+握把+枪托
。javascript
若是把M4步枪当作是一个页面的话,那么咱们能够作以下类比枪身 -> <main></main>
消音器 -> <header></header>
倍镜 -> <nav></nav>
握把 -> <aside></aside>
枪托 -> <footer></footer>
html
OK,你刚才作了一件事情,就是把m4步枪拆成了五个部分,你拆分的每个部分就是一个模块【module】,你拆分的这个过程就是模块化【modularization】。前端
模块化是一种编程思想,其核心就是拆分任务,把复杂问题简单化,这样一来既方便多人分工协做,又能够帮助咱们迅速定位问题
java
下面用一个小栗子讲一讲模块化的发展史es6
龚先生和棚先生一块儿接了一个项目,他们俩须要分别实现一些功能,很简单,就是 Console出来本身的变量a
因而他们俩一合计,安排龚先生的代码单独放在script1.js
里写,棚先生的代码单独放在script2.js
里写,而后用script标签分别引入express
// script1.js文件 var a = 1 console.log(a)
// script2.js文件 var a = 2 console.log(a)
<!--HTML文件--> <script src="./script1.js"></script> <script src="./script2.js"></script>
很快他们遇到了第一个问题 —— 变量命名冲突
尤为是包含了异步的时候,会出现以下状况编程
// script1.js文件 var a = 1 setTimeout(()=>{ console.log(a) // 咱们想console出来1,却console出了2 },1000)
// script2.js文件 var a = 2 console.log(a)
上面的问题明显是因为a是一个全局变量致使的,因此解决思路也很明确——造一个局部变量呗
promise
ES5时代使用当即执行函数制造局部变量
// script1.js文件 !function(){ var a = 1 setTimeout(()=>{ console.log(a) // 这下是2了 },1000) }() // 下面有5000行代码
// script2.js文件 console.log(2)
ES6时代直接使用
块级做用域+let
// script1.js文件 { let a = 1 setTimeout(()=>{ console.log(a) // 这下是2了 },1000) }
// script2.js文件 { let a = 2 console.log(a) }
后来公司招了一个前端大佬,说如今只能由他来控制何时console变量,因而他新建了一个control.js
文件
并经过window对象链接script1.js和scirpt2.js浏览器
// script1.js文件 { let a = 1 window.module1 = function() { console.log(a) } }
// script2.js文件 { let a = 2 window.module2 = function() { console.log(a) } }
// control.js文件 setTimeout(()=>{ window.module1() },1000) window.module2()
这个时候,很是重要的一点就是window是一个 全局变量而且充当了一个 公用仓库,这个仓库有两个关键做用,存【导出】
和取【依赖】
// script1.js文件 { let a = 1 // 把这个函数存放进window,就是导出到window window.module1 = function() { console.log(a) } }
// control.js文件 setTimeout(()=>{ // 咱们从window里取出module1函数进行调用,就是依赖了script1.js文件 window.module1() },1000) window.module2()
烦人的产品对需求又进行了更改,给了一个name.js文件缓存
// name.js文件 window.names = ['gongxiansheng','pengxiansheng']
要求如今龚先生和棚先生须要Console出本身的名字
这还不简单?几秒钟写好
// script1.js文件 { window.module1 = function() { console.log(window.names[0]) } }
// script2.js文件 { window.module2 = function() { console.log(window.names[1]) } }
// control.js文件 setTimeout(()=>{ window.module1() },1000) window.module2()
<!--HTML文件--> <script src="./script1.js"></script> <script src="./script2.js"></script> <script src="./control.js"></script> <script src="./name.js"></script>
但很快他们发现,console出来的都是undefined
前端大佬一眼看出了问题,对他们俩说大家依赖的代码必定要在大家本身的代码前引入,否则是取不到值的;你看个人control.js是否是在大家俩的代码后面引入的,由于我用到了大家俩的代码了呀
噢噢,原来是js文件加载顺序问题,改一下吧
<!--HTML文件--> <script src="./name.js"></script> <script src="./script1.js"></script> <script src="./script2.js"></script> <script src="./control.js"></script>
可是在人多了之后,咱们到时候会搞不清楚到底谁依赖了谁,保险起见只能所有都加载,性能浪费了太多
,前端大佬摇头叹息道
模块化是ES6的最大的亮点之一,由于在ES6以前的语法里从未有过模块化的体系,这对开发大型的、复杂的项目造成了巨大障碍。由于咱们没法对项目进行拆分,没法更好地进行多人协做开发。更重要的是,其它大部分语言都支持模块化。
既然语言不支持,那么如何将模块化引入JS呢?
前端社区就本身制定了一些模块加载方案——这也是CommonJS【服务器】和AMD、CMD【浏览器】的由来。
可是如今ES6引入了模块化的功能,实现起来很是简单,彻底能够取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
import和export语法较为简单,你们去MDN能够看很是详细的讲解,笔者在这里知识用注释简单介绍一下
// 命名导出 export { name1, name2, …, nameN }; export { variable1 as name1, variable2 as name2, …, nameN }; export let name1, name2, …, nameN; // also var export let name1 = …, name2 = …, …, nameN; // also var, const export function FunctionName() {...} export class ClassName {...} // 默认导出 export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; // 将其它模块内的导出做为当前文件的导出 export * from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …;
import defaultExport from "module-name"; // 导入默认默认变量 import * as name from "module-name"; // 将模块内全部变量导出,并挂载到name下【name是一个module对象】。什么要有as——为了防止export出来的变量命名冲突 import { export } from "module-name"; // 导入某一个变量 import { export as alias } from "module-name"; // 导入某一个变量并重命名 import { export1 , export2 } from "module-name"; // 导入两个变量 import { export1 , export2 as alias2 , [...] } from "module-name"; // 导入多个变量,同时能够给导入的变量重命名 import defaultExport, { export [ , [...] ] } from "module-name"; // 导入默认变量和多个其它变量 import defaultExport, * as name from "module-name"; // 导入默认变量并从新命名 import "module-name"; // 导入并加载该文件【注意文件内的变量必需要经过export才能被使用】 var promise = import(module-name); // 异步的导入
// name.js文件 let names = ['gongxiansheng','pengxiansheng'] export default names
// script1.js import names from './name.js' let module1 = function () { console.log(names[0]) } export default module1
// script2.js import names from './name.js' let module2 = function() { console.log(names[1]) } export default module2
// control.js import module1 from './script1.js' import module2 from './script2.js' setTimeout(() => { module1() }, 1000) module2()
<!--HTML文件--> <script type="module" src="./control.js"></script> <!--注意必定要加上type="module",这样才会将这个script内的代码当作模块来对待-->
其实就是按照数据类型里的引用类型
的概念去理解。
这一点与 CommonJS 规范彻底不一样。
CommonJS 模块输出的是值的缓存
,不存在动态更新。
// module1.js export var foo = 'bar'; setTimeout(() => foo = 'baz', 500);
// module2.js import {foo} from './module1.js' console.log(foo) setTimeout(() => console.log(foo), 1000); // console的结果 // bar // baz
// 报错 { export let foo = 'bar'; }
console.log(foo) import {foo} from './script1.js'
参考资料: ECMAScript 6 入门
本文纯属原创,为了方便你们理解,小故事,小栗子都是笔者本身想的。若是您以为对你有帮助,麻烦给个赞,给做者灰暗的生活挥洒挥洒积极向上的正能量,谢谢啦^_^。