zoukankan      html  css  js  c++  java
  • Nodejs的模块实现

    在Node中引入模块,需要经历如下3个步骤:(1)路径分析(2)文件定位(3)编译执行

    Node中模块分为两类:

    一是Node提供的模块——核心模块。这部分在Node源代码的编译过程中,编译进了二进制文件。在Node进程启动时,部分核心模块就直接加载进内存中,所以这部分核心模块引入时,省略掉文件定位和编译执行并且在路径分析中优先判断,加载速度是是最快的。

    二是用户编写的模块——文件模块。在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。

    1、优先从缓存加载

    Node对引入过的模块都会进行缓存,以减少二次引入时的开销。不同的是浏览器仅仅缓存文件,而Node缓存的是编译和执行之后的对象。核心模块的缓存检查先于文件模块的缓存检查。

    2、模块路径模块路径是Node在定位文件模块的具体文件时指定的查找策略,具体表现为一个路径组成的数组。

    //module_path.js
    console.log(module.paths);

    windows下执行:module_path.js文件

    C:UsersXXX>node "d:Program Files
    odejsmodule_path.js"
    [ 'd:\Program Files\nodejs\node_modules',
      'd:\Program Files\node_modules',
      'd:\node_modules' ]

    可以看出模块路径的生成规则:

    当前文件目录下的node_modules目录;父目录下的node_modules目录;延路径向上逐级递归,知道根目录下的node_modules目录。

    3、文件定位

    文件扩展名分析

    require()在分析标识符过程中,如果标识符中不包含文件扩展名,Node会按.js.json.node的次序补足扩展名,依次尝试。

    在尝试过程中调用fs模块同步阻塞式地判断文件是否存在。因为Node是单线程的,如果是.node和.json文件,在传递给require()的标识符中带上扩展名,会加快一点速度。另外一个诀窍是同步配合缓存,可以大幅度缓解Node单线程中阻塞式调用的缺陷。

    4、模块编译

    每个文件模块都是一个对象,它的定义如下:

    function Module(id, parent){
        this.id = id;
        this.exports = {};
        this.parent = parent;
        if (parent && parent.children){
            parent.children.push(this);
        }
    
        this.filename = null;
        this.loaded = false;
        this.children = [];
    }

    定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入编译。不同的文件扩展名,其载入方法也有所不同,如下:

    Node会调用不同的读取方式,如.json文件的调用:

    //Native extension for .json
    Module._extension['.json'] = function(module, filename){
        var content = NativeModule.require('fs'),readFileSync(filename, 'utf8');
        try{
            module.exports = JSON.parse(stripBOM(content));
        }catch(err){
            err.message = filename + ':' + err.message;
            throw err;
        }
    };    
    //Module._extension会被赋值给require()的extension属性。
    在确定文件的扩展名之后,Node将调用具体的编译方式来将文件执行后返回给调用者。
    每一个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上,以提高二次引入的性能。
    (1)JavaScript模块的编译
    每个模块文件中存在着require、exports、module这3个变量,但是他们在模块文件中并没有定义,在Node的API文档中每个模块中还有__filename、__dirname这两个变量。
    事实上,在编译过程中,Node对获取的JavaScript文件内容进行了头尾包装。
    
    
    (function(exports, require, module, __filename, __dirname){//头部
        var math = require('math');
        exports.area = function(redius){
            return Math.PI * redius * redius;
        };
    });//尾部

    这样每个模块文件之间都进行了作用域隔离。包装之后的代码会通过vm原生模块的runInThisContext()方法执行(类似eval,只是具有明确上下文,不污染全局),返回一个具体的function对象。最后将当前模块的exports属性, require方法, module(模块对象自身), __filename, __dirname作为参数传递给这个function()执行。

    执行之后,模块的exports属性被返回给了调用方。exports属性上的任何方法和属性都可以被外部调用到,但是模块中的其余变量或属性则不可直接被调用。




  • 相关阅读:
    jquery height
    正则表达式的一点奇怪
    this和call
    ajax views
    史上变态的模块
    在php中有什么用
    localhost访问不了
    $.extend abc
    $.extend
    和人沟通的一个要点
  • 原文地址:https://www.cnblogs.com/tianxintian22/p/5085325.html
Copyright © 2011-2022 走看看