haimianxiaojie 2019-06-27
模块化主要体现的是一种分而治之的思想。分而治之是软件工程的重要思想,是复杂系统开发和维护的基石。模块化则是前端最流行的分治手段。
分而治之:将一个大问题分解成多个较为独立的与原问题性质相同的小问题,将所有的小问题的解答组合起来即可得到大问题的答案。模块化的工程意义首先在于分治的思想,对功能进行分治,有利于我们的维护;其次是复用,有利于我们的开发。
最早的时期,前端的模块化方案很简单,就是通过命名空间,或module模式模拟类提供私有和共有方法来实现模块化,但是这些模块都不是以文件为单位,而是以对象为单位。
var namespace = { methodOne: function () { /* ...*/ }, methodTwo: function () { /* ...*/ } } namespace.methodOne()
var module = (function() { var _private = 'something', var getData = function () { console.log(_private) } return { getData: getData } })()未解决问题: 原始的script tag,难以维护,依赖模糊,请求过多。
如 LABjs ,通过LAB.js来加载js文件代替丑陋的script tag,一种基于文件的依赖管理。
如YUI,一个基于模块的依赖管理库。
随后出现了CommonJS,AMD/CMD,ES6 Module等方案,以及Browserify/Webpack等模块打包工具。
比如我们的node.js,使用的便是CommomJS规范。通过require,module.exports,exports来进行导入和导出,这里exports是module.exports的一个引用。
var http = require('http'); var server = http.createServer(); module.exports = { myserver: server }同步模块化的应用场景:对于服务器而言,所有的模块都是存在本地硬盘中的,读取速度快,所以可以采用同步的方式读取模块。
采用异步方式加载模块,通过define来定义一个模块,通过require来引入模块,模块的加载不影响后面语句的执行,所有依赖于这些模块的语句都写在一个回调函数中,加载完毕后,这个回调函数才运行。如:
// 定义一个模块,name为定义的模块名称,foo为该模块依赖的其他模块 define( 'name', [ 'foo' ], function(foo) { function outPutFoo () { console.log(foo.data) } return { outPutFoo: outPutFoo } })
// 导入模块 require(['name'], function (name) { name.outPutFoo(); })异步模块化的产生主要是因为同步加载方式无法应用到浏览器等受网速等限制加载速度较慢且不稳定的场景,所以通过异步加载的方式防止代码执行受阻,页面停止渲染等问题。
CMD规范是国内SeaJS的推广过程中产生的,CMD规范中一个模块是一个文件。
// 定义一个模块,可通过return, exports, mudule.exports决定要导出的内容 define(function (require, exports, module) { var one = require('./one') one.do() // 就近依赖,按需加载 var two = require('./two') two.do() })
AMD规范的require.js与CMD规范的sea.js
require.js主要解决的问题:
定义模块
define(function () { // ... }) // 依赖moduleB define(['moduleB'], function (moduleB) { // ... }) // 定义命名模块moduleA define('moduleA', ['moduleB'], function { // ... })
引入模块
require(['moduleA'], function (moduleA) { // ... })
如果不在统一目录,可以通过config来自定义。
require.config({ baseUrl: "...", path: { "moduleA": "....", "moduleB": "..." } })cmd规范的sea.js因为已经废弃了,所以就不再详细说了。
es6带来了语言原生的模块化方案。
const methodOne = params => { console.log(params) } const methodTwo = params => { console.log(params) } // 导出方式 1 export default { methodOne, methodTwo } // 导出方式 2 export { methodOne, methodTwo }
// 引入方式 1 对应导出方式 1 import module from './module' module.methodOne(); // 引入方式2 对应导出方式 2 import { methodOne } from './module' methodOne();
使得CommonJS In Brower成为可能,它通过将require()函数解析为ast(抽象化语法树)来遍历我们的整个关系依赖图。我们可以在前端代码中使用CommonJS规范来模块化的开发我们的应用,然后通过Browerify进行打包。
本质上webpack是一个现代JavaScript应用程序的静态模块打包器。它递归的构建一个依赖关系图,其中包含应用程序的每个模块,然后将这些模块打包成一个或多个bundle.js。
webpack 支持 CommonJS,AMD,ES6等规范,所以我们在代码中可以使用多种模块加载规范,而且通过loader,它不仅可以处理JavaScript,还可以处理像css,图片等等的静态资源。
前端作为一种GUI软件——图形用户界面(Graphical User Interface),在复杂的项目种,除了js/css的模块化,我们还需要对UI进行分治,也就是组件化开发。
组件化的理念:
组件化的工程意义首先就是分治,其次就是复用。合理的模块化和组件化可以使系统功能分治到独立的更小的工程中去,颗粒度越细,组织形式越松散,开发人员之间的开发就不会产生过多的依赖,提高开发效率,同时整个项目的维护也变得简单可行。
参考
---前端工程基础
---javascript模块化7日谈