模块化

模块化发展简史

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.jsmain.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文件

模块化

模块化发展史参考自:参考

上一篇:es6模块化的注意点以及与commonjs模块化的区别


下一篇:ES Module 和 Commonjs 的区别