模块化基础知识

1 简介

1.1 模块

将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起

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

一个模块的组成:

  • 私有的数据:内部的变量
  • 私有的行为(操作数据):内部的函数
  • 向外暴露n个行为

1.2 模块化

描述一种特别的编码项目JS的方式:以模块为单元一个一个编写的

模块化的项目: JS编码时是按照模块一个一个编码的

模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块,可以提高了代码的复用性

、提高了代码的可维护性、可以实现按需加载

2 模块化的进化过程

2.1 全局function模式

编码: 全局变量/函数

问题: 污染全局命名空间, 容易引起命名冲突/数据不安全

案例:module1.js

// 数据
let data = 'module1.js'

// 操作数据的函数
function foo() {
  console.log(`foo() ${data}`)
}
function bar() {
  console.log(`bar() ${data}`)
}

module2.js

let data2 = 'module2.js';

function foo() {  //  与另一个模块中的函数冲突了
  console.log(`foo() ${data2}`)
}

test1.html

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  let data = "修改后的数据"
  foo()
  bar()
</script>

2.2 namespace模式: 简单对象封装

编码: 将数据/行为封装到对象中

解决: 命名冲突(减少了全局变量)

问题: 数据不安全(外部可以直接修改模块内部的数据)

案例:module1.js

let myModule = {
  data: 'module1.js',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

module2.js

let myModule2 = {
  data: 'module2.js',
  foo() {
    console.log(`foo() ${this.data}`)
  },
  bar() {
    console.log(`bar() ${this.data}`)
  }
}

test2.html

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  myModule.foo()
  myModule.bar()

  myModule2.foo()
  myModule2.bar()

  myModule.data = 'other data' // 能直接修改模块内部的数据
  myModule.foo()
</script>

2.3 IIFE模式/ IIFE 增强模式

IIFE : 立即调用函数表达式--->匿名函数自调用

编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口

引入依赖: 通过函数形参来引入依赖模块

(function(window, module2){
    var data = 'atguigu'
    function foo() {
       module2.xxx()
       console.log('foo()'+data)
    }
    function bar() {
       console.log('bar()'+data)
    }

    window.module = {foo}
})(window, module2)

3 CommonJS

Commonjs可以在服务器端(通过Nodejs)和浏览器端(通过Browserify)实现

nodejs是在运行时,动态同步引入到模块中的,而Browserify则是在运行前就对模块进行编译打包的处理(已经将依赖的模块包含进来了),运行的是打包生成的js, 运行时不存在需要再从远程引入依赖模块

3.1 基本语法

3.1.1 向外暴露成员:exports或module.exports

// 1.  分别向外暴露多个成员
exports.xxx = xxx
exports.yyy = yyy


// 2.  统一向外暴露多个成员
module.exports = {
    xxx,
    yyy
}

// 3.  默认只向外暴露一个成员
module.exports=XXX

只向外暴露一个成员,必须使用 module.exports=XXX 这种方式

3.1.2 引用使用模块 require

var module = require('模块名/模块相对路径')

3.2 commonjs 模块原理

在 Node 中,每个模块内部默认都有一个自己的 module 对象

console.log(module)

// 返回结果:
  id: '.',
  exports: {},  // 默认也是一个对象
  parent: null,
  filename: 'E:\\nodejs\\资料\\03\\code\\1.js',
  loaded: false,
  children: [],
  paths:
   [ 'E:\\nodejs\\资料\\03\\code\\node_modules',
     'E:\\nodejs\\资料\\03\\node_modules',
     'E:\\nodejs\\资料\\node_modules',
     'E:\\nodejs\\node_modules',
     'E:\\node_modules' ] }

每个模块最后都会默认 return module.exports,所以如果你想要导出一个成员,直接给 exports 赋值是不起作用的

// 导出一个成员,直接给 exports 赋值是不管用的
exports='hello' 

// 必须是直接给 module.exports 直接赋值 才可以
module.exports='hello'

该 module 对象中,有一个成员叫:exports 也是一个对象,默认是空对象

console.log(module.exports)  // 返回结果:{}

也就是说如果你需要对外导出成员,只需要把导出的成员挂载到 module.exports 中即可

因为每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式,Node 为了简化你的操作,专门提供了一个变量:exports 等于 module.exports

console.log(exports === module.exports) // true

exports.foo='bar'
// 等价于
module.exports.foo='bar'

案例:

// {foo: bar}
exports.foo = 'bar'

// {foo: bar, a: 123}
module.exports.a = 123

// exports !== module.exports
// 最终 return 的是 module.exports
// 所以无论你 exports 中的成员是什么都没用
exports = {
    a: 456
}

// {foo: 'haha', a: 123}
module.exports.foo = 'haha'

// 没关系,混淆你的
exports.c = 456

// 重新建立了和 module.exports 之间的引用关系了
exports = module.exports

// 由于在上面建立了引用关系,所以这里是生效的
// {foo: 'haha', a: 789}
exports.a = 789

// 前面再牛逼,在这里都全部推翻了,重新赋值
// 最终得到的是 Function
module.exports = function() {
    console.log('hello')
}

3.3 服务器端实现模块化

3.3.1 初始化项目

会自动生成一个 package.json配置文件

npm init -y

案例的目录结构如下:

|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
  "name": "Commonjs-Nodejs",
  "version": "1.0.0"
}

3.3.2 安装项目依赖

# 用于数组去重
npm install uniq --save  
# 或者是下面的简写
npm i -S uniq 

3.3.3 模块化编码案例代码

module1.js

向外 暴露一个 对象:module.exports=XXX

 // 向外暴露一个 对象
 module.exports = {
   foo() {
     console.log('向外暴露一个对象:module1 foo()')
   }
 }

module2.js

向外 暴露一个 函数:module.exports=XXX

// 向外暴露一个 函数
module.exports = function () {
  console.log('向外暴露一个函数:module2()')
}

module3.js

// 分别向外暴露 成员
exports.foo = function () {
  console.log('分别向外暴露对象:module3 foo()')
}

exports.bar = function () {
  console.log('分别向外暴露对象:module3 bar()')
}

exports.arrData=[1,2,3,4,2,5,6,8,31,22]

// 或者一次性向外暴露多个成员
function foo(){
    console.log('分别向外暴露对象:module3 foo()')
}
function bar(){
    console.log('分别向外暴露对象:module3 bar()')
}
module.exports={
    foo:foo,
    bar:bar,
    arrData:arrData
}
// 还可以简写为:
module.exports={
    foo,
    bar,
    arrData
}

app.js:主模块文件

// 引入核心模块
const fs = require('fs')
// 引入第三方模块
const uniq = require('uniq')
// 引入自定义模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')

// 使用模块
module1.foo(); //module1是暴露的一个对象,对象是一个函数

module2(); //module2是暴露的一个函数

module3.foo() //module3是暴露的一个对象
module3.bar() //module3是暴露的一个对象
//使用第三方包(uniq数组去重)
console.log(uniq(module3.arrData))

3.3.4 通过node运行编译 app.js

node app.js

3.4 浏览器端实现模块化

3.4.1 初始化项目

会自动生成一个 package.json配置文件

npm init -y

案例的目录结构如下:

|-js
|-dist // 打包生成文件的目录
|-src // 源码所在的目录
  |-module1.js
  |-module2.js
  |-module3.js
  |-app.js / /应用主源文件
|-index.html
|-package.json
{
  "name": "browserify-test",
  "version": "1.0.0"
}

3.4.2 安装项目依赖

# 全局安装
npm install browserify -g

# 项目局部安装  --save-dev 开发依赖
npm install browserify --save-dev

3.4.3 定义模块代码

module1.js

 // 向外暴露一个对象
 module.exports = {
   foo() {
     console.log('向外暴露一个对象:module1 foo()')
   }
 }

module2.js

   //向外暴露一个函数
     module.exports = function () {
       console.log('向外暴露一个函数:module2()')
     }

module3.js

 // 分别向外暴露成员 foo bar arrData
 exports.foo = function () {
   console.log('分别向外暴露对象:module3 foo()')
 }

 exports.bar = function () {
   console.log('分别向外暴露对象:module3 bar()')
 }

 exports.arrData=[1, 3, 1, 4, 3]

// 或者一次性向外暴露多个成员
module.exports={
    foo,
    bar,
    arrData
}

app.js (应用的主js):主模块js文件

   //引用模块
   const module1 = require('./module1')
   const module2 = require('./module2')
   const module3 = require('./module3')
   //使用模块
   module1.foo()

   module2()

   module3.foo()
   module3.bar()

3.4.4 打包编译处理js

browserify ./src/app.js -o ./dist/bundle.js 

4 ES6模块化

ES6本身就内置了模块化的实现,但是目前并不是所有浏览器都能直接识别ES6模块化的语法 ,一个好的解决方案是使用 webpack编译打包

4.1 基本语法

4.1.1 向外暴露成员:export

// 1. 默认暴露一个成员
export default 成员

// 2. 分别暴露多个成员
export const a = value1 // 分别暴露一个成员
export let b = value2 // 分别暴露一个成员

// 3. 统一暴露多个成员
const c = value1
let d = value2
export { c, d }

4.1.2 引用使用模块 import

// 1. 引入default默认暴露的模块:
import xxx from '模块路径/模块名'

// 2. 引入一般模块
import { a, b } from '模块路径/模块名'
import * as module1 from '模块路径/模块名'

