前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >what is 模块化?

what is 模块化?

作者头像
用户4793865
发布2023-01-12 13:44:05
1.2K0
发布2023-01-12 13:44:05
举报
文章被收录于专栏:前端小菜鸡yym

理解

我们先来了解一下,什么是模块化,以及模块化的进化史。

什么是模块/模块化

将一个复杂的程序按照一定的规范,封装成几个块(文件),并进行组合在一起。 这些模块,最好都做到可复用性,比如可以在多个文件中使用处理时间的模块。

还有,块的内部数据/实现是私有的,只向外部暴露一些接口(方法)与外部其它模块进行通信。

模块化的进化史

最早

最早,我们的js是写到一个文件中,想怎么写怎么写。这种情况,也就是我们最原始的html的方式。会造成变量的全局污染。

代码语言:javascript
复制
var gl = 'gl'
function a(){
    gl = 'glb'
}
console.log(gl)  // 'glb'
function b(){

}

Namespace模式

简单的封装,把一部分属性放到对象中。通过对象.属性调用。 减少Global全局上的变量数目,但本质是对象,完全可以被改掉,不安全。

代码语言:javascript
复制
var wrap = {
    foo:function(){console.log(1)},
    sec:function(){}
}
wrap.foo = function(){alert(11)}
wrap.foo() // 就会弹出11

再看一个例子

如下:对象包括一个变量msg和一个方法foo,foo方法中使用了变量msg。

代码语言:javascript
复制
 let obj = {
     msg:'module2',
     // es6简写
     foo(){
        // 想使用msg 就要使用this,否则是向全局要的msg变量 
        console.log(this.msg)
     }
 }

在另一个文件中使用

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <script type="text/javascript" src="module.js"></script>
</head>
<body>
    <script type="text/javascript">
        obj.foo()
        // 去改obj的msg同样也是可以修改的
        obj.msg = 'NBA'
        obj.foo()     //NBA
    </script>
    
</body>
</html>

IIFE模式

立即执行函数。进来就执行,全局是看不到这个函数里面的数据。因此也修改(操作)不了它。

代码语言:javascript
复制
var Moudle = (fucntion(){
    var _private = 'safe';
    var foo = function(){
        console.log(_private)
    }
    return {
        foo:foo
    }
})()
Module.foo()
Module._private;   // undefined

那我们怎么使用呢?类似于JQuery把它暴露到window上。

引入依赖模式

如JQuery的最外层,其实就是一个立即执行函数。这就是模块模式,也是现代模块实现的基石。JQuery把$jQ 添加给了window。所以我们才可以使用 $

代码语言:javascript
复制
var Moudle = (function(){
    var _$body = $('body');
    var foo = function(){
        console.log(_$body)
    }
    return {
        foo:foo
    }
})(jQuery)
Moudle.foo()

为什么要模块化

  • 降低复杂度
  • 提高解耦性
  • 方便部署。比如不需要轮播图的模块,我们不需要引入

模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离,按需加载
  • 更高复用性
  • 高可维护性

页面引入script带来的问题

当我们需要引入多个js文件。需要写多个标签。如下,如果1.js中用到jquery.js中的内容,这个加载顺序是不可以换的。并且,引入多少个<script>标签,我们就需要发送多少次请求

代码语言:javascript
复制
<script src='jquery.js'></script>
<script src='1.js'></script>
<script src='2.js'></script>
<script src='3.js'></script>
<script src='4.js'></script>
复制代码

所以就带来了如下的问题

  • 请求过多
  • 依赖模糊
  • 难以维护

因此也就需要模块化规范

CommonJS规范

说明

  • 每个文件都被当做一个模块
  • 在服务器端:模块的加载是同步的。因为同步,所以会影响加载时间。
  • 在浏览器端:浏览器引擎不认识require语法,在浏览器端想要使用commonJs规范,模块需要提前编译打包处理。

基本语法

暴露模块

代码语言:javascript
复制
module.exports = value
exports.xxx = value

那么暴露的模块到底是什么? 肯定是个对象,是exports对象。

  • 对于module.exports = valuemodule.exports本就有值,是空对象 {}。然后value把空对象覆盖。
  • exports.xxx = value就相当于向对象添加属性。引入模块
  • 第三方模块:xxx为模块名
  • 自定义模块: xxx为文件路径
