CommonJS

260 阅读4分钟

一、安装nodejs

官网地址:nodejs.org/zh-cn/

安装过程省略

1. 浏览器

浏览器运行的是html页面,并加载页面中通过script元素引入的js

2. nodejs

nodejs直接运行某个js文件,该文件被称之为入口文件

nodejs遵循EcmaScript标准,但由于脱离了浏览器环境,因此:

  1. 你可以在nodejs中使用EcmaScript标准的任何语法或api,例如:循环、判断、数组、对象等
  2. 你不能在nodejs中使用浏览器的 web api,例如:dom对象、window对象、document对象等

由于大部分开发者是从浏览器端开发转向nodejs开发的,为了降低开发者的学习成本,nodejs中提供了一些和浏览器web api同样的对象或函数,例如:console、setTimeout、setInterval等。注意:nodejs在实现这些api时,完全可以使用另一个名字,但是为了降低前端开发者的学习成本,于是把这些相同功能的api起了相同的名字。

二、CommonJS

在nodejs中,由于有且仅有一个入口文件(启动文件),而开发一个应用肯定会涉及到多个文件配合,因此,nodejs对模块化的需求比浏览器端要大的多

由于nodejs刚刚发布的时候,前端没有统一的、官方的模块化规范,因此,它选择使用社区提供的CommonJS作为模块化规范

在学习CommonJS之前,首先认识两个重要的概念:模块的导出模块的导入

1. 模块的导出

要理解模块的导出,首先要理解模块的含义

什么是模块?

模块就是一个JS文件,它实现了一部分功能,并隐藏自己的内部实现,同时提供了一些接口供其他模块使用

模块有两个核心要素:隐藏暴露

隐藏的,是自己内部的实现

暴露的,是希望外部使用的接口

任何一个正常的模块化标准,都应该默认隐藏模块中的所有实现,而通过一些语法或api调用来暴露接口

暴露接口的过程即模块的导出

2. 模块的导入

当需要使用一个模块时,使用的是该模块暴露的部分(导出的部分),隐藏的部分是永远无法使用的。

当通过某种语法或api去使用一个模块时,这个过程叫做模块的导入

3. CommonJS规范

CommonJS使用module.exports导出模块,require导入模块

具体规范如下:

  1. 如果一个JS文件中存在exportsrequire,该JS文件是一个模块
  2. 模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
  3. 如果一个模块需要暴露一些API提供给外部使用,需要通过exports导出,exports是一个空的对象,你可以为该对象添加任何需要导出的内容
  4. 如果一个模块需要导入其他模块,通过require实现,require是一个函数,传入模块的路径即可返回该模块导出的整个内容

4. nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

  2. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。

     (function(){
         //模块中的代码
     })()
    
  3. 为了保证顺利的导出模块内容,nodejs做了以下处理

    1. 在模块开始执行前,初始化一个值module.exports = {}
    2. module.exports即模块的导出值。注意:最终导出的是module.exports这个对象
    3. 为了方便开发者便捷的导出,nodejs在初始化完module.exports后,又声明了一个变量exports = module.exports
     (function(module){
         module.exports = {};
         var exports = module.exports;
         //模块中的代码
         return module.exports;
     })()
    
  4. 为了避免反复加载同一个模块,nodejs默认开启了模块缓存,如果加载的模块已经被加载过了,则会自动使用之前的导出结果

5. 注意细节

  • 一定要理解以下代码

一个模块初始的时候module.exportsexports指向的是同一个对象地址。并且最后导出module.exports所指向的对象

(function(module){
    module.exports = {};
    var exports = module.exports;
    //模块中的代码
    return module.exports;
})()
  • 当我们给exports赋新的对象时,是不会导出的

因为现在exports指向了另一个新的对象,但是最后导出的是module.exports

exports = {
    name: 'abc'
    sum: function() {}
}
  • 当我们给module.exports赋新的对象时,exports中也不会导出
exports.name = 'abc';
module.exports = {
    age: 18
}

此时module.exports指向了一个新的对象,已经不等于exports了,最后导出这个新的对象。