• CommonJS


    CommonJS 规范与实现

    正如当年为了统一 JavaScript 语言标准,人们制定了 ECMAScript 规范一样,如今为了统一 JavaScript 在浏览器之外的实现,CommonJS 诞生了。CommonJS 试图定义一套普通应用程序使用的 API,从而填补 JavaScript 标准库过于简单的不足。CommonJS 的终极目标是制定一个像 C++ 标准库一样的规范,使得基于 CommonJS API 的应用程序可以在不同的环境下运行,就像用 C++ 编写的应用程序可以使用不同的编译器和运行时函数库一样。为了保持中立,CommonJS 不参与标准库实现,其实现交给像 Node.js 之类的项目来完成。

    CommonJS 规范包括了模块(modules)、包(packages)、系统(system)、二进制(binary)、控制台(console)、编码(encodings)、文件系统(filesystems)、套接字(sockets)、单元测试(unit testing)等部分。

    Node.js 是目前 CommonJS 规范最热门的一个实现,它基于 CommonJS 的 Modules/1.0 规范实现了 Node.js 的模块,同时随着 CommonJS 规范的更新,Node.js 也在不断跟进。

    模块(Module)和包(Package)是 Node.js 最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正式为了实现这种方式而诞生的。在浏览器 JavaScript 中,脚本模块的拆分和组合通常使用 HTML 的 script 标签来实现。Node.js 提供了 require 函数来调用其他模块,而且模块都是基于文件的,机制十分简单。

    CommonJS 规范的实现

    Node.js 的模块和包机制的实现参照了 CommonJS 的标准,但并未完全遵循。不过两者的区别不大,一般来说你大可不必担心,只有当你试图制作一个除了支持 Node.js 之外还要支持其他平台的模块或包的时候才需要仔细研究。通常,两者没有直接冲突的地方。

    我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分。

    CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。

    为了方便,Node.js 为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令:

    let exports = module.exports
    

    注意,不能直接将 exports 变量指向一个值,因为这样等于切断了 exports 与 module.exports 的联系。

    如果你觉得,exports 与 module.exports 之间的区别很难分清,一个简单的处理办法,就是放弃使用 exports,只使用 module.exports。

    什么是模块

    模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。

    创建及加载模块

    创建模块

    在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
    让我以一个例子来了解模块。创建一个 module.js 文件,内容是:

    //module.js
    let name;
    exports.setName = (uname) => {
        name = uname
    }
    exports.getName = () => {
        return name
    }

    在同一目录下创建 getModule.js,内容是:

    //getModule.js
    let module = require('./module')
    module.setName('zhenjianyu')
    let name = module.getName()
    console.log(name)

    module.js 通过 exports 对象把 setName 和 sayHello 作为模块的访问接口,在 getModule.js 中通过 require('./module') 加载这个模块,然后就可以直接访问 module.js 中 exports 对象的成员函数了。

    加载模块

    在 Node.js 中,我们可以直接通过 require 获取核心模块,例如 require('fs') 。核心模块拥有最高的加载优先级,换言之如果有模块与其命名冲突,Node.js 总是会加载核心模块。

    如果有模块与核心模块命名冲突,Node.js 为什么可以选择加载核心模块呢?require 的实现机制是怎样的呢?

    1、按路径加载模块

    如果 require 参数以 “/“ 开头,那么就以绝对路径的方式查找模块名称,例如 require('/home/zhenjianyu/module') 将会按照 优先级依次尝试加载 /home/zhenjianyu/module.js/home/zhenjianyuyu/module.json 和 /home/zhenjianyu/module.node

    如果 require 参数 “./“ 或 “../“ 开头,那么则以相对路径的方式查找模块,这种方式在应用中是最常见的。例如前面的例子中我们用了 require('./hello')来加载同一文件夹下的 hello.js。

    2、通过查找 node_modules 目录加载模块

    如果 require 参数不以 “/“ , “./“ 或 “../“ 开头,而该模块又不是核心模块,那么就要通过查找 node_modules 加载模块了。我们使用 npm 获取的包通常就是以这种方式加载的。
    在 node_modules 目录的外面一层,外面可以直接使用 require('express') 来代替 require('./node_modules/express')。这是 Node.js 模块加载的一个重要特征:通过查找 node_modules 目录来加载模块。
    我们不仅要在 project 目录下的 app.js 中使用 require('express'),而且可能要在 controllers 子目录下的 index_controller.js 中也使用 require('express'),这时就需要向父目录上溯一层才能找到 node_modules 中的 express 了。

    3、加载缓存

    Node.js 通过文件名缓存所有加载过的文件模块,所以以后再访问到时就不会重新加载了。注意,Node.js 是根据实际文件名缓存的,而不是 require() 提供的参数缓存的,也就是说即使你分别通过 require('express') 和 require('./node_modules/express')加载两次,也不会重复加载,因为尽管两次参数不同,解析到的文件却是同一个。

    单次加载

    上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为 require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个。我们在 getmodule.js 的基础上稍作修改:

    let module1 = require('./module')
    module1.setName('zhenjianyu')
    let module2 = require('./module')
    module2.setName('liyanan') 
    let name = module1.getName() 
    console.log(name)

    运行后发现输出结果是 liyanan,这是因为变量  module1和 module2 指向的是同一个实例,因此 module1.setName 的结果被 module2.setName 覆盖,最终输出结果是由后者决定的。

    CommonJS 模块的特点如下:

    1. 所有代码都运行在模块作用域,不会污染全局作用域。
    2. 独立性是模块的重要特点就,模块内部最好不与程序的其他部分直接交互。
    3. 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
    4. 模块加载的顺序,按照其在代码中出现的顺序。
    以自己现在的努力程度,还没有资格和别人拼天赋
  • 相关阅读:
    FLEX图像工具类-图像变形
    flex中list或Combox中的子项上移下移操作
    flex中socket的使用
    Flex2款简单FLV播放器很经典
    Flex中Css参考示例
    Flex中CursorManager的应用
    关于FLEX的安全沙箱问题
    Flex实现多文件上传之一:前台部分
    Flex与JS通信
    metasploit 常用指令
  • 原文地址:https://www.cnblogs.com/zhenjianyu/p/14689000.html
Copyright © 2020-2023  润新知