js 模块

254 阅读5分钟
  • 模块,又被称为元件/组件;模块化:把代码进行合理地分割成模块
  • 好的模块,是高度自包含,具有独特的功能,允许它们在需要的时候被添加或移除,而且不会破坏整个系统

使用原因

有利于扩展和相互依赖的代码库

  • 可维护性(maintainability)
  • 命名空间(namespacing)
  • 可重用性(reusability)

合并模块到程序中

模块模式(Module Pattern)

注:javascript 中,函数是创建范围的唯一方法

  • 例1.匿名闭包(Anonymous Closure)
(function () {
    var myGrade = [89, 91, 79, 65, 88]
    var average = function () {
        var total = myGrade.reduce((accumulator, item) => accumulator + item, 0)
        return `Your average grade is ${total/myGrade.length} .`
    }
    var failing = function () {
        var failGrades = myGrade.filter(item => item < 80)
        return `You failed ${failGrades.length} times.`
    }
    console.log(average())
    console.log(failing())
}())
  • 例2.全局导入(global import)

类似匿名,但我们可以传一个全局变量作为参数

(function (globalVariable) {
    var _private = function () {
        console.log('this is private!')
    }
    
    // 遍历数组
    globalVariable.each = function (collection, iterator) {
        if (Array.isArray(collection)) {
            for (var i=0; i<collection.length; i++) {
                iterator(collection[i], i, collection)
            }
        } else {
            for (var key in collection) {
                iterator(collection[key], key, collection)
            }
        }
    }
    
    // 过滤
    globalVariable.filter = function (collection, test) {
        var filterResult = []
        globalVariable.each(collection, function (item) {
            if (test(item)) {
                filterResult.push(item)
            }
        })
        return filterResult
    }
    
    // 映射
    globalVariable.map = function (collection, iterator) {
        var mapResult = []
        globalVariable.each(collection, function (value){
            mapResult.push(iterator(value))
        })
        return mapResult
    }
    
    // 汇总
    globalVariable.reduce = function (collection, iterator, accumulator) {
        var startingValueMissing = accumulator === undefined
        globalVariable.each(collection, function (item) {
            if (startingValueMissing) {
                accumulator = item
                startingValueMissing = false
            } else {
                accumulator = iterator(accumulator, item)
            }
        })
        return accumulator
    }
}(globalVariable))
  • 例3.对象接口(Object interface)

通过接口公开函数,同时将模块的实现隐藏在 function() 块中

var gradeCaculate = (function () {
    // 私有变量
    var _grade = [89, 100, 82, 68, 93] 
    
    // 暴露函数
    return {
        average: function () {
            var total = _grade.reduce(function (accumulator, item) {
               return accumulator + item 
            }, 0)
            return `You average grade is ${total / _grade.length}`
        },
        failing: function () {
            var failingGrades = _grade.filter(function (item) {
                return item < 70
            })
            return `You failed ${failingGrades.length} items`
        }
    }
}())
  • 例4.揭示模块模式(Revealing module pattern)

特点:确保所有方法和变量在公开之前保持私有

    var gradeCaculate = (function () {
        var _grade = [80, 100, 92, 68, 79]
        var _average = function () {
            var total = _grade.reduce(function (accumulator, item) {
                return accumulator + item
            }, 0)
            return `Your average grade is ${total / _grade.length}`
        }
        var _failing = function () {
            var failingGrades = _grade.filter(function (item) {
                return item < 70
            })
            return `You failed ${failingGrades.length} items`
        }
        
        // 暴露相关方法
        return {
            average: _average,
            failing: _failing
        }
    }())
    

Commonjs and AMD

上述案例确实能起到作用,但是有它们的缺点:

  • 需要确保正确的依赖顺序(script tag)
  • 它们仍然可能导致命名冲突

解决思想:设计一种方法访问模块的接口而无需通过全局的变量

CommonJs

  • 一个志愿工作组设计和实现 javascript API ,用于申明模块。
  • CommonJs 模块实质上是一个可重用的 javascript,它导出一个特定的对象,使其可供其程序中需要的其他模块使用(即用于导入其他模块)
基本用法
  • javascript 文件将模块存储在自己独特的模块上下文中
  • 使用 module.exports 对象公开模块
  • 使用 require 引入模块到需要的文件中
