前端模块化开发

一、什么是模块化?



简单来说,将一个项目划分为不同的功能模块来开发维护的过程就是模块化。


js一开始并没有模块化的概念,直到ajax被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理。模块化就是衍生出来解决这些问题的一种方法。




模块化开发优点:

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性
  • 灵活架构,焦点分离,方便模块间组合、分解
  • 多人协作互不干扰



二、前期

在早期,模块化还没有出现,此时人们采用以下方法来解决问题



全局function模式/文件划分方式

直接将不同功能代码写到一个个文件中,在主页面引入需要的功能文件,即模块,
然后直接调用模块中的变量函数等

在这里举个具体例子,先创建两个js文件



module1.js

var module1 = function(){
    console.log("This is module1");
}

var module2 = function(){
    console.log("module1's module2");
}


module2.js

var module2 = function(){
    console.log("This is module2");
}

var module1 = function(){
    console.log("module2's module1");
}


主页面

<body>
    

    <script src="JS/module1.js"></script>
    <script src="JS/module2.js"></script>

    <script>

        module1();
        module2();
    </script>
</body>

结果

请添加图片描述



可以看到这种方式有很大的缺陷,所有模块都在全局作用域中作用,模块一多,就很可能造成全局作用域污染,命名冲突等







namespace 模式

每一个模块只暴露一个全局变量,其它模块成员都挂到这个全局变量上

同样举个例子,创建两个js文件,分别创建一个对象,在对象中添加需要的属性,函数,变量等等



module1.js

var module1 = {
    name: "module1",
    fn: function(){
        console.log("namespace——"+this.name+"ok")
    }
}


module2.js

var module2 = {
    name: "module2",
    fn: function(){
        console.log("namespace——"+this.name+"ok")
    }
}


主页面

<body>
    

    <script src="JS/module1.js"></script>
    <script src="JS/module2.js"></script>

    <script>

        module1.fn();
        module2.fn();
    </script>
</body>


结果

请添加图片描述



特点

  • 通过「命名空间」减小了命名冲突的可能,
  • 但是同样没有私有空间,所有模块成员也可以在模块外部被访问或者修改,
  • 而且也无法管理模块之间的依赖关系。




IIFE

使用立即执行函数(IIFE)表达式为模块提供私有空间,将每个模块放在函数提供的私有作用域上,将需要暴露的成员,挂到到全局变量上



;(function(){
    var name = "module1";

    function method1(){
        console.log("The first"+name);
    }

    function method2(){
        console.log("The second"+name);
    }


    window.method = {
        method1:method1,
        method2:method2
    }
})()

注意:在匿名函数前加上“;”,是为了防止出现编译错误,例如:
var a = 10
(function(){ })()
该代码会被解析成 var a = 10(function(){ })(),即将10看作一个函数,此时会报错



<body>
    

    <script src="JS/module1.js"></script>

    <script>
        method.method1();
        method.method2();
    </script>
</body>


结果
在这里插入图片描述





IIFE模式加强——依赖注入

在匿名函数参数中加入依赖项



JS文件

;(function(window,$){
    var name="Rohatt";

    var method = function(){
        console.log(name+":This is IIFE method");
        $('body').css({
            "background": "red"
        })
    }
    window.module2 = {
        method:method
    }

})(window,jQuery)

注:如果想保持undefined的值不变(有些浏览器中undefined可以被赋值),可以在匿名函数中添加undefined参数,而不引入,如下:

(function(window,undefined){

})(window)

在jQuery中就存在这种写法,除了避免undefined被修改,也可以减少全局污染,对外只暴露$,jQuery,从作用域链上来说,也减少了查找的层级



主页面

<body>
    
    <script src="../../jquery/1.js"></script>

    <script src="JS/module2.js"></script>

    <script>
        module2.method();
    </script>
</body>






三、模块化开发方式

1.CommonJs规范(重点)


CommonJs是以在浏览器环境之外构建javascript生态系统为目标产生的项目,比如服务器和桌面环境
CommonJs是同步加载模块,但也可以在浏览器端实现



特点

  • 一个文件就是一个模块
  • 每个模块都有单独的作用域
  • 通过module.exports导出成员
  • 通过require函数载入模块


使用前提

需要下载node.js服务器



node.js下载完成后,可以检查是否下载成功:
在命令行输入node --version,看是否能查到node.js版本号,如果有就说明下载成功




基本语法
  • 暴露模块

module.exports = value; 一般来说,value是一个对象/函数
export.xxx = value;

  • 引入模块

require(xxx)
自定义模块:xxx——文件路径
第三方模块:xxx——文件名称