代码语言:javascript
复制
require(xxx)  

实现

服务器端

node.js

浏览器端

Browserify,打包工具。

官网:https://browserify.org/

commonJs基于服务端的应用

创建

新建一个COMMONJS文件夹,然后 npm init初始化packe.json(只需要回车就可以)。 modules用于存放所有子模块。modules的同级创建一个app.js它去使用其他子模块。

package.json

中存放着一些包管理信息,和配置信息

代码语言:javascript
复制
{
  "name": "commonjs",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "author": "",
  "license": "ISC"
}

module1

module.exports = value 暴露一个对象。

这就相当于定义一个对象变量。 let obj = {msg:''}

代码语言:javascript
复制
module.exports ={
    msg:'module1',
    foo(){
        console.log(this.msg)
    }
}

module2

暴露一个函数 module.exports = function(){},当我们在他下面新加 module.export =function(){}会将之前的覆盖掉。

这就相当于 定义一个 let a = function(){console.log(1)} 之后 再 a = function(){console.log(2)}

代码语言:javascript
复制
module.exports = function(){
    console.log('module2')
}

// 在写会覆盖之前的
// module.exports = function(){
//     console.log('覆盖了')
// }

module3

exports.xxx = value 就可以随意向exports对象中添加内容,都可以导出。

代码语言:javascript
复制
// exports.xxx = value
exports.foo = function(){
    console.log('foo() module3')
}
exports.bar = function(){
    console.log('bar() module3')
}

app.js

调用,其他模块内容

使用require引入,因为是自定义模块所以是路径。

代码语言:javascript
复制
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
// 现在module1是一个对象
module1.foo()
// module2等价于函数
module2()

module3.bar()
module3.foo()

然后运行看一下效果, 在app.js的层级运行node app.js

使用第三方模块

uniq 为什么需要加 --save ?

因为npm5.0之后都是默认加上了--save。所以不加上--save也会下载

代码语言:javascript
复制
npm install uniq --save

在module3中添加一个数组

代码语言:javascript
复制
// exports.xxx = value
exports.foo = function(){
    console.log('foo() module3')
}
exports.bar = function(){
    console.log('bar() module3')
}
exports.arr = [6,8,4,2,3,3,11]

在app中引入数组和uniq。注意第三方模块和自定义模块引入方式的区别。

代码语言:javascript
复制
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
let uniq = require('uniq')
// 现在module1是一个对象
module1.foo()
// module2等价于函数
module2()

module3.bar()
module3.foo()

let data = uniq(module3.arr)
console.log(data)  // [ 11, 2, 3, 4, 6, 8 ]

uniq可以去重,除了去重还可以排序。但是也存在一些问题,比如11排序到了2前面,因为它是按数字第一位编码排序的。

commonJs基于浏览器端的应用

我们上面的服务端是基于node环境去运行的。而我们浏览器端,则是需要运行在浏览器页面的。

创建项目

  • js/dist 文件用于存放打包后的文件。一些框架中的build其实和dist一样,都是存放打包后的文件的。
  • js/src 存放的就是我们的功能代码
  • index.html 因为是浏览器端,不同于服务端的node,我们要在浏览器上展示效果。所以需要index.html
  • package.json 这里我们手动创建了。在里面添加了项目名称 和 版本号,之后就可以进行npm 安装其他包了。
代码语言:javascript
复制
{
    "name": "commonjs_browserify",
    "version": "1.0.0"
}

安装browserify

browserify的作用就是打包,那项目放到线上环境,就与他无瓜了。

如下的安装命令,全局和局部都需要安装它。

代码语言:javascript
复制
## 全局 
   npm install browserify -g
## 局部
   npm install browserify --save-dev

问题 这里局部安装时为什么会有 dev ?

那就需要知道如下的两个概念

  • 开发依赖(devDependencies):自己开发使用的依赖
  • 运行依赖(dependencies):打包上线的依赖。 而 --save-dev 就代表着开发依赖。

