js模块化

js模块化

Posted by yishuifengxiao on 2019-06-03

一 模块进化历史

1.1 全局 function 模式

  • module1.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //数据
    let data = 'atguigu.com'

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

    1
    2
    3
    4
    5
    let data2 = 'other data'

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

    1
    2
    3
    4
    5
    6
    7
    8
    <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 被污染了, 很容易引起命名冲突

1.2 namespace 模式

  • module1.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let myModule = {
    data: 'atguigu.com',
    foo() {
    console.log(`foo() ${this.data}`)
    },
    bar() {
    console.log(`bar() ${this.data}`)
    }
    }
  • module2.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let myModule2 = {
    data: 'atguigu.com2222',
    foo() {
    console.log(`foo() ${this.data}`)
    },
    bar() {
    console.log(`bar() ${this.data}`)
    }
    }
  • test2.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <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()

    </script>
  • 说明

    • namespace 模式: 简单对象封装
    • 作用: 减少了全局变量
    • 问题: 不安全

1.3 IIFE 模式

  • module3.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    (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)
  • test3.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script type="text/javascript" src="module3.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 模式: 匿名函数自调用(闭包)
    • IIFE : immediately-invoked function expression(立即调用函数表达式)
    • 作用: 数据是私有的, 外部只能通过暴露的方法操作
    • 问题: 如果当前这个模块依赖另一个模块怎么办?

1.4 IIFE 模式增强

  • 引入 jquery 到项目中
  • module4.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    (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)
  • test4.html

    1
    2
    3
    4
    5
    <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>
  • 说明
    • IIFE 模式增强 : 引入依赖
    • 这就是现代模块实现的基石
  1. 页面加载多个 js 的问题
  • 页面:
    1
    2
    3
    4
    <script type="text/javascript" src="module1.js"></script>
    <script type="text/javascript" src="module2.js"></script>
    <script type="text/javascript" src="module3.js"></script>
    <script type="text/javascript" src="module4.js"></script>
  • 说明
    • 一个页面需要引入多个 js 文件
    • 问题:
      • 请求过多
      • 依赖模糊
      • 难以维护
    • 这些问题可以通过现代模块化编码和项目构建来解决

二 commonJS 规范

2.1 基于服务器端(node)应用

2.1.1 说明

2.1.2 基本语法

暴露模块

1
2
module.exports = value
exports.xxx = value

具体参见 node.js 基础入门教程

引入模块

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

2.1.3 实现

服务器端实现

Node.js
http://nodejs.cn/

浏览器端实现

Browserify
http://browserify.org/
也称为 CommonJS 的浏览器端的打包工具

区别 Node 与 Browserify

Node.js 运行时动态加载模块(同步)

Browserify 是在转译(编译)时就会加载打包(合并)require 的模块

2.1.4 Node.js 模块化教程

  1. 下载安装 node.js
  2. 创建项目结构
1
2
3
4
5
6
7
8
9
10
|-modules-
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
"name": "commonJS-node",
"version": "1.0.0"
}
  1. 下载第三方模块
  • npm install uniq —save
  1. 模块化编码
  • module1.js
    1
    2
    3
    4
    5
    module.exports = {
    foo() {
    console.log('moudle1 foo()')
    }
    }
  • module2.js
    1
    2
    3
    module.exports = function () {
    console.log('module2()')
    }
  • module3.js

    1
    2
    3
    4
    5
    6
    7
    exports.foo = function () {
    console.log('module3 foo()')
    }

    exports.bar = function () {
    console.log('module3 bar()')
    }
  • app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
    1. 定义暴露模块:
    module.exports = value;
    exports.xxx = value;
    2. 引入模块:
    var module = require(模块名或模块路径);
    */
    "use strict";
    //引用模块
    let module1 = require('./modules/module1')
    let module2 = require('./modules/module2')
    let module3 = require('./modules/module3')

    let uniq = require('uniq')
    let fs = require('fs')

    //使用模块
    module1.foo()
    module2()
    module3.foo()
    module3.bar()

    console.log(uniq([1, 3, 1, 4, 3]))

    fs.readFile('app.js', function (error, data) {
    console.log(data.toString())
    })
  1. 通过 node 运行 app.js
  • 命令: node app.js

2.2 Browserify 模块化使用教程

  1. 创建项目结构
1
2
3
4
5
6
7
8
9
10
11
12
13
|-js
|-dist //打包生成文件的目录
|-src //源码所在的目录
|-module1.js
|-module2.js
|-module3.js
|-app.js //应用主源文件
|-index.html
|-package.json
{
"name": "browserify-test",
"version": "1.0.0"
}
  1. 下载 browserify
  • 全局: npm install browserify -g
  • 局部: npm install browserify —save-dev
  1. 定义模块代码
  • module1.js
    1
    2
    3
    4
    5
    module.exports = {
    foo() {
    console.log('moudle1 foo()')
    }
    }
  • module2.js
    1
    2
    3
    module.exports = function () {
    console.log('module2()')
    }
  • module3.js

    1
    2
    3
    4
    5
    6
    7
    exports.foo = function () {
    console.log('module3 foo()')
    }

    exports.bar = function () {
    console.log('module3 bar()')
    }
  • app.js (应用的主 js)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //引用模块
    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
  • 页面使用引入:
    1
    <script type="text/javascript" src="js/dist/bundle.js"></script>

三 AMD 规范

3.1 说明

3.2 基本语法

定义暴露模块

1
2
3
4
//定义没有依赖的模块
define(function(){
return 模块
})
1
2
3
4
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
return 模块
})

引入使用模块

1
2
3
require(['module1', 'module2'], function(m1, m2){
使用m1/m2
})

3.3 require.js 使用教程

3.3.1 下载 require.js, 并引入

