模块化发展简史
1.没有模块化的时候
- 最开始 JavaScript 承担的任务量并不多,表单验证基本上就是他的全部,最多就是简短的前端交互
2.传统模块化
-
随着 ajax 的流行,前端能做的东西一夜之间暴涨,代码量飞速增加,单文件维护代码已经太沉重,于是拆之,进而引入模块化,将负责不同功能的代码拆分成小粒度的模块,方便维护
-
这里说的模块化是抛开现在所熟知的 require,amd,seajs 等,不借助任何的模式和工具,由 JavaScript 直接完成的代码结构化
-
JavaScript 天生没有模块化的概念(直到 ES6), 不像后端语言源生自带模块功能, 比如 Java 的 import、C++的 include、Node 的 require
-
应用模块化开发的主要目的是为了复用代码、代码结构清晰、便于维护等
-
在开发过程中,往往会将一些重复用到的代码提取出来,封装到一个 function 里,然后在需要的地方调用,那么这可以看做是一种模块化
function show(element) { // 展示一个元素 }
function close(element) { // 隐藏一个元素 }
例如上面两个函数,实现显示和隐藏一个元素的功能,这种类似的方法往往需要大范围多次调用,可以放到一个js文化中
3.传统模块化存在的问题
存在的问题
- 01.全局变量冲突风险:如果编写
page-1
的人不知道utils
里面有一个show/close
方法,然后他自个也写了一个,同时还添加了额外逻辑,自然就覆盖了原来的方法,那么 page-2 的人在不知道的情况下调用了这方法,自然会发生错误. - 02.人工维护依赖关系:因为存在依赖关系,所以必须先加载
utils
,然后才能加载page-1/2
,这里的例子非常简单,但在实际项目场景了,这样的依赖会非常多且复杂,维护非常困难,很难建立清晰的依赖关系。当时高大上的校内网,那种程度的页面得需要多少的模块去支撑。后续项目迭代往往会带来意料之外的问题.
尝试解决问题
01.可以把这些方法放到一个 object 里面对外输出(为解决全局变量冲突)
-
存在的问题:
1)依然不能避免utils
被覆盖的可能性
2)带来了暴露内部变量的问题,外部可以访问到_name
02.命名空间(为解决全局变量冲突)
- 部分开发者引入了命名空间
var com.company.departure.team.utils = {}
代码模块通过严格的命名规则做了规范,可以按照实际情况具体到部门、team、类库.如果在代码规范上做了这样的约束,基本上可以避免变量名冲突的问题,但同时带来的需要输入过多单词的负担
03.闭包封装(为解决全局变量冲突)
(function() {
var Company = Company || {};
Company.Base = Company.Base || {};
var _name = ‘baotong.wang’
function show () {}
function close () {}
Company.Base.Util = {
show: show,
close: close
}
})();
上述写法通过一个立即执行的函数表达式,赋予了模块的独立作用域,同时通过全局变量配置了我们的 module,从而达到模块化的目的.到这一步,全局变量冲突的问题就解决了。
04.代码合并(解决依赖关系)
- 对文件进行合并处理,将可以合并的业务代码连接到一个文件里
需要注意的是,合并并不是所有的文件合并为一个为好,比如公共文件 jquery 文件、功能公共方法可以单独引用,利用浏览器的缓存机制,减少多页面情况下总的下载量。如果站点一共就是一个 SPA,合并为一个为好
05.代码混淆压缩(解决依赖关系)
- 就是代码压缩
06.通过工具YUI compressor(解决依赖关系)
- YUI compressor,出自雅虎
4.Node
2009 年,node 的发布给前端带来了无限可能,npm 生态的逐渐成熟给了我们更多选择,以往需要通过其他语言工具执行的编译过程也可以由前端一手接管。同时 node 也带来了 commonJS,给前端的模块化提供了新的思路
01.commonJS 概述
- 作为后端语言,没有模块化加载机制是运转不起来的,node 选择实现了 commonJS 作为它的模块加载方案,整体非常简单。注:commonJS 并不是 node 发明的,他只是按照该规范做了一套实现。
02.npm 生态
- npm 生态让 node 有了自己的模块仓库,各种类库的不断支持让我们也有了更多选择。commonJS 一开始就提供了对 npm module 的支持,在路径查找的时候内部配置了对 node_modules 文件夹的查找支持
03.node
- 大批后端的加入丰富了作为一门后端语言的各种基本功能
- 利用 node 开发工作工具,提高前端的工作效率,社区里解析 commonJS 的、构建工程工具不断喷涌而出, 具有代表性的有 grunt、gulp、browserify,webpack,前端模块化可以更进一步
5…模块化方案
01.commonJS的几个概念
- 每个文件是一个模块,有自己的作用域。这里面定义到函数、变量、类都是私有的,对其他文件不可见;
- 每个模块内部,module 变量代表当前模块,它是一个对象;
- module 的 exports 属性(即 module.exports)是对外的接口;加载某个模块,其实是加载该模块的 module.exports 属性如果文件中没有 exports 属性,那么外部引用不到任何东西;
- 使用 require 关键字加载对应的文件,也就是模块;
- require 命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象,如果没有发现该模块,报错。
// 设置文件输出
module.exports = {
func: function() {},
field: "string"
}
// 添加单个 export
module.exports.show = function() {}
//这里引入几个模块、文件
require("modulepath");
var Base = require("../base.js");
var page = require("./file.js");
page.show();
02 其它模式
- 除了 commonJS,当前留下的模块化模式还有以 requireJS 为代表的 AMD 和以 seaJS 为代表 CMD
03.代码改造
基于 commonJS改造上面代码
- 首先,那层闭包可以不用加了,你没看到有谁在 node 里面加这个东西吧,这层其实还是需要有点,但是我们交给工具自动帮我们加上。
- 其次,我们需要在模块内部写上对外输出的内容,module.exports = *;
- 然后,在业务代码中添加对模块的引用,var module = require(“modulepath”), 有了这个之后就能引用 module export 出来的功能了
- 最后,通过打包工具的编译,解析 commonJS,分析入口文件得到最终输出。
最终得到的代码如下:
// module.js
var _name = 'baotong.wang';
function show() { alert(_name); }
function close() {}
module.exports = {
show,
close
}
// page.js
var module = require('./module.js');
module.show();
04.浏览器端支持
- commonJS 是服务器端的模块化方案,浏览器端是不支持的,单是 require 就没有,所以就需要辅助工具来替我们完成 commonJS 代码向浏览器代码的转换
- 社区成熟的解析类库有 browserify,能够完美解析 commonJS
- 但是browserify 并不能满足所有特点需求,因此出现了 fekit、ykit 两款针对 commonJS 的前端工具,来执行代码的编译
- fekit是自己实现的一套解析 commonJS 的工具集,对一些规范的实现不是很规范,同时面向的是内部的 module 仓库,导致和主流 npm 环境脱节
- ykit是基于 webpack 和公司业务特点封装的一个工具集,核心打包交给了 webpack,同时做了部分优化
6.ES6 的模块化方案
- ES6中的模块化。不能直接运行,需要转化为es5语法,通过node平台转化
- ES6 中给出了 import export 这样的方案,目前为止绝大多数都是通过 babel 将 ES6 代码转为 ES5,import 转为了 require,export 转为了 module.exports,即 commonJS
- 他的实现原理和 commonJS 这种引用即引用整个类不一样,它是用啥就引用啥,export 输出的也不是一个类
7.实现模块化流程
-
01.在项目目录下初始化项目
npm init -y
,生成package.json -
02.下载包(最好写依赖)
npm install --save-dev babel-cli -D
npm install --save-dev babel-preset-es2015 -D
-
03.在项目目录下新建
.babelrc
文件(一定要放在项目目录下)
在.babelrc
文件中添加下面的代码
{
"presets": ["es2015"],
"plugins": []
}
-
04.在项目目录中新建
.js
文件,在里面可以书写模块化语法
在项目文件夹src下新建a.js
和main.js
模块文件a.js
主文件main.js
注意:
1)export
导出数据
2)import {数据名} from '文件地址'
导入数据
3)数据和导入的数据名字保持一致,如果有多个重名可以取别名
4)多个数据中间使用逗号隔开
5)同级目录一定要加./
6)一般一个特殊的功能作为一个模块(例如计算器的加减乘除) -
05.需要把es6转化为es5 babel指令进行转化 一定要在bable安装目录下执行
node_modules/bin
文件夹下执行指令
转换指令:babel 源文件 -d 目标文件
:babel ../../src -d ../../dist
使用转换指令后会在新建目标文件夹并生成对应的js文件 -
06.执行js代码
注意:执行生成的目标文件夹内js文件
执行指令:node 文件名
执行转化指令也可以使用快捷键
- 需要先在
package.json
里的"scripts"
里的"test"
后面添加快捷键代码(注意不要少了,
) - 添加格式:
"快捷键":"babel 项目文件 -d 目标文件"
- eg.
"es6":"babel src -d dist"
快捷键es6 ,项目文件src,目标文件dist - 快捷键的转换命令(同样在bable安装目录下执行
node_modules/bin
文件夹下执行指令)npm run 快捷键
转换成功:
执行目标文件的js代码:
使用快捷键后的package.json
文件
模块化发展史参考自:参考