言归正传,我们进入到commonJS_Browserify项目中运行 局部安装命令和全局安装命令。安装成功后,我们看一下package.json的内容。

代码语言:javascript
复制
{
    "name": "commonjs_browserify",
    "version": "1.0.0",
    "devDependencies": {
        "browserify": "^17.0.0"
    }
}

不知道,你发没发现,这个依赖是加入到了devDependencies中的?我们在试一下安装uniq

代码语言:javascript
复制
npm install uniq    // 这里其实相当于默认加了 --save
代码语言:javascript
复制
{
    "name": "commonjs_browserify",
    "version": "1.0.0",
    "devDependencies": {
        "browserify": "^17.0.0"
    },
    "dependencies": {
        "uniq": "^1.0.1"
    }
}

可以看到uniq加入到了dependencies下而不是devDependencies。

开始测试

我们还是使用服务端的module中的内容

module1

代码语言:javascript
复制
// module.exports = value 暴露一个对象
module.exports ={
    msg:'module1',
    foo(){
        console.log(this.msg)
    }
}

module2

代码语言:javascript
复制
// 暴露一个函数 module.exports = function(){}

module.exports = function(){
    console.log('module2')
}

module3

代码语言:javascript
复制
// exports.xxx = value
exports.foo = function(){
    console.log('foo() module3')
}
exports.bar = function(){
    console.log('bar() module3')
}
exports.arr = [6,8,4,2,3,3,11]

app

因为现在app.js和其他三个module.js文件在一个文件夹下。所以引入方式如下

代码语言:javascript
复制
let module1 = require('./module1');
let module2 = require('./module2');
let module3 = require('./module3');

module1.foo();

// 本质就是函数
module2();


module3.foo()
module3.bar()

我们试一下在index.html中直接引入app.js这个js的主文件

代码语言:javascript
复制
<html>
    <head></head>
    <body>
        <script src="./js/src/app.js"></script>
    </body>
</html>

如下报错:require is not defined。正如我们前面提到的,浏览器不认识require。因此需要browserify进行打包。

打包处理js

代码语言:javascript
复制
browserify js/src/app.js -o js/dist/bundle.js
browserify 要打包的文件 -output缩写 打包到的文件

可以看到bundle.js文件已经生成了

如下就是bundle.js文件的内容,也就是browserify进行打包后的文件。

index.html中引用

引用打包后生成的文件。

代码语言:javascript
复制
<html>
    <head></head>
    <body>
        <script src="./js/dist/bundle.js"></script>
    </body>
</html>

AMD规范(不常用)

说明

AMD(Asyncchronous Module Definition) 异步模块定义。其主要用于浏览器端,模块的加载是异步的。他的出现是比commonJs规范的浏览器端实现,要早的。

AMD是要依赖于一个库的 Require.js

定义暴露模块

定义没有依赖的模块

对于没有依赖的模块,define是一个方法,方法中只需要一个函数。而你想暴露的模块只需要return出去就可以使用了。

代码语言:javascript
复制
define(funtion(){
    return 模块
})

定义有依赖的模块

此时,define方法的参数,就需要传两个了。

  • 第一个参数是数组,也就是依赖于其它模块够成的一个数组
  • 第二个参数同样是一个函数,但是需要有形参。形参与这个数组一一对应。
代码语言:javascript
复制
define(['module1','module2'],function(){
    return 模块
})

引入使用模块

代码语言:javascript
复制
require(['module1','module2'],function(m1,m2){
    使用 m1 / m2
})

AMD规范的应用

NoAMD

我们先看一下,不遵循模块化规范,也就是不使用Require.js依赖的方式。

新建文件

js/dataService.js 这个js模块,是没有引用外部依赖的。现在这个模块暴露到外面的名字是dataService。

代码语言:javascript
复制
// 立即执行函数
(function (window) {
    let name = 'dataService.js';
    function getName() {
        return name
    }
    // 暴露给window  这个模块现在的名字:dataService
    window.dataService = { getName }   // 这里相当于 {getName:getName}
})(window)

js/alerter.js 我们的alert.js,依赖了上面的dataService模块

代码语言:javascript
复制
// 定义一个有依赖的模块
(function (window, dataService) {
    let msg = 'alerter.js'
    function showMsg() {
        console.log(msg, dataService.getName())
    }
    window.alerter = { showMsg }
})(window, dataService)

