博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅谈node的模块机制的实现
阅读量:6893 次
发布时间:2019-06-27

本文共 4261 字,大约阅读时间需要 14 分钟。

背景

javascript最初主要是两个方面的作用。一方面是做表单的验证,另一个方面是做一些特效来提供友好的人机交互体验。经历了漫长的发展后,javascript天然缺乏像java的类文件,python的import机制,没有模块系统。想要用javascript开发大型项目,文件的组织就成为一个很大的问题。我们可能都写过如下的代码:

    
example// 往往这些js文件还有顺序关系复制代码

直到有了CommonJS规范,javascript大放光彩。node就是借鉴CommonJS的modules规范实现了一套易用的模块系统,接下来,我们就聊一聊node是怎么实现的。

node中模块的使用

在聊使用之前,我们先聊聊node中模块的使用方法。

require函数的使用

require函数用于在当前模块中加载和使用别的模块例如:

// 加载node自己的模块和三方模块const path = require('path')// 加载自己写的模块const foo = require('./foo')// 在node中支持.js,.json,.node为扩展名的文件。const mock = require('./goodList.json')const nodeDem = require('./nodeDem.node')复制代码

exports 和module.exports 的使用

实质上exports是module.exports的一个引用,至于为什么下面我们会说到,这里我们谈谈他的用法。 我们创建一个exports-demo.js的文件。打下如下代码:

exports.a = 'a'exports.name = function () {    console.log('my name is xiaopang')}复制代码

我们创建另外一个文件user.js。打下如下代码:

var ExportsDemo = require('./exports-demo.js')ExportsDemo.a // aExportsDemo.name() // my name is xiaopang复制代码

那上面的代码用module.exports怎么写呢?同样我们创建一个module-exports-demo.js文件。他的代码如下:

var f = {    a: 'a',    name: function () {        console.log('my name is xiaopang')    }}module.exports = f复制代码

在user.js中调用代码如下:

var ModuleExportsDemo = require('./module-exports-demo.js')ModuleExportsDemo.a // aModuleExportsDemo.name() // my name is xiaopang复制代码

那么问题来啦什么时候用exports,什么时候用module.exports呢?

如果你想让你的模块返回一个特殊的对象类型,比如构造函数,那么你得使用 module.exports ;如果你只想模块作为一个典型的模块实例(module instance),那么就用exports。这个仅仅是一些经验之谈。

node的模块实现即require函数的实现

前面我们聊了聊node中模块的使用,并留下了一个坑那就是exports和module.exports到底是什么关系,现在我们就慢慢填坑。在填坑之前,我们先聊一聊require都干了啥?聊完这个之后呢,我们一步一步实现就好啦。大体分为一下四步:

  • 路径分析
    • 首先require会根据我们传的url,来找到我们文件的路径。
  • 模块加载
    • 根据之前找到的路径来读取文件,然后讲字符串函数,转换为函数并执行。
  • 解析文件名
    • node中支持以.js、.node、.json为后缀的文件,有时候我们可能并不会输入文件的后缀名例如 var foo = require('foo')
  • 缓存
    • 在使用的时候你会发现,同一个模块你引用多次,node并不会执行多次。 说完上面这些,我们先搭个大体的架子,随后我们一步步实现。
// require 函数function $require (path) {    Module._load(path)}// 熟悉的module函数function Module (id) {    this.id = id;    this.exports = {}}Module_load = function (path) {    let filename = Module._resolveFilename(path)    var module = new Module(path)    resolveAndLoadFile(module)}// 加载模块Module._resolveFilename = function (name) {}// 不同的文件扩展名对应不同的解析函数,这里不对.node文件进行处理Module._extension = {    '.js': function () {},    '.json': funciton () {}}// 缓存Module._cache = {}复制代码

模块的加载

Module._resolveFilename = function (path) {        // 如果输入的URL带有.js或者.json后缀的话,我们直接解析就是        if ((/.js$|.json$/).test(path)) {            return path.resolve(__dirname, path)        } esle {            // 如果没有后缀,我们就需要给他拼接上            let exts = Object.keys(Module._extension);            let realPath;            for (let i = 0; i < exts.length; i++) {                let temp = path.resolve(__dirname, path + exts[i])                // 判断文件是否真的存在                try {                    fs.accessSync(temp)                    realPath = temp                } cache (e) {                    throw new Error(e)                }            }            if (!realPath) {                throw new Error('module is not exists')            }        }    }复制代码

解析文件名

function resolveAndLoadFile (module) {    const ext = path.extname(module.id)    Module._extension[ext](module)}对于json处理很简单,只需要把json字符串转换为json就可以啦Module._extension = {    '.json': function (module) {        Module.exports = JSON.parse(fs.readFileSync(module.id), 'utf8')    }}对于js的处理,比较复杂,主要是要将字符串函数转为函数即: "console.log('hello,world')"  进行转化。在这里我们不深究,实际上node给我们提供了一个vm的沙箱,它会帮我们处理这件事情。Module._extension = {    '.js': function (module) {        const content = fs.readFileSync(module.id, 'utf8')        const funStr = Module.wrap(content)        const fn = vm.runInThisContext(funStr)        fn.call(module.exports, module.exports, $require, module)    }}Module.wrapper = ["(function (exports, require, module, __filename, __dirname) {","})"]Module.wrap = function (script) {    return Module.wrapper[0] + script + Module.wrapper[1]}复制代码

缓存

Module._load = function (path) {    // 解析出绝对路径    let filename = Module._resolveFilename(path)     // 解析出绝对路径后,匹配对应的文件名后缀,对应不同的解析方法     let module = new Module(filename)     // 缓存处理    if (!Module._cache[filename]) {        Module._cache[filename] = module    } else {        return Module._cache[filename].exports    }    // 尝试解析模块    resolveAndLoadFile(module)}复制代码

好啦,以上就是全部啦,欢迎大家拍砖。

转载地址:http://kphbl.baihongyu.com/

你可能感兴趣的文章
《游戏大师Chris Crawford谈互动叙事》一9.6 互动小说机理剖析
查看>>
聚米微商好做吗 、为什么这么多人都在做聚米婧氏品牌呢
查看>>
互联网企业安全高级指南3.11 业务持续性管理
查看>>
Vertica的这些事&lt;八&gt;—— vertica加密数据
查看>>
python 自定义 包 模块 打包 安装
查看>>
ubuntu桌面的标题栏和启动栏消失问题[亲测可用]
查看>>
Cloud technology in today's job market
查看>>
3月31日云栖精选夜读:数据科学咨询:想要转型毫无头绪?看了本文你不慌
查看>>
程序猿日记S01E03
查看>>
如何解决域名解析不生效问题?
查看>>
Android开发者指南(9) —— ProGuard
查看>>
MySQL · 答疑解惑 · 物理备份死锁分析
查看>>
字符串指针修改问题
查看>>
JavaScript权威设计--跨域,XMLHttpRequest(简要学习笔记十九)
查看>>
跨入流式计算时代,用不着洪荒之力——在阿里云容器服务上一键部署JStorm
查看>>
通过JCONSOLE监控TOMCAT的JVM使用情况
查看>>
jquery editable plugin--点击编辑文字插件
查看>>
[Java] TreeMap、HashMap、LindedHashMap的区别
查看>>
javascript 常用自定义方法
查看>>
MariaDB · 新特性 · 窗口函数
查看>>