JS模块化

JS模块化

背景

js本身简单的页面设计:页面动画 + 表单提交
并无模块化 or 命名空间的概念

JS模块化需求日益增长

幼年期:无模块化

  1. 开始需要在页面中增加一些不同的js:动画、表单、格式化
  2. 多种js文件被分在不同的文件中
  3. 不同的文件又被同一个模板饮用
// 当时是被认可的:文件分离是最基础的



问题:

  • 污染全局作用域 => 不利于大型项目的开发以及多人团队的共建

成长期:模块化的雏形 - IIFE(立即函数、语法侧的优化)

作用域的把控

例子:

// 定义一个全局变量
let count = 0
// 代码块1
const increase = () => ++count
// 代码块2
const reset = () => {
    count = 0
}

increase()
reset()

利用函数的块级作用域

(() => {
    let conut = 0
    // ...
})

仅定义了一个函数,并没有执行,如果立即执行

(() => {
    let conut = 0
    // ...
})()

初步实现了一个最简单的模块(通过立即执行函数)

尝试去定一个最简单的模块

const iifeModule = (() => {
    let count = 0
    // 代码块1
    const increase = () => ++count
// 代码块2
    const reset = () => {
        count = 0
    }
    return {
        // 代码块1
         increase: () => ++count,
        // 代码块2
        reset: () => {
            count = 0
        }
    }
})()

iifeModule.increase()
iifeModule.reset()

追问:如果有额外依赖,如何优化iife? - 参数的调用

优化1:依赖其他模块的IIFE
const iifeModule = ((dependencyModule1, dependencyModule2) => {
    let count = 0
    return {
        // 代码块1
         increase: () => ++count,
        // 代码块2
        reset: () => {
            count = 0
        }
    }
})(dependencyModule1, dependencyModule2)

iifeModule.increase()
iifeModule.reset()

问:了解jquery的依赖处理以及模块加载方案吗?/ 了解传统IIFE是如何解决多方依赖的问题吗?
答:IIFE + 传参调配

实际上,jquery 等框架其实应用了 revealing module(揭示模式) 的写法

const iifeModule = ((dependencyModule1, dependencyModule2) => {
    let count = 0
    // 代码块1
    const increase = () => ++count
    // 代码块2
    const reset = () => {
        count = 0
    }
    // 仅暴露接口,不暴露方法
    return {
        increase,
        reset
    }
   
})(dependencyModule1, dependencyModule2)

iifeModule.increase()
iifeModule.reset()

成熟期

CJS - CommonJS

由node制定的一套方案
特征:
  • 通过 module + exports 去对外暴露借口
  • 通过 require 来调用其他模块

模块组织方式
main.js 文件中

// 引用
const dependencyModule1 = require(dependencyModule1)
const dependencyModule2 = require(dependencyModule2)

// 处理
let count = 0
// 代码块1
const increase = () => ++count
// 代码块2
const reset = () => {
    count = 0
}

// 做一些跟引入依赖相关事宜……

// 暴露接口
exports.increase = increase
exports.reset = reset

module.exports = {
    increase,
    reset
}

模块使用方式

const { increase, reset } = require(main.js)

increase()
reset()

可能被问到的东西

实际执行处理
转化成立即执行函数
CJS 和 IIFE 的结合

// 需要穿参
(function () {
    const dependencyModule1 = require(dependencyModule1)
    const dependencyModule2 = require(dependencyModule2)
    
    // 业务逻辑……
}).call(thisValue, exports, require, module)

CJS 优点:
CommonJS 率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题

CJS 缺点:
主要针对了服务端的解决方案,都是同步引入。对于异步拉取依赖整合没有那么友好。

新的问题 —— 异步依赖

AMD规范

通过异步加载 + 允许定制回调函数
经典实现框架:require.js

新增定义方式:

// 通过deifine来定义一个模块,然后require进行加载
/**
 * id: 模块名
 * [depends]:依赖模块
 * callback:工厂方法
 */
define(id, [depends], callback)
require([module], callback)

模块定义方式:

define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1,dependencyModule1) => {
    // 处理
    let count = 0
    // 代码块1
    const increase = () => ++count
    // 代码块2
    const reset = () => {
        count = 0
    }
    
    return {
        increase, reset
    }
})

引入模块:

require(['amdModule'], amdModule => {
    amdModule.increase()
})

