• Modules-nodejs


    Modules

    Node有一个简易的模块加载系统。在node中,文件和模块是一一对应的。下面示例是foo.js加载同一目录下的circle.js

    foo.js的内容:

    var circle = require('./circle.js');
    console.log( 'The area of a circle of radius 4 is '
               + circle.area(4));

    circle.js的内容:

    var PI = Math.PI;
    exports.area = function (r) {
        return PI * r * r;
    };
    exports.circumference = function (r) {
        return 2 * PI * r;
    };
    circle.js模块输出了area()circumference()两个函数。要输出某个对象,把它加到exports这个特殊对象下即可

    注意,exportsmodule.exports的一个引用,只是为了用起来方便。当你想输出的是例如构造函数这样的单个项目,那么需要使用module.exports

    // 正确输出构造函数
    module.exports = MyConstructor;

    模块内的本地变量是私有的。在这里例子中,PI这个变量就是circle.js私有的。

    模块系统的实现在require("module")中。

    循环

    当存在循环的require()调用时,一个模块可能在返回时并不会被执行。

    考虑这样一种情形:

    a.js:

    console.log('a starting');
    exports.done = false;
    var b = require('./b.js');
    console.log('in a, b.done = %j', b.done);
    exports.done = true;
    console.log('a done');

    b.js

    console.log('b starting');
    exports.done = false;
    var a = require('./a.js');
    console.log('in b, a.done = %j', a.done);
    exports.done = true;
    console.log('b done');

    main.js:

    console.log('main starting');
    var a = require('./a.js');
    var b = require('./b.js');
    console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

    首先main.js加载a.js,接着a.js又去加载b.js。这时,b.js会尝试去加载a.js。为了防止无限的循环,a.js会返回一个unfinished copyb.js。然后b.js就会停止加载,并将其exports对象返回给a.js模块。

    这样main.js就把这两个模块都加载完成了。这段程序的输出如下:

    $ node main.js
    main starting
    a starting
    b starting
    in b, a.done = false
    b done
    in a, b.done = true
    a done
    in main, a.done=true, b.done=true

    如果你的程序中有循环的模块依赖,请确保工作正常。

    核心模块

    Node中有一些模块是编译成二进制的。这些模块在本文档的其他地方有更详细的描述。

    核心模块定义在node源代码的lib/目录下。

    require()总是会优先加载核心模块。例如,require('http')总是返回编译好的HTTP模块,而不管是否有这个名字的文件。

    文件模块

    如果按文件名没有查找到,那么node会添加 .js.json后缀名,再尝试加载,如果还是没有找到,最后会加上.node的后缀名再次尝试加载。

    .js 会被解析为Javascript纯文本文件,.json 会被解析为JSON格式的纯文本文件. .node 则会被解析为编译后的插件模块,由dlopen进行加载。

    模块以'/'为前缀,则表示绝对路径。例如,require('/home/marco/foo.js') ,加载的是/home/marco/foo.js这个文件。

    模块以'./'为前缀,则路径是相对于调用require()的文件。 也就是说,circle.js必须和foo.js在同一目录下,require('./circle')才能找到。

    当没有以'/'或者'./'来指向一个文件时,这个模块要么是"核心模块",要么就是从node_modules文件夹加载的。

    如果指定的路径不存在,require()会抛出一个code属性为'MODULE_NOT_FOUND'的错误。

    node_modules文件夹中加载

    如果require()中的模块名不是一个本地模块,也没有以'/', '../', 或是 './'开头,那么node会从当前模块的父目录开始,尝试在它的/node_modules文件夹里加载相应模块。

    如果没有找到,那么就再向上移动到父目录,直到到达顶层目录位置。

    例如,如果位于'/home/ry/projects/foo.js'的文件调用了require('bar.js'),那么node查找的位置依次为

    • /home/ry/projects/node_modules/bar.js
    • /home/ry/node_modules/bar.js
    • /home/node_modules/bar.js
    • /node_modules/bar.js

    这就要求程序员应尽量把依赖放在就近的位置,以防崩溃。

    Folders as Modules

    可以把程序和库放到一个单独的文件夹里,并提供单一入口来指向它。有三种方法,使一个文件夹可以作为require()的参数来加载。

    首先是在文件夹的根目录创建一个叫做package.json的文件,它需要指定一个main模块。下面是一个package.json文件的示例。

    { "name" : "some-library",
      "main" : "./lib/some-library.js" }

    示例中这个文件,如果是放在./some-library目录下面,那么require('./some-library')就将会去加载./some-library/lib/some-library.js

    This is the extent of Node's awareness of package.json files.

    如果目录里没有package.json这个文件,那么node就会尝试去加载这个路径下的index.js或者index.node。例如,若上面例子中,没有package.json,那么require('./some-library')就将尝试加载下面的文件:

    • ./some-library/index.js
    • ./some-library/index.node

    Caching

    模块在第一次加载后会被缓存。这意味着(类似其他缓存)每次调用require('foo')的时候都会返回同一个对象,当然,必须是每次都解析到同一个文件。

    多次调用 require(foo) 未必会导致模块中的代码执行多次. 这是一个重要的功能. 借助这个功能, 可以返回部分完成的对象; 这样, 传递依赖也能被加载, 即使它们可能导致循环依赖

    如果你希望一个模块多次执行,那么就输出一个函数,然后调用这个函数。

    Module Caching Caveats

    模块的缓存是依赖于解析后的文件名。由于随着调用的位置不同,可能解析到不同的文件(比如需从node_modules文件夹加载的情况),所以,如果解析到其他文件时,就不能保证require('foo')总是会返回确切的同一对象。

    The module Object

    在每一个模块中,变量 module 是一个代表当前模块的对象的引用。 特别地,module.exports 可以通过全局模块对象 exports 获取到。 module 不是事实上的全局对象,而更像是每个模块内部的。

    module.exports

    module.exports 对象是通过模块系统产生的。有时候这是难以接受的,许多人想让他们的模块是某个类的实例。 因此,将要导出的对象赋值给 module.exports 。例如,假设我们有一个模块称之为 a.js

    // Do some work, and after some time emit
    // the 'ready' event from the module itself.
    setTimeout(function() {
      module.exports.emit('ready');
    }, 1000);

    那么,在另一个文件中我们可以这样写

    var a = require('./a');
    a.on('ready', function() {
      console.log('module a is ready');
    });
    注意: 给module.expors赋值必须立即生效, 不能在回调中执行, 这样不能工作的

    x.js:

    setTimeout(function() {
      module.exports = { a: "hello" };
    }, 0);

    y.js:

    var x = require('./x');
    console.log(x.a);
    module.require(id)
    • id {String}
    • Return: {Object} 已解析模块的 module.exports

    module.require 方法提供了一种像 require() 一样从最初的模块加载一个模块的方法。

    注意,为了这样做,你必须取得一个对 module 对象的引用。 require() 返回 module.exports,并且 module是一个典型的只能在特定模块作用域内有效的变量,如果要使用它,就必须明确的导出。

    module.id

    用于区别模块的标识符。通常是完全解析后的文件名。

    module.filename

    模块完全解析后的文件名。

    module.loaded
    • {Boolean}

    不论该模块是否加载完毕,或者正在加载的过程中。

    module.parent

    引入这个模块的模块。

    module.children

    这个模块引入的所有模块对象。

    总体来说...

    为了获取调用 require 加载的确切的文件名,使用 require.resolve() 函数。

    综上所述,下面用伪代码的高级算法形式表达了 require.resolve 是如何工作的:

    NODE_MODULES_PATHS(START)
    1. let PARTS = path split(START)
    2. let ROOT = index of first instance of "node_modules" in PARTS, or 0
    3. let I = count of PARTS - 1
    4. let DIRS = []
    5. while I > ROOT,
       a. if PARTS[I] = "node_modules" CONTINUE
       c. DIR = path join(PARTS[0 .. I] + "node_modules")
       b. DIRS = DIRS + DIR
       c. let I = I - 1
    6. return DIRS

    从全局文件夹加载

    如果 NODE_PATH 环境变量设置为一个以冒号分割的绝对路径的列表, 找不到模块时 node 将会从这些路径中搜索模块。 (注意:在 windows 操作系统上,NODE_PATH 是以分号间隔的)

    此外,node 将会搜索以下地址:

    • 1: $HOME/.node_modules
    • 2: $HOME/.node_libraries
    • 3: $PREFIX/lib/node

    $HOME 是用户的主目录,$PREFIX 是 node 里配置的 node_prefix

    这些大多是由于历史原因。强烈建议读者将所依赖的模块放到 node_modules 文件夹里。 这样加载的更快也更可靠。

    访问主模块

    当 Node 直接运行一个文件时,require.main 就被设置为它的 module 。 也就是说你可以判断一个文件是否是直接被运行的

    require.main === module
    对于一个 foo.js 文件,如果通过 node foo.js 运行是 true ,但是通过 require('./foo') 运行却是 false
    因为 module 提供了一个 filename 属性(通常等于 __filename), 所以当前程序的入口点可以通过require.main.filename 来获取。

    附录: 包管理技巧

    Node 的 require() 函数的语义被设计的足够通用化,以支持各种常规目录结构。 包管理程序如 dpkg,rpm 和 npm 将不用修改就能够从 Node 模块构建本地包。

    接下来我们将给你一个可行的目录结构建议:

    假设我们希望将一个包的指定版本放在 /usr/lib/node/<some-package>/<some-version> 目录中。

    包可以依赖于其他包。为了安装包 foo,可能需要安装包 bar 的一个指定版本。 包 bar 也可能有依赖关系,在某些情况下依赖关系可能发生冲突或者形成循环。

    因为 Node 会查找它所加载的模块的真实路径(也就是说会解析符号链接), 然后按照上文描述的方式在 node_modules 目录中寻找依赖关系,这种情形跟以下体系结构非常相像:

    • /usr/lib/node/foo/1.2.3/ - foo 包 1.2.3 版本的内容
    • /usr/lib/node/bar/4.3.2/ - foo 包所依赖的 bar 包的内容
    • /usr/lib/node/foo/1.2.3/node_modules/bar - 指向 /usr/lib/node/bar/4.3.2/ 的符号链接
    • /usr/lib/node/bar/4.3.2/node_modules/* - 指向 bar 包所依赖的包的符号链接

    因此即便存在循环依赖或依赖冲突,每个模块还是可以获得他所依赖的包的一个可用版本。

    当 foo 包中的代码调用 require('bar'),将获得符号链接 /usr/lib/node/foo/1.2.3/node_modules/bar 指向的版本。 然后,当 bar 包中的代码调用 require('queue'),将会获得符号链接/usr/lib/node/bar/4.3.2/node_modules/quux 指向的版本。

    此外,为了进一步优化模块搜索过程,不要将包直接放在 /usr/lib/node 目录中,而是将它们放在/usr/lib/node_modules/<name>/<version> 目录中。 这样在依赖的包找不到的情况下,就不会一直寻找/usr/node_modules 目录或 /node_modules 目录了。

    为了使模块在 node 的 REPL 中可用,你可能需要将 /usr/lib/node_modules 目录加入到 $NODE_PATH 环境变量中。 由于在 node_modules 目录中搜索模块使用的是相对路径,基于调用 require() 的文件所在真实路径,因此包本身可以放在任何位置。

  • 相关阅读:
    Windows Server 2008安装Memcached笔记
    解决powerDesinger12逆向工程报错:Unable to list the tables
    冒泡排序算法
    ASP.NET面试题(推荐_有答案)
    ASP.NET服务器控件分类简介
    将excel文件中的数据导入导出至SQL数据库中(Microsoft.Jet.OLEDB.4.0和Microsoft.ACE.OLEDB.12.0|office2003和office2007)
    关于sql access excel以及在web.config中数据库连接字符串的写法
    ODBC / OLEDB___DAO / RDO / ADO
    什么是CSV格式文档
    AppSettings和ConnectionStrings的区别
  • 原文地址:https://www.cnblogs.com/xiaopen/p/5304348.html
Copyright © 2020-2023  润新知