前言:前端模块化出现的缘由和实现的一些弊端
出现的缘由
为什么会出现前端模块化呢,要想实现功能的复用,就需要把代码引入重复利用,如果不使用模块化,我们之前会使用的方法有哪些呢?
1.直接定义全局function的方式,那么会出现函数命名污染的情况,并且各个部分之间的关系也看不出来。
2.在命名空间(name space)中定义方法,定义一个obj = {function1() {},function2(){}};这样有个问题就是函数内部的成员可以被随意修改,很不安全。
3.通过IIFE模式,也就是函数自调用闭包的方式,暴露给window,但是如果存在模块之间的依赖,就会很麻烦。
4.IIFE增强模式:引入依赖。为了解决上面的问题,就出现了IIFE增强模式,说白了就是传参把其他的模块给传进去,就出现了先后顺序很重要的问题,如果前面的都还没挂载到window上面,此时运行后面的js代码肯定就是undefined。
因此,为了解决1.命名污染。2.提升代码的维护性。3.更好的分离代码,实现按需加载。4提高代码的复用性等,我们需要采用模块化规范,这里就介绍比较常用的四种模块化的规范,分别是commonJS,AMD,CMD和ES6四种。
一、commonJS
1.commonJS-node
node.js原生就支持commonJS的规范,所以在node.js中可以直接使用,不需要引入其他的包了。commonJS是在服务端跑的,所以都是读的本地磁盘中的内容,不存在异步的问题,所以require过来就直接可以用,因为是同步的,所以会有阻塞的情况,但是在浏览器端,一般这些js文件也就是模块,很多是通过网络请求过来的,是异步的所以使用commonJS就不是那么合适。
语法就是exports和require两个。
1.1exports可以一起导出,也可以分开导出,require直接导入
module1.png上面是module1内部的代码,可以module.exports = {}的方式,exports就是一个对象
module2.png
上面是module2的内部代码,exports对象也可以是一个方法
module3.png上面是module3的内部代码,通过exports.foo和exports.bar的方式分开添加
app.png上面是app.js的内部代码,可以看到直接通过require('路径')的方式把模块直接给引入过来了,非常方便
1.2 关于package.json
package.pngpackage.json是通过npm.init初始化的时候就产生的记录本包的详细信息的文件,包含了name(这个name不能包含中文,也不能有大写,在老版本的npm的时候,不具有自动转小写的功能),version以及author等内容,并且通过npm install uniq -save(运行时依赖,这里的save在npm5之后,不加save也默认就是save)或者npm install uniq --save-dev(开发时依赖),在dependencies中记录
1.3 执行app.js
因为是在node环境中执行,当前webStorm执行也是用的node,不是在浏览器环境中,无论通过node app.js还是右键执行app.js都可以完成。
1.4 commonJS的不足
既然commonJS已经这么方便了,为啥会出现AMD和CMD以及ES6的后续的规范呢,就是因为commonJS是在node中实现的,也就是node环境中可以支持require和exports,但是在浏览器环境中,是不支持的,所以要想在浏览器端使用commonJS的规范的话,就必须使用browserify这种第三方包把代码给转换成浏览器端支持的代码,后面将写commonJS在浏览器端的实现。
2.commonJS-Browserify
由于前文中提到的commonJS规范在浏览器端无法适用的情况,我们需要引入一个第三方包也就是Browserify,把原本只能在node环境中跑的代码给转换成浏览器端可以使用的代码。
browserify的安装(必须全局和局部都安装):
①、全局安装
npm install browserify -g
②、局部安装
npm install broserify --save-dev
安装完成之后,运行browserify js/src/app.js -o js/dist/bundle.js
browserify.png
上述指令中的-o,代表的是output,后面js/dist/bundle.js就是输出的目标位置,没有dist目录的话会自动创建,而且这个命令运行之后,命令行不会有什么打印,不代表没执行成功。(这里其他module1,module2这些都没有变,和前面1当中的代码是一样的,只是在浏览器中执行了)
script标签引入.png
在html中通过script标签引入那个bundle.js就可以了,接下来在浏览器中执行。
浏览器中执行结果.png
上图就是在浏览器环境中执行的结果,如果script的标签src引入的不是这个bundle,直接是app.js的话,require这种语法,浏览器根本就不认识,就会出现下面的这个情况。
不用browserify打包.png
二、AMD规范
AMD(Asynchronous Module Definition异步模块定义)规范相比于CMD来说应用得更加广泛一些。AMD是专门用于浏览器端的,模块的加载是异步的。
AMD规范是通过Require.js实现的,所以需要下载Require.js文件。
基本语法:
①、定义暴露模块:如果是定义没有依赖的模块
define(function(){
return 模块
})
如果是定义有依赖的模块
define(['module1','module2'],function(module1,module2) {
return 模块
})
②、引入使用模块
requirejs(['module1','module2'],function(m1,m2) {
使用m1/m2
})
2.1没有AMD时候的模块化实现方式
alert .png dataService.png app.png html.png如上图所示,在js文件夹中创建了alert和dataService两个模块,这些模块之间都是通过IIFE也就是立即执行函数来把模块内的方法暴露给window对象的,三者之间存在依赖关系,必须按照先后顺序加入script标签,先dataService,后alert再app.js这三个加载顺序,这就是没有AMD的时候的执行方法,就是暴露给window,但这样的缺陷是非常明显的,就是模块之间的依赖必须要手动去定好,不然一定会存在undefined的情况。
2.2 采用AMD-RequireJS的方式实现模块化
html.png上图是使用require.js来实现的方式,就是script标签的scr是require.js,但是有一个主js的入口,就是data-main属性,里面指定了主文件的入口。
main.png
上图就是main.js的代码。里面是一个立即执行函数,因为他不需要再向外暴露接口,直接就使用requirejs方法,把需要的模块名字传入,然后后面回调函数进行使用就可以了。
alerter.png
上面是alerter,他引入了dataService模块和jquery(必须小写)模块,回调函数中,就使用了其他的模块。
dataService.png
这个dataService,是不需要依赖其他模块的,所以没有传前面的数组,直接定义好,然后暴露接口就可以了。
层级目录.png
上面是层级目录,记得libs下面有这个require.js
requirejs配置.png
这个部分就是requirejs的配置,必须配置,否则根本不知道你数组里头引入,或者定义的时候,那个模块到底是谁,后面映射了路径。shim用来解决那些不支持AMD规范的js包的,就比如angular,本来就是不支持的,你配置了path也没用,配置shim就可以了,上面也可以配置baseUrl。
三、CMD规范
CMD规范用的相对少一些,是阿里开发的,但现在卖给外国人了,使用起来的语法有点像CommonJS和AMD的结合版,SeaJS实现了CMD规范,所以需要下载这个seajs。
使用语法:
①、定义没有依赖的模块
define(function(require,exports,module) {
exports.xxx = value;
module.exports = value;
//这个和commonJS很像,关键就是那个exports对象
})
②、定义有依赖的模块
define(function(require,exports,module) {
var module2 = require('./module2');
require.async('./module3',function(m3) {
///使用module3,是异步的,到时候module3拿到了就给回调直接执行
})
exports.xxx = value
})
③、引入使用模块
define(function(require) {
var m1 = require('./module1')
var m4 = require('./module4')
//执行...
})
html.png
上图是html里面的使用方法,先引入sea.js,然后seajs.use一下main.js。
mainjs.png
main.js里面因为只是在使用,所以不需要加后面的exports和module,当然你开心也可以加上。
里面的引入方式,还是一样的,就是require就可以了,很像commonjs。
module1.png
上面是module1,接下来以此就是四个module。
module2.png module3.png
上面是module3和2,都是一个没有依赖的。
module4.png
上面是module4,依赖了2和3。前面mainjs引入了module1和4,执行后发现异步调用的3被放在后面了,说明确实异步了,结果就是1243。
7575.png
上面是seajs的位置,不要忘记。
对比CMD和AMD可以发现,AMD的依赖是前置的,在一个数组里头,直接就先加载了再执行后面的代码,而CMD的依赖是后置的,只有需要的时候,才会去执行模块的加载。
四、ES6
ES6需要安装一些加载插件:babel-cli,babel-preset-es2015和browserify,babel-cli主要是实现命令行,执行babel指令可以运行,后面那个babel-preset-es2015才是把ES6转成ES5的工具,babel不仅可以完成es6转es5,还能转其他的,转啥就下载啥,所以这里就需要下载这个babel-preset-es2015。
1.npm install babel-cli browserify -g
全局安装babel-cli这样我们在任何地方都可以执行babel命令了,browserify我们前面已经安装过了,现在也安装一下吧,反正也不吃亏。
2.npm install babel-preset-es2015 --save-dev
这个很明显是开发时候的依赖,转了到时候运行的时候就不需要再转了嘛。
3.定义.babelrc
不要忘记前面那个. (点),在根目录里面新建一个.babelrc,加了点的时候,webstorm自动把它识别为json文件了。babelrc也能在package.json里面配置
{
"name":'pname',
"babel": {
//config
}
}
配置babelrc.png
我们这里直接在根目录配置babelrc,rc代表的是run control运行控制。
4.编写模块之间的依赖
5.编译
因为前面写的js文件,首先浏览器根本就不认识ES6的语法,因此需要babel来转成es5,另外,需要把它弄到浏览器环境中能执行,所以必须browserify
5.1、babel js/src -d js/lib
-d就是目标是js/lib文件夹
-d前面就是需要编译的整个文件夹路径,里头都需要编译
babel命令.png
5.2 browserify js/lib/main.js -o js/dist/bundle.js
这个就是把他弄到服务器端跑的工具
上面两两步编译之后,浏览器就能够识别并运行了。
成功1.png 成功2.png
6.默认暴露的写法(补充)
默认暴露的module3.png成功3.png
1.值得注意的一点:package.json中name的值,不要取跟github上常用包一样的名字,否则会死活无法下载下来。
2.安装babel必须安装babel-cli,cli就是command line interface也就是命令行接口,比如node里面的npm为啥可以执行,是因为里面就有cli,存了各种命令,但是babel自身没有,所以需要手动下载。
3.npm install jquery@1指定下载jquery1里面的最新版本,默认包就是import $ from 'jquery'不需要加路径名,而且也是默认暴露的
网友评论