问:如果在amdModule中想兼容已有代码,怎么办?

define('amdModule', [], require => {
    // 引用
    const dependencyModule1 = require(dependencyModule1)
    const dependencyModule2 = require(dependencyModule2)

// 处理
    let count = 0
// 代码块1
    const increase = () => ++count
// 代码块2
    const reset = () => {
        count = 0
    }

// 做一些跟引入依赖相关事宜……

// 暴露接口
    exports.increase = increase
    exports.reset = reset

    module.exports = {
        increase,
        reset
    }
})

问:AMD中使用revealing

define('amdModule', [], (require, exports, module) => {
    // 引用
    const dependencyModule1 = require(dependencyModule1)
    const dependencyModule2 = require(dependencyModule2)

    // 处理
    let count = 0
    // 代码块1
    const increase = () => ++count
    // 代码块2
    const reset = () => {
        count = 0
    }

    // 做一些跟引入依赖相关事宜……

    // 暴露接口
    exports.increase = increase
    exports.reset = reset
})

define(require => {
    const otherModule = require('amdModule')
    otherModule.increase()
    otherModule.reset()
})

问:兼容 AMD & CommonJS?答:UMD
UMD的出现

(define('amdModule', [], (require, exports, module) => {
    // 引用
    const dependencyModule1 = require(dependencyModule1)
    const dependencyModule2 = require(dependencyModule2)

    // 处理
    let count = 0
    // 代码块1
    const increase = () => ++count
    // 代码块2
    const reset = () => {
        count = 0
    }

    // 做一些跟引入依赖相关事宜……

    // 暴露接口
    exports.increase = increase
    exports.reset = reset
}))(
    //  目标是一次性区分 CommonJS or AMD
    typeof module === "object"
    && module.exports
    && typeof define !== "function"
     ? // 是 CommonJS
    factory => module.exports = factory(require, exports, module) 
        : // 是AMD
    define
)
  • 优点:适合在浏览器中加载异步模块,可以并行加载多个模块
  • 缺点:会有引入成本,不能按需加载

CMD规范

按需加载
主要应用的框架 sea.js
define('module', [], (require, exports, module) => {
    let $ = require('jquery');
    // jquery 相关的代码
    
    let dependencyModule1 = require('dependencyModule1')
    //  dependencyModule1 相关逻辑
})
  • 优点:按需加载,依赖就近
  • 缺点:依赖打包,加载逻辑存在每个模块中,扩大模块体积

ES6模块化

走向新时代

新增定义:
引入关键字 —— import
导出关键字 —— export

模块引入、导出和定义
esModule.js

// 引入
import dependencyModule1 from 'dependencyModule1'
import dependencyModule2 from 'dependencyModule2'

// 实现
let count = 0
export const increase = () => ++count
const reset = () => {
    count = 0
}

// 导出
export default {
    reset
}

模板引入的地方:

node中:

import { reset } from 'esModule.js'
reset()

import esModule from 'esModule.js'
esModule.reset()
  • 优点(重要性):通过一种最统一的形态整合js模块化
  • 缺点(局限性):本质上还是运行了依赖分析

解决模块化的新思路 —— 前端工程化

背景

根本问题 — 运行时进行依赖分析

前端的模块化处理方案依赖于运行时分析

解决方案:线下执行
grunt、gulp、webpack


    
    
    
define('a', () => {
    let b = require('b');
    let c = require('c');
    
    exports.run = () => {
    //   run
    }
})

工程化实现

step1: 扫描依赖关系表:

{
    a: ['b', 'c']
    b: ['d']
}

step2: 重新生成依赖数据模板


    
    
    

step3: 执行工具,采用模块化方案解决模块化处理依赖

define('a', ['b', 'c'], () => {
    // 执行代码
    exports.run = () => {
        //   run
    }
})
优点:
  1. 构建时生成配置,运行时执行
  2. 最终转化执行处理依赖
  3. 可以扩展

完全体:webpack为核心的工程化 + MVVM框架组件化 + 设计模式

总结:

  1. 无模块化 -> IIFE => 从无到有的过程
  2. IIFE -> CommonJS => 从代码层到框架层
  3. CommonJS -> AMD => 解决了异步问题
  4. AMD -> CMD => 解决了按需加载的问题

你可能感兴趣的:(javascript)