在没有模块化思想以前,咱们老是将大量的逻辑代码写在一块儿,这样的代码杂乱无章,没有条理性,不便于维护,不利用复用。而且不少代码重复,逻辑重复。甚至形成全局变量污染,也不方便保护私有数据。
为了解决上面的问题,模块化的编程思想应运而生。
模块化的基本思想就是:==闭包自调用函数==
对闭包了解不够的同窗,请先查看《 JS闭包全面解析》一文。html
想要了解模块化就要先知道JS中3个模块规范。
JS中的模块规范(CommonJS,AMD,CMD),若是你听过模块化这个东西,那么你就应该听过或CommonJS、AMD、CMD这些规范,我也听过,但以前也真的是听听而已。直到最近项目中使用到了才有了必定的理解, 如今就看看吧,这些规范究竟是啥东西,怎么用的。(本文对CommonJS及CMD作一个大概的说明,对AMD中RequireJS作较为全面的讲解)前端
CommonJS API定义不少普通应用程序(主要指非浏览器的应用)使用的API,也是Node中使用的模块化解决方案。vue
CMD:common module define,CMD实际上是阿里一位大神编写的seajs中提出的模块化解决方案。
==其实CMD能够当作是CommonJS的前端实现==。
在前几年很是火,不过随着前端框架的崛起,vue、react、angular都集成了各自的模块化。而且es六、webpack等都提供了模块化的解决方案。使得seajs出场机会愈来愈少,做者也中止了更新。seajs也渐渐退出了历史的舞台。react
AMD:async module define:异步模块定义。
AMD其实就是requireJS实现的模块化解决方案,下面我会着重的介绍AMD规范中的requireJS。
连接>>>RequireJS中文网jquery
例如在一个电商网站中,购物车和商品的逻辑会在须要场景应用,因此咱们就能够将二者抽出做为模块开发,使用的时候直接引用,直接上代码。
首先咱们先建立一个cart.js文件webpack
define([],function(){ console.log('cart模块'); })
而后建立一个product.js文件es6
define([],function(){ console.log('product模块'); })
而后在首页index.html中调用模块web
<!-- 首先在官网下载requirejs源文件,经过script标签导入 --> <script src="../js/require.js"></script> <script> // 将以前定义好的cart和product模块导入首页模块中 require(["cart","product"],function(){ console.log('这里是首页模块'); }) </script>
仍是上面的栗子,再加一些代码
cart.js编程
define([],function(){ // 将函数做为模块的返回值 return function(){ console.log('购物车模块初始化'); } })
product.jsbootstrap
define([],function(){ // 模块不只能够返回函数,也可返回对象 return { init() { console.log('商品模块初始化'); } } })
index.html
// 这里咱们给require的回调函数添加形参,接收前面对应模块的返回值,要与数组顺序一致 require(['cart','product'],function(cart,product) { // 这里咱们不想一进入就加载cart和product模块,而是等点击按钮再去加载模块 // 这里也能够理解成按需加载模块 var btn = document.getElementById('btn1'); btn.onclick(function(){ // 在按钮的点击事件中去加载模块 cart(); product.init(); }) })
==注意==:
param1,,,param2
通常将模块的入口也定义在一个单独的js文件中,如main.js。
这样引用入口文件处就能够简写为:
<script data-main="./main" src="../js/require.js"></script>
经过在入口文件中的一些配置可让咱们在使用模块时更加便捷。
如咱们想在模块中使用jQuery,咱们要这样写
define([jquery-3.3.1],function($){ })
这里可能有人不理解为何每一个模块引用jq,都要引用jq模块。由于:
设想一下,若是有几十个模块,每一个模块都这样引入jq,若是jq的文件目录发生改变亦或是jq版本改变,那将会是一个很是大的工程。
这时咱们就能够利用path来解决这个问题
main.js
require.config({ path:{ jquery:"lib/jquery-3.3.1", // 文件 bootstrap:"assets/bootstrap/js/bootstrap.min", // 文件 service:"../service" // 文件夹 } })
固然,不是全部的模块都须要配置在这里的,通常来讲经常使用的模块、文件夹才须要配置。
这样当须要用到jq的时候,只须要导入入口文件中配置好的jquery便可,后续的任何修改,每一个引用的模块都会同步。
// 指定文件的能够直接导入文件,指定文件夹的能够经过配置的文件夹目录找到对应文件 define(["jquery","service/xxxxService","bootstrap"],function($,xxxxService){ })
为何jq能够像咱们编写的其余模块同样被导入使用,是由于jq中已经注册了amd模块。虽然说jq的设计并非像咱们写的amd模块那样,可是在内部已经作了兼容,许多第三方库都是这么兼容amd的,经过源码能够看到:
define([],function() { // 定义一个模块,将jq对象返回,这样咱们在导入模块后拿到的参数$就是这个jq对象 return jQuery; })
一个模块化的项目目录都会比较复杂,如建立两个js文件,cart.js、cartDetail.js。存放的目录为~/js/cart/中,那么想要在cart模块中倒入cartDetail模块就要这样去写:
cart.js
define(["js/cart/cartDetail"],function(cartDetail){ })
虽然两个模块同处一个文件夹中,可是模块的导入是根据入口文件所在的路径去查找的,若是入口文件放在根路径下,那么导入模块的路径也是根路径。
利用baseUrl简化路径查找:
main.js
require.config({ baseUrl:"js/" })
改造后咱们再导入模块能够这样去写:
cart.js
define(["cart/cartDetail"],function(cartDetail){ })
==注意==:path里面的配置也是相对于baseUrl的
场景:a模块依赖b模块,可是b模块也须要a模块,若是按常理去写会形成循环依赖,致使报错。
define(["require","a"],function(require){ require("a")(); })
另外还须要==注意==的一点是:一个模块被不一样模块引用若干次,可是他们获取到的都是该模块同一个引用(闭包数据共享),模块代码不会从新执行,节省性能。
这个方式也是jQuery中使用的。
if ( typeof define === "function" && define.amd ) { define([], function() { return jQuery; } ); }
学习模块化,重要的不是学习具体的实现,而是学习一种思想。只有真正的领悟了模块化的思想才能把模块化更好的应用到开发中,而且在使用其余框架时才能更加驾轻就熟。