// module.js
function myModule() {
    this.hello = function () {
        return 'hello'
    }
    this.goodbye = function () {
        return 'goodbye'
    }
}
module.exports = myModule

// require
var myModule = require('module.js')
var myModuleInstance = new myModule()
myModuleInstance.hello()
myModuleInstance.goodbye()

相比之前描述的方式,这种方法有两个明显的优点:

  • 避免全局命名空间污染
  • 使我们的依赖更加精确

注:CommonJs 采用服务器优先加载;而且是同步加载模块(适用服务端渲染,案例:nodejs)

AMD(Asynchronous Module Definition)

异步加载模块

  • 使用 AMD
    • 需要使用 define 关键字定义模块
    • define 函数使用每个模块的依赖项的数组作为第一个参数
    • 依赖以非阻塞的方式在后台加载
    • 加载完,调用定义在 define 的函数(即第二个参数f)
      • 在回调中使用已加载的模块(如:myModule, otherModule)
// 定义模块。myModule
define([], function () {
    return {
        hello: function () {
            return 'hello world'
        }
    }
})

// 在模块中加载其他模块
define(['myModule', 'otherModule'], function (myModule, otherModule) {
    myModule.hello()
})

与 CommonJs 不同,AMD 采用浏览器优先和异步方式来完成工作;而且 AMD 定义的模块类型可以是对象、字符串、构造函数、JSON和其它类型,而 CommonJs 的只能是对象。

但是,AMD 不适用 io 、文件系统和面向服务器的功能,这些只能通过 CommonJs 获得。

UMD(universal module definition)

  • 同时支持 AMD 和 CommonJs 的功能
    • UMD 本质上创建了一种方法来使用两者中的任何一种,同时还支持全局变量定义
  • 能够在客户端和服务端起作用
// 兼容 AMD 、CommonJs、全局变量
// UMD example:https://github.com/umdjs/umd

(function (root, factory) {
    // root -> this
    if (type define === 'function' && define.amd) {
        // AMD. define 的 amd 属性,区别于自定义的 define 函数
        define(['myModule', 'otherModule'], factory(myModule, otherModule))
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        // CommonJs
        module.exports = factory(require('myModule'), require('otherModule'))
    } else {
        // 全局变量
        root.returnExports = factory(root.myModule, root.otherModule)
    }
}(this, function (myModule, otherModule) {
    // 模块的功能。依赖的模块:myModule、otherModule
    const _pravite = () => {
        console.log('I am pravite')
    }
    const sayHello = () => {
        console.log('hello')
    }
    const sayGoodbye = () => {
        console.log('goodbye')
    }
    return {
        sayHello,
        sayGoodbye
    }
}))

NativeJs

  • 上述讨论中,已经创建使用模块模式、CommonJs 和 AMD 来模拟模块系统的方法
  • 但是,上面的模块不是 javascript 原生的
  • ECMAScript 6 (ES6) 已经有内置模块

ES6 模块的优势

相比于 CommonJs 或 AMD

  • 紧凑和声明性语法
  • 异步加载
  • 更好地支持循环依赖
与 CommonJs 的区别
  • ES6 模块的导入是导出的实时只读视图
  • CommonJs 的导入是导出的副本

CommonJs 例子

// js/counter.js
var counter = 1 
function increment () {
    counter ++
}
function decrement () {
    counter --
}
module.exports = {
    counter: counter,
    increment: increment,
    decrement: decrement
}

// src/main.js
var counter = require('../../js/counter.js')
counter.increment()
console.log(counter.counter) // 1

// 这个例子产生了两个模块副本,exports 时一个,require 时一个
// 在 main.js 的副本,和原始模块的副本断开连接。执行 increment 方法增加的原始模块(counter.js)的 counter 值,因此执行完输出的 counter 依然为 1

// 如果想输出的 counter 增加 1 ,处理如下:
// src/main.js
counter.counter++
console.log(counter.counter) // 2

ES6 例子

// js/counter.js
// 注:导出的时候需要申明
export let counter = 1
export function increment () {
    counter++
}
export function decrement () {
    counter--
}

// src/main.js
import * as counter from '../../counter'
console.log(counter.counter) // 1
counter.increment()
console.log(counter.counter) // 2
// 实时更新导入模块的值

参考资料