首先,来说说模块化的意义,我们在写一个网站的时候,往往需要很多很多的js代码,但是代码多了,就会出现很多的问题,比如,过多的代码,会极大的增加复杂性,程序之间的耦合度会变高,而且全局的命名空间也会被污染。
这时候,就需要用到模块化这个概念了,将一个完整的程序,把不同的功能,拆分出来,数据是私有的,只是向外暴露几个接口(API),如果要用到这个功能的时候,只要调用相应功能的api就好了。
模块从开始的简单的把方法放到对象中,到匿名闭包(IIFE),再到引入依赖,构成了今天模块化的基石。
首先,模块化会降低程序的复杂度,提高程序的解耦性,不污染全局的命名空间,更好的部署(如果需要这个功能,我就引入这个模块,不需要就不用引入)。
避免命名冲突,更好的分离程序(按需要加载),高复用性,高维护性。
因为我们把一个程序拆开了,所以,要请求的js文件数量也会成倍增加。
所以,依赖模糊,难以维护。
/**
* 全局函数模式: 将不同的功能封装成不同的全局函数
* 问题: Global被污染了, 很容易引起命名冲突
*/
let a =1;
function foo() {
console.log(a,"全局函数模式");
}
2.namespace(简单对象封装)模式
将方法放到对象中,虽然不会污染全局命名,但是还是可以通过对象点方法的模式修改函数,不安全
/**
* namespace模式: 简单对象封装
* 作用: 减少了全局变量
* 问题: 不安全(数据不是私有的, 外部可以直接修改)
*/
let obj ={
foo(){
console.log(1,"对象封装模式");
}
}
3.IIFE模式 匿名函数自调用,将属性方法写在匿名函数中,最后再暴露出来。
/**
* IIFE模式: 匿名函数自调用(闭包)
* IIFE : immediately-invoked function expression(立即调用函数表达式)
* 作用: 数据是私有的, 外部只能通过暴露的方法操作
* 问题: 如果当前这个模块依赖另一个模块怎么办?
*/
//IIFE模式
(function () {
function foo() {
console.log("匿名函数自调用,IIFE模式");
}
window.module3 = {foo};
})()
4.IIFE增强模式 利用匿名函数自调用
目前主流的模块化规范有这么几种,commonjs AMD CMD(已经被阿里出售,只需要了解即可),ES6规范。
我们就先从conmmonjs来讲起。
commonjs在服务器和浏览器都可以使用,服务器主要是node,主流浏览器只要编译之后,也都可以使用。
commonjs
说明:每个文件都可以当作一个模块。
在服务器端模块加载是同步进行的(同步意味着阻塞与等待)
在浏览器上需要在开发时进行编译打包处理,因为有些命令浏览器无法识别。
使用:暴露:module.exports = value
exoorts.xxx = value
暴露的是什么呢?其实是exports这个对象,在初始状态下,它是一个空对象。
引入:require(文件路径)
引入文件时,会把文件执行,并且把文件中的exports这个对象返回。
实现:
服务器:nodejs
浏览器:Browserify,就是commonjs的浏览器的打包工具。
这是服务器使用commonjs,其实是node很常规的用法
/*module1*/
module.exports = {
num:1,
foo(){
console.log(this.num,"让exports等于一个对象");
}
}
/*module2*/
module.exports.foo = ()=> {
console.log("让exports.xxx = 一个方法");
}
exports.bar = ()=> {
console.log("让exports.xxx = 一个方法2");
}
/*module3*/
module.exports = function () {
console.log("让exports成为一个方法");
}
/*app*/
let module1 = require("./modules/module1");
let module2 = require("./modules/module2");
let module3 = require("./modules/module3");
module1.foo();
module2.foo();module2.bar();
module3();
输入结果
/*
1 '让exports等于一个对象'
让exports.xxx = 一个方法
让exports.xxx = 一个方法2
让exports成为一个方法
*/
这是浏览器的实现方法,因为浏览器是无法识别require语句的,所有,我们需要用browserify来编译打包
这个方块里的来自尚硅谷的模块化教程,因为我borwserify无法执行,所以只能引用了
## Browserify模块化使用教程
1. 创建项目结构 ``` |-js |-dist //打包生成文件的目录 |-src //源码所在的目录 |-module1.js |-module2.js |-module3.js |-app.js //应用主源文件 |-index.html |-package.json { "name": "browserify-test", "version": "1.0.0" } ``` 2. 下载browserify * 全局: npm install browserify -g * 局部: npm install browserify --save-dev 3. 定义模块代码 * module1.js ``` module.exports = { foo() { console.log('moudle1 foo()') } } ``` * module2.js ``` module.exports = function () { console.log('module2()') } ``` * module3.js ``` exports.foo = function () { console.log('module3 foo()') } exports.bar = function () { console.log('module3 bar()') } ``` * app.js (应用的主js) ``` //引用模块 let module1 = require('./module1') let module2 = require('./module2') let module3 = require('./module3') let uniq = require('uniq') //使用模块 module1.foo() module2() module3.foo() module3.bar() console.log(uniq([1, 3, 1, 4, 3])) ``` * 打包处理js: * browserify js/src/app.js -o js/dist/bundle.js * 页面使用引入: ``` ```3.AMD(REQUIREJS)
AMD是专用来浏览器的模块化的,通过define()定义与暴露模块,requirejs()引入模块,接下来我们亲自敲敲代码
必须要先在requirejs的官网,下载好requirejs这个文件,才可以使用AMD模块化
首先按照如下规则创建目录与文件
|-js
|-libs 放置requirejs文件的文件夹
|-require.js 用来处理js代码,让他们可以起到模块化的作用
|-modules 用来放置自己定义的代码
|-alerter.js 这是有依赖的一个js模块
|-dataService.js 这是没有依赖的js模块
|-main.js 这是用来引入模块,配置模块的文件
|-index.html 这是用来执行的网页文件
dataService.js
//用define()来写模块,如果有依赖,第一个参数则是一个数组,里面放入要引用的依赖名字
//如果没有,则是一个匿名函数,在里面定义需要的属性和方法,最后用return暴露
define(function () {
let str = "我是没有依赖的暴露amd";
function foo() {
console.log(str);
}
//用来暴露
return {foo};
})
alerter.js
//这个模块依赖之前的模块,则第一个参数是数组,放入了之前模块的名称,第二个参数的参数则是该
//模块的名字,这样就可以在本模块使用之前模块的方法了。
define(["dataService"],function (dataService) {
let str = "我是有依赖的暴露amd"
function foo() {
console.log(str,dataService.foo());
}
return {foo};
})
main.js
(function () {
//这是一个配置文件,配置这些模块的路径,这样才可以找到,并且引用他们
requirejs.config({
//这是一个基本路径
baseUrl: 'js/',
//这是各各文件的路径,如果没有baseUrl这个属性,则以自身所在路径寻找
paths:{
dataService:"./modules/dataService",
alerter:"./modules/alerter"
}
})
//这个方法用来引入需要引入的模块,并且可以执行。
requirejs(["alerter"],function (alerter) {
alerter.foo()
})
})()
index.html
Modular Demo 2
如果使用AMD要引入其他第三方模块,如jQuery,angular,则需要单独配置。
(function () {
//这是一个配置文件,配置这些模块的路径,这样才可以找到,并且引用他们
requirejs.config({
//这是一个基本路径
baseUrl: 'js/',
//这是各各文件的路径,如果没有baseUrl这个属性,则以自身所在路径寻找
//最后的js文件不要有.js后缀。
paths:{
dataService:"./modules/dataService",
alerter:"./modules/alerter",
//因为jQuery的源码支持AMD模块,所以使用AMD模块时,jQuery的名字需要是小写
//,因为jQuery源码检测到使用AMD时,自己定义了模块,就叫jquery。
jquery:"./libs/jquery-1.10.1",
//这是angular的配置地址
angular:"./libs/angular"
},
//如果想使用angular,则需要单独的配置。
shim:{
angular:{
exports:"angular"
}
}
})
//这个方法用来引入需要引入的模块,并且可以执行。
requirejs(["alerter","angular","jquery"],function (alerter,angular,$) {
alerter.foo()
console.log(angular);
$("body").css("background","yellow");
})
})()
CMD只作为了解就好,作为框架的一种,cmd比较像commonjs与amd的结合体。
使用define(function(require,exports,module){}),在回调函数中定义方法和属性,使用module.exports暴露方法,对象。
使用require("路径")同步,require.async("路径",function(){})异步,来引入模块。
而且需要专门的模块来支持sea.js,需要在html文件中引入,再在js代码中,使用 sea.use("主模块路径")来引入主要模块.
module1.js
/*
* 最外层的方法是固定写法,里面的回调函数的参数表示,引用模块,暴露模块
* */
define(function (require,exports,module) {
let info = "module1";
function foo() {
return info;
}
//暴露模块,暴露的是exports这个对象
module.exports = {
foo
}
})
module2.js
define(function (require,exports,module) {
let info = "module2";
function foo() {
console.log(info);
}
module.exports = foo;
})
module3.js
define(function (require,exports,module) {
let info = "module3";
function foo() {
console.log(info);
}
module.exports.foo = {foo};
})
module4.js
define(function (require,exports,module) {
//使用同步的方式引入,返回的对象就是模块中的exports对象
let module2 = require("./module2.js")
module2();
//异步的方式引入,只有全部引入完之后,才会执行回掉函数中的方法。
require.async("./module3.js",function (module3) {
module3.foo.foo();
})
let info = "module4";
function foo() {
console.log(info);
}
module.exports = {
foo
}
})
main.js
//主模块,只需要引入其他模块就可以
define(function (require,exports,module) {
let module1 = require("./module1");
console.log(module1.foo());
require.async("./module4",function (module4) {
module4.foo()
})
})
index.html
Title
ES6的模块化是如今应用最为广泛的,es6语法比较简介 import 导入 ,export暴露,即可。
因为es6模块不被一些浏览器支持,所以,需要
安装babel-cli, babel-preset-es2015和browserify
babel-cli的作用是可是让一些命令在命令行窗口运行,babel-preset-es2015是让js代码编译为es5,
browserify是让一些引用和暴露的代码被识别。
下面来演示一下
module1.js
//分别暴露,exports后加空格直接暴露任意需要暴露的东西
export function foo() {
console.log("foo() 分别暴露");
}
export function bar() {
console.log("bar() 分别暴露");
}
export let arr = [1,2,3,4,5];
module2.js
//统一暴露,export之后加空格暴露对象,对象中放入之前定义好的方法即可
function fun1() {
console.log("fun1 统一暴露");
}
function fun2() {
console.log("fun2 统一暴露");
}
export {
fun1,
fun2
}
module3.js
//默认暴露,语法,export default 暴露对象
//默认暴露的好处是,引入模块时不需要使用解构赋值的情况来引入
//可以直接引入暴露的对象
function foo() {
console.log("foo()");
}
function bar() {
console.log("我是es6的默认暴露");
}
export default {
foo,
bar
}
main.js
//统一暴露与分别暴露都需要使用解构赋值的方式引入在该模块暴露的方法,如果声明一个变量引入,则变量为undefined
import {foo,bar} from "./module1";
import {fun1,fun2} from "./module2";
//默认暴露,可以直接使用一个变量接受模块中暴露的方法。
import module3 from "./module3"
module3.bar();
module3.foo();
foo();
bar();
fun1();
fun2();
然后在目录中配置一个文件,名为 .babelrc 没有后缀名,内容为
{
"presets": ["es2015"]
}
最后使用babel与browserify进行编译,引入最后编译的文件到网页中即可执行。