前端模块化和组件化理解
模块化主要体现的是一种分而治之的思想。分而治之是软件工程的重要思想,是复杂系统开发和维护的基石。模块化则是前端最流行的分治手段。
分而治之:将一个大问题分解成多个较为独立的与原问题性质相同的小问题,将所有的小问题的解答组合起来即可得到大问题的答案。模块化的意义
模块化的工程意义首先在于分治的思想,对功能进行分治,有利于我们的维护;其次是复用,有利于我们的开发。
为什么需要模块化
- web sites慢慢变成了web app
- 随着项目的扩大,代码量越来越大,代码越来越复杂
- 对代码、文件的高度解耦的要求
- 优化上,希望减少http请求
前端模块化历史和方案
最早的时期,前端的模块化方案很简单,就是通过命名空间,或module模式模拟类提供私有和共有方法来实现模块化,但是这些模块都不是以文件为单位,而是以对象为单位。
- 命名空间,减少global中的变量,但本质上是对象,一点都不安全
var namespace = { methodOne: function () { /* ...*/ }, methodTwo: function () { /* ...*/ } } namespace.methodOne()
- module模式,可通过传参注入全局对象
var module = (function() { var _private = 'something', var getData = function () { console.log(_private) } return { getData: getData } })()未解决问题: 原始的script tag,难以维护,依赖模糊,请求过多。
- script loader
如 LABjs ,通过LAB.js来加载js文件代替丑陋的script tag,一种基于文件的依赖管理。
- module loader
如YUI,一个基于模块的依赖管理库。
随后出现了CommonJS,AMD/CMD,ES6 Module等方案,以及Browserify/Webpack等模块打包工具。
同步加载CommonJS
比如我们的node.js,使用的便是CommomJS规范。通过require,module.exports,exports来进行导入和导出,这里exports是module.exports的一个引用。
var http = require('http'); var server = http.createServer(); module.exports = { myserver: server }同步模块化的应用场景:对于服务器而言,所有的模块都是存在本地硬盘中的,读取速度快,所以可以采用同步的方式读取模块。
异步加载 AMD (Asynchronous Module Definition,异步模块定义)
采用异步方式加载模块,通过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 (Common Module Definition,通用模块定义)
CMD规范是国内SeaJS的推广过程中产生的,CMD规范中一个模块是一个文件。
- AMD提倡依赖前置,在定义模块的时候就要声明其依赖的模块
- CMD提倡就近依赖(按需加载),在用到某个模块的时候再去require进来。
// 定义一个模块,可通过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
1. require.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 import export/export default
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();
模块打包工具browserify
使得CommonJS In Brower成为可能,它通过将require()函数解析为ast(抽象化语法树)来遍历我们的整个关系依赖图。我们可以在前端代码中使用CommonJS规范来模块化的开发我们的应用,然后通过Browerify进行打包。
模块打包工具 webpack
本质上webpack是一个现代JavaScript应用程序的静态模块打包器。它递归的构建一个依赖关系图,其中包含应用程序的每个模块,然后将这些模块打包成一个或多个bundle.js。
webpack 支持 CommonJS,AMD,ES6等规范,所以我们在代码中可以使用多种模块加载规范,而且通过loader,它不仅可以处理JavaScript,还可以处理像css,图片等等的静态资源。
组件化
前端作为一种GUI软件——图形用户界面(Graphical User Interface),在复杂的项目种,除了js/css的模块化,我们还需要对UI进行分治,也就是组件化开发。
组件化的理念:
- 页面上独立的可视/可交互区域视为一个组件
- 每个组件作为一个目录,所需资源就近维护,在统一工程目录开发维护
- 组件之间可以只有组合替换
组件化的工程意义首先就是分治,其次就是复用。合理的模块化和组件化可以使系统功能分治到独立的更小的工程中去,颗粒度越细,组织形式越松散,开发人员之间的开发就不会产生过多的依赖,提高开发效率,同时整个项目的维护也变得简单可行。
参考
---前端工程基础
---javascript模块化7日谈