继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

浅谈NodeJs的模块机制

www说
关注TA
已关注
手记 439
粉丝 83
获赞 493

历史

我们都知道,js在刚被创建的时候,只是为了在网页上写一些小脚本而已,比如网页特效,表单验证等等,创立者也许没觉悟到以后的js会发展到如此规模。这是web1.0时代。
在web 2.0时代,各种前端库,前端框架被开发出来,jquery,angular就是代表。此时js的功能也就从写写小特效啥的跃迁到了应用开发的级别上。可以说,js经历了工具类库,组件库,前端框架,前端应用的变迁。
于是在越来越广泛的应用中,js暴露了它先天就缺乏的一项功能:模块。在其它高级语言,都有模块的定义,java有类,python有import机制,ruby有require,php有require和include。而js此时还单纯的用script标签引入,用命名空间来约束代码,杂乱无章,于是,commonjs规范便应运而出。

commonJS的模块规范

commonjs规范的出发点就是让js在任何地方都能运行。它弥补了js此时的几点缺陷:

  • 没有模块概念

  • 标准库较少

  • 没有标准接口

  • 没有包管理系统

当然,如今commonjs规范已经解决了大部分问题,而且涵盖了模块,二进制,Buffer,字符集编码,I/O流,单元测试,web服务网关接口,包管理,等。

commonjs对模块的定义很简单,包含了模块定义,模块引用,模块标识3个部分。

  1. 模块引用

var xx = require('xxx')

关键字require来接受模块标识,引入这个模块的API到上下文中。

  1. 模块定义

一个例子来解释:

//add.jsfunction add(a,b){  return a+b;
}// 这样导出的 add是作为 exports 的一个方法被导出的exports.add = add;// main.jsvar Add = require('add');console.log(Add.add(1,2));//Add是require引入的模块名,add是方法名。

node在编译的时候,会把代码封装成如下的样子

// require 是对 Node.js 实现查找模块的 Module._load 实例的引用// __finename 和 __dirname 是 Node.js 在查找该模块后找到的模块名称和模块绝对路径(function(exports,require,module,__filename,__dirname){  function add (a,b){    return a+b;
  }
  exports.add = add;
})

为了将函数直接导出成一个模块,而不是一个方法,用到了全局变量module,下面就是我们常见的样子了:

// add.jsfunction add (a,b){  return a+b ;
}module.exports = add;// main.jsvar add = require('add');console.log(add(1,2));
  1. 模块标识

模块标识就是require()里的参数,必须是小驼峰命名的字符串,或者是路径(./ 或../)。可以没有后缀.js

Node的模块机制

Node并不是完全按照commonjs规范来实现,而是进行了一些取舍,并增加了自己的一些特性。
Node中引入模块,经历3个步骤:

  • 路径分析

  • 文件定位

  • 编译执行

Node中模块分为2种,核心模块(Node提供的)和文件模块(用户自己编写的)
Node对引入过的模块会进行缓存,就像前端浏览器会缓存静态脚本来提高性能一样。require()方法在对同一模块的二次加载一律采用缓存优先的方式。但是对核心模块的缓存检查优先于对文件模块的缓存检查。

  1. 路径分析

就是对模块标识的分析呗。
模块标识符在Node中分以下几类:

  • 核心模块,如http,path

  • ./ ../相对路径

  • / 绝对路径

  • 非路径形式的文件模块。

  1. 文件分析

如果模块标识符没有后缀,默认补上后缀从.js,.json,.node来次序查找。

  1. 模块编译

js编译上面已经提到了。Node对JS文件进行了包装,在头部添加了(function (exports, require, module, __filename, __dirname) {...})。这样每个模块都进行了作用域隔离。包装之后通过vm原生模块的runInThisContext()方法执行(类似eval,只是具有明确上下文,不污染全局)返回一个function。然后将上述参数传给这个function执行。
json编译更简单,Node直接用JSON.parse()方法编译json内容,得到的对象赋给exports。

包和NPM

commonjs的包规范包含2个组成部分,包结构和包描述文件
包描述文件:package.json
包结构:

  • package.json 包描述文件

  • bin: 存放可执行二进制文件的目录

  • lib 存放js代码的目录

  • doc. 存放文档的目录

  • test: 存放单元测试用例的代码

NPM的用法就不多说了。

前后端公用模块

自从Node出来以后,js也可以运用在后端。但是前后端的JS扮演的角色不同,浏览器端的js需要经历从同一个服务器分发到多个客户端执行。服务端的js则是相同的代码多次执行。前者的瓶颈在于带宽。后者的瓶颈在于CUP和内存。前者需要通过网络加载。后者从磁盘中加载。
因为Node基于commonjs规范来同步的加载模块的。前端若用同步方式来加载模块,在用户体验上会造成很大的问题。UI在初始化的时候需要等待很长时间来加载js脚本。所以提出了异步模块定义AMD和CMD。这在我的另一篇博客中有提到。就不多说了。

为了写个能兼容前后端的模块规范,类库的开发者要把代码包装在一个 闭包里。这样就能兼容Node,AMD,CMD和常见的浏览器。



作者:从小就很瘦
链接:https://www.jianshu.com/p/198251a4735e


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP