js模块化

537 阅读3分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

模块化的理解

什么是模块?将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起;块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。

一个模块的组成:数据是内部的属性;操作数据的行为是内部的函数。

编码时按照模块一个一个编码的,那么整个项目就是一个模块化的项目。

模块化的进化过程

全局 function 模式

module1.js

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

module2.js

let data2 = 'other data';
    
function foo() {  //这里与另一个模块中的函数冲突了
   console.log(`foo() ${data2}`)
}

index.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>
  • 编码:将不同的功能封装成不同的全局函数。
  • 问题:Global 被全局污染了,很容易引起命名冲突

namespace 模式

module1.js

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

module2.js

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

index.html

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

  myModule2.foo()
  myModule2.bar()

  //可以直接修改模块内部的数据
  myModule.data = 'other data' 
  myModule.foo()
  • 编码:简单对象封装
  • 作用:减少了全局变量
  • 问题:依旧可以修改模块内部代码,不安全

IIFE 模式

module1.js

(function (window) {
//数据
let data = 'atguigu.com'

//操作数据的函数
function foo() { //向外暴露的内部私有函数
  console.log(`foo() ${data}`)
}

function bar() {//向外暴露的内部私有函数
  console.log(`bar() ${data}`)
  otherFun() //内部调用
}

function otherFun() { //未暴露的内部私有函数
  console.log('otherFun()')
}

//暴露行为
window.myModule = {foo, bar}
})(window)

index.html

<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
  myModule.foo()
  myModule.bar()
  //myModule.otherFun()  //报错:myModule.otherFun is not a function
  console.log(myModule.data) //undefined 不能访问模块内部数据
  myModule.data = 'xxxx' //并不是修改的模块内部的data
  myModule.foo() //未受影响
</script>
  • 编码:匿名函数自调用(闭包)
  • IIFE : immediately-invoked function expression(立即调用函数表达式)
  • 作用:数据是私有的,外部只能通过暴露的方法操作
  • 问题:如果当前这个模块依赖另一个模块怎么办?

IIFE模式增强

module4.js

(function (window, $) {
  //数据
  let data = 'atguigu.com'

  //操作数据的函数
  function foo() { //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }

  function bar() {//用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }

  function otherFun() { //内部私有的函数
    console.log('otherFun()')
  }

  //暴露行为
  window.myModule = {foo, bar}
})(window, jQuery)
index.html

<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript">
  myModule.foo()
</script>
  • 引入依赖
  • 是现代模块实现的基石
  • 页面加载多个 js 的问题:一个页面引入多个 js 的时候,会出现请求过多、依赖模糊、难以维护的问题。

模块化规范

CommonJS

CommonJS 分为浏览器端和服务器端

基本语法

定义暴露模块:exports

exports.xxx = value
module.exports = value

引入模块:require

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

引入模块发生在什么时候?

服务器端:运行时, 动态同步引入

浏览器端:在运行前对模块进行编译/转译/打包的处理(已经将依赖的模块包含进来了),运行的是打包生成的 js,运行时不存在需要再从远程引入依赖模块。

AMD

AMD 只存在在浏览器端,需要依赖 require.js

定义暴露模块:define

define([依赖模块名], function(){return 模块对象})

引用模块:require

require(['模块1', '模块2', '模块3'], function(m1, m2){//使用模块对象})

配置项

require.config({
  //基本路径
  baseUrl : 'js/',
  //标识名称与路径的映射
  paths : {
    '模块1' : 'modules/模块1',
    '模块2' : 'modules/模块2',
    'angular' : 'libs/angular',
    'angular-messages' : 'libs/angular-messages'
  },
  //非AMD的模块
  shim : {
    'angular' : {
        exports : 'angular'
    },
    'angular-messages' : {
        exports : 'angular-messages',
        deps : ['angular']
    }
  }
})

CMD

CMD 只存在在浏览器端,需要依赖于 sea.js,目前暂无一个正式的官网。

定义暴露模块:define

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

引用模块:使用 seajs

seajs.use(['模块1', '模块2'])

ES6

ES6 内置了模块化的实现

定义暴露模块 : export

// 暴露一个对象
export default 对象

// 暴露多个
export var xxx = value1
export let yyy = value2

var xxx = value1
let yyy = value2
export {xxx, yyy}

引入使用模块 : import

// default 模块
import xxx  from '模块路径/模块名'

// 其他模块
import {xxx, yyy} from '模块路径/模块名'
import * as module1 from '模块路径/模块名'

浏览器不能直接识别 ES6 模块化的语法,因此需要使用 BabelES6 转换成 ES5,但由于转换完的文件中还使用了 CommonJS,因此需要使用 Browserify 将文件打包处理,转换成浏览器可以识别使用的文件。

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。