App.js 在主模块中使用了alert模块

代码语言:javascript
复制
(function (alerter) {
    alerter.showMsg()
})(alerter)

index.html

(O_O)? 直接引入主文件可以吗 ?

代码语言:javascript
复制
<html>
    <head></head>
    <body>
        <script src="./App.js"></script>  
    </body>
</html>

当然是不太行。

📢 此处需要像我们最早的html一样,通过script标签,并且需要按顺序的引入文件。

代码语言:javascript
复制
<html>
    <head></head>
    <body>
        <!-- 这么引入可以吗? -->
        <script src="./js/dataService.js"></script>

        <script src="./js/alerter.js"></script>
        <script src="./App.js"></script>  
    </body>
</html>

AMD规范

require.js 官网 https://requirejs.org/

创建文件 与上面类似

libs/require.js

其内容复制于 requreJs官网 https://requirejs.org/docs/release/2.3.6/comments/require.js

其内容大概如下,不需要看懂

dataService.js

同样,是没有依赖其它模块的。我们上面没有引入reuireJs规范时,是通过window暴露的。而requireJs则只需要return 模块。

代码语言:javascript
复制
// 定义没有依赖的模块
define(function () {
    let name = 'dataService.js'
    function getName() {
        return name;
    }
    // 暴露模块  之前都是window去暴露
    return { getName }

})

alerter

是有依赖模块的。记得数组中的参数,与回调函数中的参数对应上。当然可以名字不对应上,但是为了更好的维护性,还是建议同名。

代码语言:javascript
复制
// 定义有依赖的模块

define(['dataService'], function (dataService) {
    let msg = 'alerter.js'
    function showMsg() {
        console.log(msg, dataService.getName())
    }
    return { showMsg }
})

main.js

我么看一下requireJs官网是怎么使用的,如下的app.js代表着主文件,我们这里用的是main.js

paths中的模块名要与模块所在位置对应,因为requirejs在 js/libs下。所以,我们dataService模块的相对路径为 ./modules/dataService,alerter同理。

  • 并且我们注释掉了baseUrl,因为此时我们是在main.js的角度去找的其他两个模块的路径。
  • 如果我们不注释掉,出发点则是在根目录的角度。baseUrl是会拼到./modules/dataService前面的。
  • ./modules/dataService为什么不加.js呢?因为它会默认加.js,如果我们加了就会 .js.js
代码语言:javascript
复制
  (function () {
        requirejs.config({
            // baseUrl:'js/libs'
            paths: {
                // 因为alerter中使用了
                dataService: './modules/dataService',
                alerter : './modules/alerter'
            }
        })
        // 这里就不需要再向外暴露了 因为已经是主文件了
        requirejs(['alerter'], function (alerter) {
            alerter.showMsg()
        })
    })()

index.html

最后需要在html文件中引入主文件,官网的内容如下

代码语言:javascript
复制
<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <!-- src进来会先加载require.js  加载完成后去加载data-main中的内容也就是主文件 -->
        <script data-main="js/main.js" src="js/libs/require.js"></script>
    </body>
</html>

调用模块成功

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解
    • 什么是模块/模块化
      • 模块化的进化史
        • 最早
        • Namespace模式
        • IIFE模式
        • 引入依赖模式
      • 为什么要模块化
        • 模块化的好处
          • 页面引入script带来的问题
            • 所以就带来了如下的问题
        • CommonJS规范
          • 说明
            • 基本语法
              • 暴露模块
              • 实现
            • commonJs基于服务端的应用
              • 创建
              • module1
              • module2
              • module3
              • app.js
              • 使用第三方模块
            • commonJs基于浏览器端的应用
              • 创建项目
              • 安装browserify
              • 开始测试
              • 打包处理js
              • index.html中引用
          • AMD规范(不常用)
            • 说明
              • 定义暴露模块
                • 定义没有依赖的模块
                • 定义有依赖的模块
              • 引入使用模块
                • AMD规范的应用
                  • NoAMD
                  • AMD规范
              相关产品与服务
              云服务器
              云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档