3.3.2 创建项目结构

1
2
3
4
5
6
7
8
|-js
|-libs
|-require.js
|-modules
|-alerter.js
|-dataService.js
|-main.js
|-index.html

3.3.3 定义 require.js 的模块代码

  • dataService.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    define(function () {
    let msg = 'atguigu.com'

    function getMsg() {
    return msg.toUpperCase()
    }

    return {getMsg}
    })
  • alerter.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    define(['dataService', 'jquery'], function (dataService, $) {
    let name = 'Tom2'

    function showMsg() {
    $('body').css('background', 'gray')
    alert(dataService.getMsg() + ', ' + name)
    }

    return {showMsg}
    })

3.3.4 应用主(入口)js: main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function () {
//配置
require.config({
//基本路径
baseUrl: "js/",
//模块标识名与模块路径映射
paths: {
"alerter": "modules/alerter",
"dataService": "modules/dataService",
}
})
//引入使用模块
require( ['alerter'], function(alerter) {
alerter.showMsg()
})
})()

3.3.5 页面使用模块


3.3.6 使用第三方基于 require.js 的框架(jquery)

  • 将 jquery 的库文件导入到项目:
    • js/libs/jquery-1.10.1.js
  • 在 main.js 中配置 jquery 路径
    1
    2
    3
    paths: {
    'jquery': 'libs/jquery-1.10.1'
    }
  • 在 alerter.js 中使用 jquery
    1
    2
    3
    4
    5
    6
    7
    8
    define(['dataService', 'jquery'], function (dataService, $) {
    var name = 'xfzhang'
    function showMsg() {
    $('body').css({background : 'red'})
    alert(name + ' '+dataService.getMsg())
    }
    return {showMsg}
    })

3.3.7 使用第三方不基于 require.js 的框架

(angular/angular-messages)

  • 将 angular.js 和 angular-messages.js 导入项目
    • js/libs/angular.js
    • js/libs/angular-messages.js
  • 在 main.js 中配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    (function () {
    require.config({
    //基本路径
    baseUrl: "js/",
    //模块标识名与模块路径映射
    paths: {
    //第三方库
    'jquery' : 'libs/jquery-1.10.1',
    'angular' : 'libs/angular',
    'angular-messages' : 'libs/angular-messages',
    //自定义模块
    "alerter": "modules/alerter",
    "dataService": "modules/dataService"
    },
    /*
    配置不兼容AMD的模块
    exports : 指定导出的模块名
    deps : 指定所有依赖的模块的数组
    */
    shim: {
    'angular' : {
    exports : 'angular'
    },
    'angular-messages' : {
    exports : 'angular-messages',
    deps : ['angular']
    }
    }
    })
    //引入使用模块
    require( ['alerter', 'angular', 'angular-messages'], function(alerter, angular) {
    alerter.showMsg()
    angular.module('myApp', ['ngMessages'])
    angular.bootstrap(document,["myApp"])
    })
    })()
  • 页面:

    1
    2
    3
    4
    <form name="myForm">
    用户名: <input type="text" name="username" ng-model="username" ng-required="true">
    <div style="color: red;" ng-show="myForm.username.$dirty&&myForm.username.$invalid">用户名是必须的</div>
    </form>

四 ES6 模块化

说明

依赖模块需要编译打包处理

4.1 语法

导出模块

  1. 分多次导出模块的多个部分
1
2
3
export class Emp{  }
export function fun(){ }
export var person = {};
  1. 一次导出模块的多个部分
1
2
3
4
class Emp{  }
function fun(){ }
var person = {};
export {Emp, fun, person}
  1. default 导出(只能有一个)
1
export default {}

导入模块

1
2
3
4
import defaultModule from './myModule';  //导入默认的
import {Emp} from './myModule'; //导入指定的一个
import {Emp, person} from './myModule'; //导入指定的多个
import * as allFromModule from './myModule'; //导入所有

4.2 使用示例

ES6-Babel-Browserify 使用教程

  1. 定义 package.json 文件
1
2
3
4
{
"name" : "es6-babel-browserify",
"version" : "1.0.0"
}
  1. 安装 babel-cli, babel-preset-es2015 和 browserify
1
2
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
  1. 定义.babelrc 文件
1
2
3
{
"presets": ["es2015"]
}
  1. 编码
  • js/src/module1.js
    1
    2
    3
    4
    5
    6
    7
    export function foo() {
    console.log('module1 foo()');
    }
    export let bar = function () {
    console.log('module1 bar()');
    }
    export const DATA_ARR = [1, 3, 5, 1]
  • js/src/module2.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let data = 'module2 data'

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

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

    export {fun1, fun2}
  • js/src/module3.js

    1
    2
    3
    4
    5
    6
    export default {
    name: 'Tom',
    setName: function (name) {
    this.name = name
    }
    }
  • js/src/app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import {foo, bar} from './module1'
    import {DATA_ARR} from './module1'
    import {fun1, fun2} from './module2'
    import person from './module3'

    import $ from 'jquery'

    $('body').css('background', 'red')

    foo()
    bar()
    console.log(DATA_ARR);
    fun1()
    fun2()

    person.setName('JACK')
    console.log(person.name);
  1. 编译
  • 使用 Babel 将 ES6 编译为 ES5 代码(但包含 CommonJS 语法) : babel js/src -d js/lib
  • 使用 Browserify 编译 js : browserify js/lib/app.js -o js/lib/bundle.js
  1. 页面中引入测试
1
<script type="text/javascript" src="js/lib/bundle.js"></script>
  1. 引入第三方模块(jQuery)
    1). 下载 jQuery 模块:
    • npm install jquery@1 —save
      2). 在 app.js 中引入并使用
      1
      2
      import $ from 'jquery'
      $('body').css('background', 'red')