看完我的笔记不懂也会懂----javascript模块化

JavaScript模块化

模块化引子

  1. 什么是模块,什么是模块化

    1. 模块其实就是一个个有特定功能的js文件
    2. 模块化就是将一个复杂的js程序依据一定的规范分割成数个模块(js文件)(数个模块组合在一起就是一整个功能齐全程序)
  2. 为什么要模块化
    数据更安全,只向外暴露接口(黑盒)

  3. 模块化的好处

    1. 当项目代码量大时,便于管理
    2. 有效的减轻全局NameSpace的污染(命名冲突)
    3. 模块功能明确,可以按需部署、加载
    4. 更高的复用性与可维护性
  4. 模块化缺点

    1. 页面引入加载script,这就导致了相比原来需要发的网络请求增加
    2. 模块彼此之间相互依赖,容易导致依赖模糊
    3. 模块彼此依赖导致耦合度增加, 变得更加难以维护

模块化的历史进化

  1. 全局Function模式

    //全局函数模式: 将不同功能封装成不同的全局函数
    
    /* 
        1.  数据直接暴露 
        2.  容易造成全局的命名空间污染
     */
    let str = '全局函数模式'
    
    function foo(){
        console.log('foo()' + str)
    }
    
    function bar(){
        console.log('bar()' + str)
    }
    
    foo()
    bar()
    
  2. nameSpace模式

    //namespace模式:   使用对象对函数(方法、API)进行简单的封装
    
    /*作用是:   
            保障了数据的安全性(相对全局中不容易被修改)
            降低了全局命名空间的污染程度
    */
    
    let manufacture = {
        str: '飞机制造成功',
        madePlane(){
            console.log(this.str)
        }
    }
    
    manufacture.madePlane()
    
  3. IIFE(匿名函数自调用)

    //IIFE: immeiately-invoked function expression
    
    //匿名函数自调用(闭包)
    
    (function foo(){
        let str = 'closure !!'
        function foo(){
            console.log(`str==>${str}`)
        }
        
        //window.foo = foo    //向外暴露但是又容易造成命名空间的污染
    
        //改进
        window.module = {
            foo: foo
        }
    
        //进一步改进    es6对象属性简写
    })()
    
  4. IIFE模式增强 (这就是JS模块化的原理)

        /* 
        IIFE模式增强:   引入依赖
        这就是现在模块实现的基石
    */
    
    (function(w,$){
        let str = 'IIFE模式增强'
        function foo(){
            console.log(str)
            //用于测试将网页变背景色
        $('body').css(
            {'background-color': 'red'}
            )
        }
        
        //向外暴露
        w.module = foo
    })(window,jQuery)
    
  5. Load Script模式

    <script src="./script1.js"></script>
    <script src="./script2.js"></script>
    <script src="./script3.js"></script>
    

    一个页面中需要引入多个js文件造成的问题:

    1. 请求过多
    2. 依赖模糊
    3. 难以维护

模块化规范----commonJS

commonJS是Node.js服务器端使用的JS模块化规范

commonJS的特性:

  • 其中的每一个js文件都可当做一个模块
  • 在服务器端,模块的加载是在运行时同步加载的
  • 在浏览器端,由于浏览器不认识require()的语法,所以模块需要提前编译打包处理

commonJS语法之暴露模块

exports.key = value
module.exports = value
/* 
    注意: 
        module.exports的方式暴露模块,只能够暴露最后一次module.exports出的对象,因为后面暴露的对象会覆盖前面的
*/

commonJS向外暴露的都是exports这个对象,为什么这么说呢?

学过node.js的同学应该都知道exports默认指向的是一个object对象

module.exports = value相当于改变exports的指向,暴露的仍然是exports

exports.key就更不用说了,只是单纯的向exports中添加属性然后暴露出去

commonJS语法之引入模块

// 当引入的是第三方模块, 直接写模块名
const $ = require('Jquery')

// 当引入的是自定义模块时, 需要书写完整的路径名
const HeaderComponent = require('./desktop/HeaderComponent.vue')

node端使用commonJS示例

分别定义三个module