服务器端运行

示例如下,先创建一个js文件m1.js,写下需要的功能

var name = "Jonhson"

function fn(){
    console.log('m1.js的模板调用');
}

exports.fn = fn;
exports.name = name;

之后再创建一个js文件引入模块,main.js

var m1 =require('./m1')

// console.log(m1.fn());
console.log(m1);

之后使用node.js运行(这里使用的是gitbash),得到一下结果

在这里插入图片描述

undefined是log函数的返回值





浏览器端运行

在浏览器端不能直接使用node.js来运行文件,此时需要下载一个工具包browserify来帮助你在浏览器加载模块

下载地址


如果是window10系统,需要先打开管理员权限(window+x),再进行下载
之后,输入命令npm install -g browserify下载
-g代表全局安装,可以在命令行执行这个命令

如下图:

请添加图片描述





同样来举个例子。还是先创建js文件,m1.js,app.js


//m1.js文件


var a = "m1";

function fn(){
    console.log(a + ":Hello! nice to meet you");
}

function fn1(){
    console.log("This is fn1");
}

module.exports = {
    'fn':fn,
    'fn1':fn1
}



//app.js文件

var m1 = require('./m1');
m1.fn1()
<body>
    <!--引入需要使用的模块-->
  <script src="./app.js"></script>
</body>

此时控制台会报错,浏览器无法识别require


在这里插入图片描述


这是就需要用browserify进行编译,使用命令:
browserify app.js -o bundle.js
将app.js文件编译为bundle.js文件

在相应的文件夹中进行


编译成功后可以看到多文件夹中了一个bundle.js文件

bundle.js文件

(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
var m1 = require('./m1');

m1.fn1()
},{"./m1":2}],2:[function(require,module,exports){
var a = "m1";

function fn(){
    console.log(a + ":Hello! nice to meet you");
}

function fn1(){
    console.log("This is fn1");
}

module.exports = {
    'fn':fn,
    'fn1':fn1
}

},{}]},{},[1]);

之后再在index页面引用bundle.js文件就可以了







2.AMD规范


AMD:Asynchronous Module Definition,异步模块定义

它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。



AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js

require.js下载



基本语法
  • 引入模块

require ([‘module1’,‘module2’],function(m1,m2){
     业务代码
})

  • 定义暴露模块

定义没有依赖的模块

define(function(){
return 模块;
})

定义存在依赖的模块

define([‘module1’,‘module2’],function(m1,m2){
return 模块;
})





引入自定义模块

将开发的文件单独封装为一个模块

  • 定义好文件(modules/module.js)
define(function(){
    var name = "chole";

    function getMaxNumber(arr){
        return Math.max.apply(null,arr);
    }

    // 返回对象
    return { 'getMaxNumber':getMaxNumber,'name':name }

});
  • 配置和使用模块(main.js)

使用requirejs.config进行配置,即标明引用模块的路径
requirejs([ ],function(){ }),[ ]和path中一致,function中的参数为模块的返回值,即暴露出来的变量


// 主模块

requirejs.config({

     //在未配置baseUrl时,m1中文件路径为相对于main.js的路径
    //模块不需要写后缀
    // baseUrl: 'js/lib',
    paths: {
        // jquery: './libs/1',
        m1:'./modules/module'
    }
});


requirejs(['m1'],function(method1){
    // $('body').css({
    //     'background':'green'
    // })
    console.log(method1);

    var arr = [1,23,132,4];
    console.log(method1.getMaxNumber(arr));
})

  • 使用模块

引入require.js文件,同时引入接口文件

<body>
    <!-- require.js文件实现了amd规范,引入了define,require函数 -->
    <!-- main.js为项目入口文件 -->
    <script  data-main="JS/main.js" src="JS/require.js"></script>
</body>
  • 结果


    在这里插入图片描述




引入第三方模块

在require.js官方网址中有相关介绍

在这里插入图片描述

链接




例如:jQuery支持AMD规范

放图

jQuery文件中进行了模块化

在这里插入图片描述



以jquery为例:
只需在刚才的main.js文件中添加相关路径即可

  • 配置和使用模块
requirejs.config({
    // baseUrl: 'js/lib',
    paths: {
        jquery: './libs/1',
        m1:'./modules/module'
    }
});


requirejs(['jquery','m1'],function($,method1){
    $('body').css({
        'background':'green'
    })
    console.log(method1);

    var arr = [1,23,132,4];
    console.log(method1.getMaxNumber(arr));
})
  • 结果(body背景色变为绿色)

在这里插入图片描述




