commonjs

commonjs

CommonJS 规范简介

既然 JavaScript 需要模块化来解决上面的问题,那就需要制定模块化的规范,CommonJS 就是解决上面问题的模块化规范,规范就是规范,没有为什么,就和编程语言的语法一样。我们一起来看看。

CommonJS 概述

Node.js 应用由模块组成,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

// a.js
var name = 'morrain'
var age = 18

上面代码中,a.js 是 Node.js 应用中的一个模块,里面申明的变量 name 和 age 是 a.js 私有的,其它文件都访问不到。

CommonJS 规范还规定,每个模块内部有两个变量可以使用,require 和 module

require 用来加载某个模块

module 代表当前模块,是一个对象,保存了当前模块的信息。exports 是 module 上的一个属性,保存了当前模块要导出的接口或者变量,使用 require 加载的某个模块获取到的值就是那个模块使用 exports 导出的值

// a.js
var name = 'morrain'
var age = 18
module.exports.name = name
module.exports.getAge = function(){
   return age
}

//b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge())// 18

CommonJS 之 exports

为了方便,Node.js 在实现 CommonJS 规范时,为每个模块提供一个 exports的私有变量,指向 module.exports。你可以理解为 Node.js 在每个模块开始的地方,添加了如下这行代码。

var exports = module.exports

于是上面的代码也可以这样写:

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
    return age
}

有一点要尤其注意,exports是模块内的私有局部变量,它只是指向了 module.exports,所以直接对 exports 赋值是无效的,这样只是让 exports 不再指向 module.exports了而已 如下所示:

// a.js
var name = 'morrain'
var age = 18
exports = name

如果一个模块的对外接口,就是一个单一的值,可以使用 module.exports 导出

// a.js
var name = 'morrain'
var age = 18
module.exports = name

CommonJS 之 require

require 命令的基本功能是,读入并执行一个 js 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。

第一次加载某个模块时,Node.js 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的 module.exports 属性返回了。

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
    return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'

如上所示,第二次 require 模块A时,并没有重新加载并执行模块A。而是直接返回了第一次 require 时的结果,也就是模块A的module.exports

还一点需要注意,CommonJS 模块的加载机制是,require 的是被导出的值的拷贝。也就是说,一旦导出一个值,模块内部的变化就影响不到这个值 。

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
    age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18

CommonJS 实现

了解 CommonJS 的规范后,不难发现我们在写符合 CommonJS 规范的模块时,无外乎就是使用了 requireexportsmodule 三个东西,然后一个 js 文件就是一个模块。如下所示:

// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function () {
  return age
}
// b.js
var a = require('a.js')
console.log('a.name=', a.name)
console.log('a.age=', a.getAge())

var name = 'lilei'
var age = 15
exports.name = name
exports.getAge = function () {
  return age
}
// index.js
var b = require('b.js')
console.log('b.name=',b.name)

如果我们向一个立即执行函数提供 requireexportsmodule 三个参数,模块代码放在这个立即执行函数里面。模块的导出值放在 module.exports 中,这样就实现了模块的加载。如下所示:

(function(module, exports, require) {
    // b.js
    var a = require("a.js")
    console.log('a.name=', a.name)
    console.log('a.age=', a.getAge())

    var name = 'lilei'
    var age = 15
    exports.name = name
    exports.getAge = function () {
      return age
    }

})(module, module.exports, require)

知道这个原理后,就很容易把符合 CommonJS 模块规范的项目代码,转化为浏览器支持的代码。很多工具都是这么实现的,从入口模块开始,把所有依赖的模块都放到各自的函数中,把所有模块打包成一个能在浏览器中运行的 js 文件。譬如 Browserify 、webpack 等等。

我们以 webpack 为例,看看如何实现对 CommonJS 规范的支持。我们使用 webpack 构建时,把各个模块的文件内容按照如下格式打包到一个 js 文件中,因为它是一个立即执行的匿名函数,所以可以在浏览器直接运行。

// bundle.js
(function (modules) {
    // 模块管理的实现
})({
  'a.js': function (module, exports, require) {
    // a.js 文件内容
  },
  'b.js': function (module, exports, require) {
    // b.js 文件内容
  },
  'index.js': function (module, exports, require) {
    // index.js 文件内容
  }
})

接下来,我们需要按照 CommonJS 的规范,去实现模块管理的内容。首先我们知道,CommonJS 规范有说明,加载过的模块会被缓存,所以需要一个对象来缓存已经加载过的模块,然后需要一个 require 函数来加载模块,在加载时要生成一个 module,并且 module 上 要有一个 exports 属性,用来接收模块导出的内容。

// bundle.js
(function (modules) {
  // 模块管理的实现
  var installedModules = {}
  /**
   * 加载模块的业务逻辑实现
   * @param {String} moduleName 要加载的模块名
   */
  var require = function (moduleName) {

    // 如果已经加载过,就直接返回
    if (installedModules[moduleName]) return installedModules[moduleName].exports

    // 如果没有加载,就生成一个 module,并放到 installedModules
    var module = installedModules[moduleName] = {
      moduleName: moduleName,
      exports: {}
    }

    // 执行要加载的模块
    modules[moduleName].call(modules.exports, module, module.exports, require)

    return module.exports
  }

  return require('index.js')
})({
  'a.js': function (module, exports, require) {
    // a.js 文件内容
  },
  'b.js': function (module, exports, require) {
    // b.js 文件内容
  },
  'index.js': function (module, exports, require) {
    // index.js 文件内容
  }
})

可以看到, CommonJS 核心的规范,上面的实现中都满足了。非常简单,没想像的那么难。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值