4.2 模块化实现

4.2.1 向外暴露成员

module1.js :分别向外暴露多个成员 export XXX

export function foo() {
  console.log('module1 foo()')
}

export function bar() {
  console.log('module1 bar()')
}

export const DATA_ARR = [1, 3, 5, 1]

module2.js :统一向外暴露多个成员 export {XXX,YYY}

let data = 'module2 data'

function fun1() {
  console.log('module2 fun1() ' + data);
}

function fun2() {
  console.log('module2 fun2() ' + data);
}

// 统一向外暴露多个成员
export {fun1, fun2}

module3.js: 默认向外暴露 export default XXX

默认暴露:可以暴露任意数据类型,暴露什么数据,就可以接收什么数据

默认暴露只能向外暴露一次

// 1.  默认暴露一个函数
export default () => {
  console.log('我是默认暴露的箭头函数')
}

// 2.  默认暴露一个对象
export default {
    msg: 'hello world',
    foo() {
        console.log('我是默认暴露里面的回调函数:'+this.msg.toUpperCase())
    }
}

4.2.2 引入使用模块 import

1、 如果模块文件采用的是: 分别向外暴露多个成员统一向外暴露多个对象 的方式,那么引入模块的时候,可以采用 对象解构的方式

import {foo, bar, DATA_ARR} from './module1';
foo();
bar();
console.log(DATA_ARR);

import {fun1, fun2} from './module2';
fun1();
fun2();

2、 如果模块文件采用的是:默认暴露的方式,可以直接获取模块

import module3 from './module3'
module3();

3、 引入第三方模块

//引入第三方模块
import $ from 'jquery';
$('body').css('background', 'red')

4.3 浏览器端实现模块化

4.3.1 项目说明

1、 使用 Babel 将 ES6 编译为 ES5 代码

2、 使用 Browserify 编译打包 js

Browserify编译参考文档

4.3.2 实现步骤

1 初始化项目

npm init -y

2 安装项目依赖

# 安装babel依赖:
npm i @babel/core @babel/cli @babel/preset-env -D

# 安装browserify依赖:
npm install browserify -D

3 配置文件

新建 .babelrc 文件,配置babel预设(或者文件名为: babel.config.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
           "ie":"10", //这个是自己添加的,最好是加上
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage"  (这个选项可以不写)
      }
    ]
  ]
}

4 编码

module1.js:分别暴露多个成员

export function foo() {
  console.log('module1 foo()')
}

export function bar() {
  console.log('module1 bar()')
}

export const DATA_ARR = [1, 3, 5, 1]

module2.js :统一暴露多个成员

let data = 'module2 data'

function fun1() {
  console.log('module2 fun1() ' + data);
}

function fun2() {
  console.log('module2 fun2() ' + data);
}

//统一暴露对象
export {fun1, fun2}

module3.js : 默认暴露一个成员

//默认暴露一个箭头函数
export default () => {
  console.log('我是默认暴露的箭头函数')
}

module4.js:默认暴露一个对象

//默认暴露一个对象
 export default {
   msg: 'hello world',
   foo() {
     console.log('我是默认暴露里面的回调函数:'+this.msg.toUpperCase())
   }
 }

main.js:主模块文件

import {foo, bar, DATA_ARR} from './module1';
foo();
bar();
console.log(DATA_ARR);

import {fun1, fun2} from './module2';
fun1();
fun2();

//采用默认暴露的方式:引入模块的方法
import module3 from './module3'
module3();

import module4 from './module4'
module4.foo();

//引入第三方模块
import $ from 'jquery';
$('body').css('background', 'red')

5 编译

1、 使用Babel将ES6编译为ES5代码

babel js/src -d js/build

2、 使用Browserify编译js :

browserify js/build/main.js -o js/build/bundle.js

5 AMD require.js

基本语法

	// 定义要向外暴露的模块:    
	define([依赖模块名], function() {
	    return 模块
	})

	// 引入模块:       
	require(['模块1', '模块2'], function(m1, m2) {
	    //使用m1与m2
	})

	//配置     
	require.config({
	    // 基本路径        
	    baseUrl: 'src/',
	    //映射: 模块标识名: 路径       
	    paths: {
	        //自定义模块       
	        'a': 'modules/a', // 可以不加后缀名  
	        'b': 'modules/b', //第三方库
	        'jquery': 'libs/jquery-1.10.1',
	    }
	})

5.1 如何在浏览器端实现模块化

主要是通过 require.js 在浏览器端实现模块化

5.1.1 下载require.js 并引入

5.1.2 创建项目结构

|-js
|-libs
  |-require.js
|-modules
  |-alerter.js
  |-dataService.js
|-main.js
|-index.html

5.1.3 定义require.js的模块代码

dataService.js:没有依赖的模块

  //定义没有 依赖 的模块
  define(function () {
    const msg = 'dataService.js'

    // 将字符串转化为大写
    function getMsg() {
      return msg.toUpperCase()
    }

    // 返回大写的 HELLO WORLD
    // 向外暴露模块 return
    return { getMsg }
  })      

alert.js:定义有 依赖 的模块

  // 定义有依赖的模块:依赖项dataService jquery
  define(['dataService', 'jquery'], function (dataService, $) {
    const name = 'alert.js'
    function showMsg() {
      $('body').css('background', 'red')
      alert(dataService.getMsg() + ', ' + name)
    }

    return {showMsg}
  })

5.1.4 主程序文件: main.js

(function () {
//配置
requirejs.config({
  //基本路径(相对于根目录)
  baseUrl: "js/",
  //模块标识名与模块路径映射
  paths: {
    "dataService": "modules/dataService",
    "alert": "modules/alert",
    //引入 第三方模块 (前提是jquery是支持AMD的,如果第三方库不支持AMD,则不可以引入)
    "jquery": "libs/jquery-3.3.1.min",
  }
})

//引入使用模块
requirejs(['alert'], function (alert) {
  alert.showMsg()
})
})()

5.1.4 页面使用模块

配置data-main属性和src属性

<script data-main="js/main" src="js/libs/require.js"></script>

5.2 使用基于require.js的第三方框架(jQuery)

1、 将jquery的库文件导入到项目:

js/libs/jquery-1.10.1.js

2、 在main.js中配置jquery路径,设置映射

paths: {
          'jquery': 'libs/jquery-1.10.1'
      }

3、 在alerter.js中使用jquery

define(['dataService', 'jquery'], function (dataService, $) {
    var name = 'xfzhang'
    function showMsg() {
        $('body').css({background : 'red'})
        alert(name + ' '+dataService.getMsg())
    }
    return {showMsg}
})

5.3 使用第三方不基于require.js的框架(angular)

1、 将angular.js导入项目 js/libs/angular.js

2、 在main.js中配置

(function () {
  require.config({
    //基本路径
    baseUrl: "js/",
    //模块标识名与模块路径映射
    paths: {
      //第三方库
      'jquery' : './libs/jquery-1.10.1',
      'angular' : './libs/angular',
      //自定义模块
      "alerter": "./modules/alerter",
      "dataService": "./modules/dataService"
    },
    /*
     配置不兼容AMD的模块
     exports : 指定与相对应的模块名对应的模块对象
     */
    shim: {
      'angular' : {
        exports : 'angular'
      }
    }
  })
  //引入使用模块
  require( ['alerter', 'angular'], function(alerter, angular) {
    alerter.showMsg()
    console.log(angular);
  })
})()

6 CMD sea.js

基本语法:

// 定义暴露模块:
define(function(require, module, exports) {
    // 通过require()引入依赖模块        
    // 通过module/exports来暴露模块      
    exports.xxx = value
})

//  使用模块:	
seajs.use(['模块1', '模块2'])

6.1 如何在浏览器端实现模块化

CMD主要是通过 sea.js 在浏览器端实现模块化

6.1.1 下载Sea.js第三方包

seajs官方网站

6.1.2 创建项目结构

|-js
|-libs
  |-sea.js
|-modules
  |-module1.js
  |-module2.js
  |-module3.js
  |-module4.js
  |-main.js
|-index.html

6.1.3 定义 sea.js的模块代码

module1.js:向外暴露一个成员

define(function (require, exports, module) {
  // 内部变量数据
  var data = 'atguigu.com'
  // 内部函数
  function show() {
    console.log('module1 show() ' + data)
  }

  // 向外暴露一个成员
  exports.show = show
})

module2.js:默认向外暴露一个成员

define(function (require, exports, module) {
  module.exports = {
    msg: 'I Will Back'
  }
})

module3.js:

define(function (require, exports, module) {
  const API_KEY = 'abc123'
  exports.API_KEY = API_KEY
})

module4.js

define(function (require, exports, module) {
  // 同步引入依赖模块
  var module2 = require('./module2')

  function show() {
    console.log('module4 show() ' + module2.msg)
  }

  exports.show = show
    
  // 异步引入依赖模块
  require.async('./module3', function (m3) {
    console.log('异步引入依赖模块3  ' + m3.API_KEY)
  })
})

main.js : 主(入口)模块

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})
  1. index.html:
<!--
使用seajs:
1. 引入sea.js库
2. 如何定义导出模块 :
  define()
  exports
  module.exports
3. 如何依赖模块:
  require()
4. 如何使用模块:
  seajs.use()
-->
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main')
</script>
posted @ 2023-10-10 09:38  songxia777  阅读(9)  评论(0编辑  收藏  举报