// 暴露模块
module.exports = {
    name: '我是module1',
    printInfo () {
        console.log(this.name)
    }
}
// 暴露模块
module.exports = function () {
    console.log('我是模块module2')
}
// 引入uniq模块用于测试
const uniq = require('uniq');

// 暴露模块
exports.sayHello = function () {
    console.log('hello')
}

exports.sayName = function () {
    console.log('Fitz')
}


exports.testUniq = function () {
    arr = [1,1,3,4,4,5,3,5,6,4,5,4,4,5,8]
    console.log(uniq(arr))
}

然后在主文件中引入所有需要用的模块

// 引入所有模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')

console.log(module1.name)
module1.printInfo()

module2()

module3.sayName()
module3.sayHello()
module3.testUniq()

browser端使用commonJS示例

需要用到的编译工具: brorserify

npm i -D browserify@14.5.0

分别定义三个module

// 暴露模块
module.exports = {
    name: '我是module1',
    printInfo () {
        console.log(this.name)
    }
}
// 暴露模块
module.exports = function () {
    console.log('我是模块module2')
}
// 暴露模块
exports.sayHello = function () {
    console.log('hello')
}

exports.sayName = function () {
    console.log('Fitz')
}

然后在主文件中引入所有需要用的模块

// 引入所有模块
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')

console.log(module1.name)
module1.printInfo()

module2()

module3.sayName()
module3.sayHello()

在html页面引用主文件,也就是在浏览器端使用commonJS,需要先使用browserify来进行编译

browserify 主模块路径 -o 编译输出路径

模块化规范----AMD

AMD(Asynchronous Module Definition)规范是专门用于浏览器端的模块加载,它的加载过程是异步的,这样就有效避免了阻塞浏览器进行其他工作

AMD模块化规范依赖的是RequireJS这个工具

npm i -D require@2.3.6

AMD语法之暴露模块

// 暴露没有依赖的模块
define(function () {
    // do something

    return 模块
})

// 暴露有依赖的模块
define(
    ['依赖1','这里名字任意去但是要与主模块配置相同'],
    function (m1, m2) {
        // do something

        return 模块
    }
)

AMD语法之引入模块

// 配置模块依赖  这是必须的
requirejs.config({
    baseUrl: '基础路径',
    paths: {
        '依赖1': '路径',  // 注意路径最后不需要加文件后缀名
        '这里名字任意去但是要与主模块配置相同': '路径'
    }
})

requirejs(
    ['依赖'],
    function (m1) {
        // do something
    }
)

对于像Jquery这些支持requireJS的库的模块引入

requirejs.config({
    baseUrl: '基础路径',
    paths: {
        jquery: '路径'  // key必须是小写的jquery
    }
    /* 
        原因是,jquery在源码最后显示指定了requirejs模块名
        define('jquery', [], function(){return JQuery})

        define的第一个参数用于显示指定requireJS模块名
        指定后在config中必须使用这个名字,不能自定义
    */
})

requirejs(
    ['jquery'],
    function ($) {
        // do something
    }
)

对于不支持requireJS规范的库,如angular

requirejs.config({
    baseUrl: '基础路径',
    paths: {
        angular: '库的路径'
    },
    shim: {
        angular: {
            exports: 'angular' // 这里才是依赖的名字
        }
    }
    
})

requirejs(
    ['angular'],
    function (angular) {
        // do something
    }
)

requireJS在浏览器端使用

<script data-main="主模块路径" src="requireJS库所在路径"></script>

模块化规范----CMD

CMD规范是阿里巴巴公司内部的一种JS模块化规范,现已经出售转让(官网已无法访问)。而且在日常开发中使用极少,在这仅作为介绍使用

CMD规范同样用于浏览器端,模块的加载也是异步的,在模块使用的时候才会加载执行

CMD语法之暴露模块

// 定义没有依赖的模块
define(function (require, exports, module) {
    exports.xxx = yyy
    module.exports = {}
})

// 定义有依赖的模块
define(function (require, exports, module) {
    // 同步引入依赖
    const necessary1 = require('模块')
    // 异步引入依赖
    require.async('模块', function (m) {
        // do something
    })
    exports.xxx = yyy
    module.exports = {}
})

CMD语法之引入模块

