Node.js学习 - 模块系统

模块化概述

Javascript不是一种模块化编程语言,它不支持“类”(class),更遑论“模块”(module)了,随着前端发展对模块需求越来越大,模块也经历了从最初的简单模块写法到AMD和CMD规范的出现,再到ES6发布,目前已经可以很方便的在Javascript中使用“类”和“模块”了。

模块化规范
浏览器端规范

  • AMD(require.js遵循的AMD规范)
  • CMD(Sea.js遵循的CMD规范)
  • ES6

服务器端规范

  • CommonJS(Node.js是CommonJS的实现)
  • Node的应用是由模块组成的

CommonJS特点

  • 所有代码运行在当前模块作用域,不会污染全局作用域
  • 模块同步加载,根据代码执行顺序载入
  • 模块可多次加载,只会在第一次加载的时候运行一次,运行结果被缓存
  • 以后再次加载模块,会直接从缓存中读取结果
// export.js
module.exports = {
    name: 'taki',
    getName: function (name) {
        console.log(name)
    }
}

// require.js
let person = require('./export')
console.log(person)
console.log(person.name)
person.getName('taki_01')
> node require.js
{ name: 'taki', getName: [Function: getName] }
taki
taki_01

module对象详解

module对象属性

  • id:当前模块id
  • exports:表示当前模块暴露给外部的值
  • parent:是一个对象,表示调用当前模块的模块
  • children:是一个对象,表示当前模块调用的模块
  • filename:模块的绝对路径
  • paths: 从当前模块开始查找node_modules目录,然后依次进入到父目录,查找父目录下的node_modules目录;依次迭代,直到根目录下的node_modules目录
  • loaded:一个布尔值,表示当前模块是否被完全加载
// export.js
module.exports = {
    name: 'taki',
    getName: function (name) {
        console.log(name)
    }
}

console.log(module)
> node export.js
Module {
  id: '.',
  exports: { name: 'taki', getName: [Function: getName] },
  parent: null,
  filename:
   'E:\\Course\\SeniorFrontEnd\\draft\\nodejs\\1.2.1\\export.js',
  loaded: false,
  children: [],
  paths:
   [ 'E:\\Course\\SeniorFrontEnd\\draft\\nodejs\\1.2.1\\node_modules',
     'E:\\Course\\SeniorFrontEnd\\draft\\nodejs\\node_modules',
     'E:\\Course\\SeniorFrontEnd\\draft\\node_modules',
     'E:\\Course\\SeniorFrontEnd\\node_modules',
     'E:\\Course\\node_modules',
     'E:\\node_modules' ] }

模块分类

核心模块:Node.js提供的模块,例如http模块,fs模块等,称为核心模块。核心模块在node源代码编译过程终就编译成了二进制文件,在node进程启动的时候,部分核心模块就直接加载进内存中,因此这部分模块是不需要手动加载,而且在路径分析中优先判断,加载速度是最快的

文件模块: 用户自己编写的模块,称为文件模块。文件模块是需要按需加载的,速度较慢

module.exports与exports

module.exports和exports都是引用类型变量,指向同一个地址内存,在node中两者一开始都指向一个空对象
exports对象是通过形参的方式传入,直接赋值给形参的引用,但是并不能改变作用域外的值
module.exports是为了解决exports直接赋值的问题而产生的

开始 exports = module.exports 相当于二者都是针对同一个对象的引用,但如果执行了exports = {}之后,exports指向的地址就会发生改变,所以在使用exports暴露对象的时候不能直接对其赋值。而module.exports = {}不会有这个问题。

require方法详解

模块引入规则

通过exports或者module.exports抛出一个模块,通过require方法传入模块标识符,然后node根据一定的规则引入改模块,我们就可以使用模块中定义的方法和属性

加载步骤

路径分析、文件定位、编译执行

路径分析(模块标识符分析)

  • 核心模块
  • 以’.‘或者’…/'开始的相对路径文件模块
  • 以’/'开始的绝对路径模块
  • 非路径形式的模块

文件定位

  • 扩展名分析
  • 目标文件和包分析

模块加载过程

Module._load = function (request, parent, isMain) {
    // 计算绝对路径
    var filename = Module._resolveFilename(request, parent);

    // 第一步:如果有缓存,取出缓存
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
        return cachedModule.exports;
    }

    // 第二步:是否为内置模块
    if (NativeModule.exists(filename)) {
        return NativeModule.require(filename);
    }

    // 第三步:生成模块实例,存入缓存
    var module = new Module(filename, parent);
    Module._cache[filename] = module;

    // 第四步:加载模块
    try {
        module.load(filename);
        hadException = false;
    } finally {
        if (hadException) {
            delete Module._cache[filename];
        }
    }

    // 第五步:输出模块的exports属性
    return module.exports
}

路径查找过程

Module._findPath = function (request, paths) {
    // 列出所有可能的后缀名:.js,.json,.node
    var exts = Object.keys(Module._extensions);

    // 如果是绝对路径,就不再搜索
    if (request.charAt(0) === '/') {
        path = [''];
    }

    // 是否有后缀的目录斜杠
    var trailingSlash = (request.slice(-1) === '/');

    // 第一步:如果当前路径已在缓存中,就直接返回缓存
    var cacheKey = JSON.stringify({ request: request, paths: paths });
    if (Module._pathCache[cacheKey]) {
        return Module._pathCache[cacheKey];
    }

    // 第二步:依次遍历所有路径
    for (var i = 0, PL = paths.length; i < PL; i++) {
        var basePath = path.resolve(paths[i], request);
        var filename;

        // 第三步:是否存在该模块文件
        if (!trailingSlash) {
            filename = tryFile(basePath);

            // 第四步:该模块文件加上后缀名,是否存在
            if (!filename && !trailingSlash) {
                filename = tryExtensions(basePath, exts);
            }
        }

        // 第五步:目录中是否存在 package.json
        if (!filename) {
            filename = tryPackage(basePath, exts);
        }

        // 第六步:是否存在目录名 + index + 后缀名
        if (!filename) {
            filename = tryExtensions(path.resolve(basePath, 'index'), exts);
        }

        // 第七步:将找到的文件路径存入返回缓存,然后返回
        if(filename){
            Module._pathCache[cacheKey] = filename;
            return filename;
        }
    }

    // 第八步:没找到文件,返回false
    return false;
}

Extra

var a = require('.a.js')
var b = require('.a.js')

console.log(a === b)
// true

知识技能获取,感谢[网易云课堂 - 微专业 - 前端高级开发工程师]运营团队。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值