3.CMD规范


在 CMD 规范中,一个模块就是一个文件


基本语法
  • 定义模块

define(factory)
factory 参数可以是一个函数,也可以是一个对象或字符串。


factory为对象、字符串时,表示模块的接口就是该对象、字符串

定义对象:
define({‘name’:‘giant’,‘word’:‘nice to meet you’})

定义字符串
define(“I’m a guy, so what?”)

两者返回值都为该参数值,可自行实验


factory为函数时,默认会传入三个参数:require、exports 和 module

define(function(require, exports, module) ,参数之间的顺序不可调换

require
require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口

exports
exports 是一个对象,用来向外提供模块接口。(也可以通过return直接向外提供接口)

module
module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。


module.exports : 当前模块对外提供的接口。

传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports

  • 提供模块接口

上面已经提到,使用exports或module.exports
exports.属性名 = 需提供的内容
module.exports = { 键值对 }         以对象形式添加

  • 引入依赖模块

require(’ 依赖模块地址 ')

  • 加载模块

seajs.use(id, callback?)

id
id为加载模块的路径,加载多个模块时,使用数值的形式添加路径
callback
回调函数,可以没有参数





引入模块并使用

在这里举个例子,包含依赖模块引入和两种提供模块接口的方法

先创建一个底层工具库utils.js,返回随机整数值

define(function(require,exports,module){
    
    module.exports = {
        getNumberByMathRadom () {
            return Math.random();
        },

        //返回N到M之间的一个数
      getNumberByNtoM (n,m) {
        return  n + Math.ceil( (m-n)*Math.random());
       }
    }
})

之后创建m1.js模块文件,通过require引入utils,js

// define(factory)

define(function(require,exports,module){

    function getMaxNumber(arr){
        return Math.max.apply(null,arr);
    }


    // 引入依赖的模块
    var utils = require('utils');

    var num = utils.getNumberByNtoM(12,89);
    var name = "m1.js";
    exports.getMaxNumber = getMaxNumber;
    exports.name = name;
    exports.num = num;
})

可以再创建一个m2.js模块文件 (注释详解)

define(function(require,exports,module){

    function getMaxNumber(arr){
        return Math.max.apply(null,arr);
    }

    var name = "m2.js";
    var age = "20";
    var hobby = "swing";

    // module.exports = {
    //     'name':name,
    //     'age':age,
    //     'hobby':hobby,
    //     'getMaxNumber':getMaxNumber
    // }


    // es6新增规范
    //在对象中,如果变量名和属性值名称一致,则可以省略属性值不写

    //注:es6中把方法中的function()进行了省略
    module.exports = {
        name,
        age,
        hobby,
        getMaxNumber,
        fn () {
            console.log(this.name);
        }
    }

})

之后创建main.js文件,引入模块

seajs.use(['m1','m2'],function(module1,module2){
    console.log(module1);

    var arr = [1,23,4,1];
    console.log(module1.getMaxNumber(arr));

    console.log(module2);
    module2.fn();

})

最后在主页面调用


<body>
    
    <script src="sea.js"></script>
    <script src="main.js"></script>
</body>

结果

在这里插入图片描述





4.ES6规范(重点)


ES6模块化的设计思想是尽量的静态化,使得在编译时就能够确定模块之间的依赖关系。



基本语法


  • 模块导出

两种导出方式:

命名导出
可以在变量声明的同时导出
export foo = “foo”
export const foo = " foo "


export关键字不能嵌套在某个块中,且export语句和导出的位置没有顺序限制


默认导出
使用default关键字声明,每个模块只能有一个默认导出
const foo = " foo ";
export default foo;


只要导出中含有default,就会将其识别为默认导出
ES6支持在一个模块中同时定义这两种导出,
export { foo as default , bar };

  • 模块导入

import { } from ’ 路径 ’
import { foo } from ’ ./fooModule.js ’


import和export类似,不能嵌套在某个块中,和导入的位置没有顺序限制
如果在浏览器中通过标识符原生加载模块,文件必需带有js扩展名

如果想把一个模块中的所有命名导出集中到一块,可以使用     导出
export * from ’ ./foo.js ';
将foo.js中所有的命名导出出现在导入的模块中
冲突
使用也可能发生导出名称冲突,发生冲突时,结果为最后一个名称相同的导出文件






如何提高js代码质量:

参考文章








参考资料:

前端开发:什么是模块化?如何使用?

前端工程化 — 模块化开发

seajs

深入学习CommonJS和ES6模块化规范

Commonjs、esm、Amd 和 Cmd 的循环依赖表现和原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值