define(function (require, exports, module) {
    const m1 = require('模块1')
    const m2 = require('模块2')
    // do something
})

CMD规范使用示例

定义两个模块

// 使用CMD规范, 创建没有依赖的模块
define(function(require, exports, module){
    exports.sayName = function () {
        console.log('Fitz')
    }
})
// 这个模块依赖于module1.js

// 使用CMD规范, 创建有依赖的模块
define(function(require, exports, module) {
    const m1 = require('./module1.js')
    m1.sayName()
    function sayHello () {
        console.log('hello')
    }
    module.exports = {
        sayHello
    }
})

在主模块中引入其他模块

// IIFE是非必须的,使用只是能确保内部数据的安全
(function () {
    define(function (require) {
        const m2 = require('./modules/module2.js')
        m2.sayHello()
    })
})()

浏览器端引入sea.js并使用定义好的模块

<body>
    <script src="./libs/sea.js"></script>
    <script>
        seajs.use('./index.js') // 使用模块
    </script>
</body>

模块化规范----ES6规范

ES6规范是现在模块化中运用最多、最广泛的一种规范,其语法较为简单,需要我们重点掌握

ES6模块化同样需要对代码进行编译打包处理,运用到的工具有Babel和Browserify

npm i -D babel-preset-es2015@6.24.1
npm i -D browserify@14.5.0 babel

需要为babel添加转换语法相关的配置文件.babelrc

{
    "presets": ["es2015"]
}

ES6语法之暴露模块

// 常规暴露之 分别暴露
export var a = 123
export const b = {age: 21}
export function () {
    // do something
}

// 常规暴露之 统一暴露
function func1 () {}
function func2 () {}
function func3 () {}
export {
    func1,
    func2,
    func3
}


// 默认暴露: 本质上就是常规暴露, 只不过暴露的是一个变量名叫default的接口对象
export default a = 123
export default function () {}
export default {}

ES6语法之引入模块

// 引入第三方模块
import identitify from '包名'

// 引入自定义模块
import moduleName from '模块路径'

ES6引入模块还有一种通用的方法,能够适用默认暴露、统一暴露、分别暴露

// 通用方法引入模块
import * as 自定义模块名 from '模块路径'    // 一定要取别名

console.log(自定义模块名)
/* 
    是一个对象,包含模块内所有向外暴露的接口
    其中默认暴露default会作为这个对象的一个属性

    [module] {
        接口1: 接口内容,
        接口2: 接口内容,
        default: 默认暴露的内容
    }
*/

ES6规范使用示例

创建三个模块分别运用三种暴露方法

// 暴露模块之    分别暴露

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

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

export function baz () {
    console.log('baz()')
}

export const name = 'module1.js'
// 暴露模块之   统一暴露

function func1 () {
    console.log('func1()')
}

function func2 () {
    console.log('func2()')
}

function func3 () {
    console.log('func3()')
}

export {
    func1,
    func2,
    func3
}
// 暴露模块之   默认暴露

// export default只能使用一次, 因为其本质就是向外暴露一个变量名为default的接口
export default function () {
    console.log('使用默认暴露')
}

// 后续可以进行使用常规暴露
export function foo () {
    console.log('module3 foo()')
}

在主模块中引入所有的模块

// 使用import语法引入模块
import {foo} from './modules/module1'
import {func1} from './modules/module2'

// 可以为模块内的对象改名
import {baz as baby} from './modules/module1'

// 引入默认暴露的模块
import md3 from './modules/module3'

// 如果不同模块中有同名对象,必须为对象改名,否则后面引入的同名对象会覆盖前面引入的
import {foo as md3Foo} from './modules/module3'

foo()       // 'foo()'
func1()     // 'func1()'
baby()      // 'baz()'
md3()       // '使用默认暴露'
md3Foo()    // 'module3 foo()'

编译相关语法:

# 使用Babel将ES6编译转换成ES5的代码
babel 源文件 -d 目标路径

# 由于转换后会包含CommonJS的语法,所以需要用browserify进一步转换才能被浏览器使用
browserify 源文件 -o 目标路径
原文地址:https://www.cnblogs.com/fitzlovecode/p/learn_jsModular.html