export等等那些故事(一)CommonJS规范

简介

因为今天要梳理的export我实在是区分不清楚,把它们的关系弄的很乱,今天就彻底梳理一下吧。

规范

想理清楚他们之间的关系,要先从规范开始看起。

CommonJS规范

1. 概述:
  • 我们熟知的Node是由模块组成的,Node采用CommonJS模块规范。即每一个文件都是一个模块,有自己的作用域。自己文件里面定义的变量、函数、类都是私有的,其他文件不可见。
  • 想在多个文件分享变量,必须定义为global对象的属性。
  • CommonJS规范规定,每个模块内部,module变量代表当前的模块。这个变量是一个对象,他的exports属性(module.exports)是对外的接口。如果在一个文件中想使用另一个模块中的变量,即在该文件中使用require方法。(加载某个模块,其实就是加载该模块的module.exports属性)
// name.js 文件

const name = "Jack";
const sayName = function (value) {
  return `hello ${name}`
}

module.exports.name = name
module.exports.sayName = sayName
// index.js 文件
const name = require("./name.js")

console.log(name.name)
console.log(name.sayName("Jone"))
  • 这种方法不会污染全局作用域,所有代码都运行在模块内部。模块可以多次加载,但是只会在第一次加载时候运行一次,然后运行结果就被缓存了,以后再加载的时候,就会直接从缓存中读取。如果想再次运行模块,那么需要将缓存清除。
2. module对象

Node内部提供一个Module构建函数,所有模块都是Module的实例。

function Module(id, parent) {
  this.id = id
  this.exports = {}
  this.parent = parent
}

每个模块内部,都有一个module对象,代表当前模块。
  • module.id 是模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径
  • module.loaded 返回一个布尔值 表示模块是否已经加载完成
  • module.parent 返回一个对象,表示调用改模块的模块
  • module.children 返回一个数组 表示该模块要用到的其他模块
  • module.exports 表示模块对外输出的值
console.log(module)
可以试一试
3. module.exports属性

表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量

4. exports 变量

Node为每个模块提供一个exports变量,指向module.exports这等同在每个模块的头部,有var exports = module.exports命令

console.log(exports === module.exports) // true

对外输出模块接口时,可以向exports对象添加方法

exports.sayName = function (name) {
  return `hello ${name}`
}

不能直接将exports变量指向一个值,这样就切断了exports与module.exports的联系。(也就是指向另一个值,然后exports就不再指向module.exports

exports = "change"
console.log(exports === module.exports) // false

所以这种写法是无效的。
···
name.js
exports.sayName = function(name) {
return "hello " + name
}
module.exports = "haha"
···

index.js
const name = require("./name")
console.log(name)

name.js中的sayName是无法对外输出的,因为module.exports被重新赋值了。如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。

5. require命令

用于加载模块文件(用于读入并执行一个Javascript文件),然后返回该模块的exports独享。如果没有发现指定模块,会报错。

name.js
exports.sayName = function(name) {
  return "hello " + name
}
exports.name = "testing"
const name = require("./name")

console.log(name) // { sayName: [Function], name: 'testing' }

如果输出的是一个函数,就不能定义在exports对象上面,而要定义在module.exports变量上面。

module.exports = function (name) {
  console.log("hello " + name)
}
require('./name')('haha') // hello haha

require命令调用自身,等于是执行module.exports,因此会输出hello haha.(调用自己,顺便传了个参数,前一段时间做express的时候就把app传递给router,js了 ~)

6. 加载规则

require命令用于加载文件,后缀名默认为.js
也就是在加载js文件的时候加不加后缀名都可以!根据参数的不同格式,require命令去不同路径寻找模块文件。

  • / 加载的是一个位于绝对路径的模块文件
  • ‘./’ 加载的是一个位于相对路径 跟当前执行脚本的位置相比的模块文件
  • 如果参数字符串不以上面两种开头目标是加载的是一个默认提供的核心模块(Node的系统安装目录中)或者一个位于各级node_modules 目录的已安装的模块(全局或者局部安装的) 可以使得不同的模块可以将所依赖的模块本地化。
  • 如果不以第一第二种开头,但是是一个文件路径
require('element-ui/css/index.css')

就是找到element-ui 模块,然后找到这个模块下的css路径下的index.css文件。

  • 如果指定的模块文件没有发现,Node会尝试为文件名添加.js .json .node后,再去搜索 分别是javascript脚本文件解析 json格式的文本文件解析 .node会以编译后的二进制文件解析。
  • 如果想得到require命令加载的确切文件名,使用require.resolve()方法
const name = require.resolve("./name")

console.log(name) // D:\zamall\vueblog\server\name.js
7. 目录的加载规则

为目录设置一个入口文件,让require方法可以通过这个入口文件,加载这个目录

在目录中设置package.json 文件 将入口文件写入main字段
  • require发现参数字符串指向一个目录之后,会自动查看该目录的package.json 文件,然后加载main字段执行的入口文件。如果package.json 文件没有main字段 或者没有package.json 文件 ,则会加载该目录下的index.js 或者 index.node 文件
8.模块的缓存

上面讲过,Node在加载某个模块的时候,Node会缓存该模块。以后再次加载该模块,就直接从缓存中取出该模块的module.exports属性。

name.js
index.js
require('./name.js')
require('./name.js').name = "hello"
console.log(require('./name.js').name) // hello

连续三次调用require命令。第二次加载的时候,为输出对象添加一个name属性,第三次加载的时候,这个name属性依然存在,require命令并没有重新加载模块文件,而是输出了缓存。
所有缓存的模块保存在require.cache之中,如果想删除模块的缓存。

// 删除指定模块的缓存 -moduleName 为要删除模块缓存的模块名称
delete require.cache[moduleName]

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})
// tips:缓存是根据 绝对路径 识别模块的,所以相同的模块名,他们的路径不同,`require`命令还是会重新加载该模块
9.模块的循环加载
name1.js

exports.name = 'name11'
console.log('a.js ', require("./name2").name)
exports.name = 'name12'
name2.js

exports.name = 'name21'
console.log('a.js ', require("./name1").name)
exports.name = 'name22'
main.js

console.log('main.js ', require('./name1').name)
console.log('main.js ', require('./name2').name)
结果
node main
name2.js  name11
name1.js  name22
main.js  name12
main.js  name22

如果在main.js中再次打印一份到控制台
输出的结果还是上面main.js相同的两份。因为第一次加载就缓存起来了。所以再次调用,会直接取出缓存中的内容

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容