CommonJS-Node.js-世界了解模块的机密-为-Node.js-规范成功原理 (commonwealth)
了解Node.js
Node.js是一个基于ChromeV8引擎的Script运转环境,经常使用了一个事情驱动、非阻塞式I/O模型,让JavaScript运转在服务端的开发平台,它让JavaScript成为与、/target=_blankclass=infotextkey>Python、Perl、Ruby等服务端言语相提并论的脚本言语。Node中削减了很多内置的模块,提供各种各样的性能,同时也提供许多第三方模块。
模块的疑问
为什么要有模块
复杂的前端名目须要做分层处置,依照性能、业务、组件拆分红模块,模块化的名目至少有以下好处:
几种模块化规范
Node中的模块
Node中驳回了CommonJS规范
成功原理:
Node中会读取文件,拿到内容成功模块化,Require方法同步援用
tips:Node中任何js文件都是一个模块,每一个文件都是模块
Node中模块类型
Node中内置模块
fsfilesystem
操作文件都须要用到这个模块
constpath=require('path');//处置门路
constfs=require('fs');//filesystem
////同步读取
letcontent=fs.readFileSync(path.resolve(__dirname,'test.js'),'utf8');
console.log(content);
letexists=fs.existsSync(path.resolve(__dirname,'test1.js'));
console.log(exists);
path门路处置
constpath=require('path');//处置门路
//join/resolve用的时刻可以混用
console.log(path.join('a','b','c','..','/'))
//依据曾经有的门路来解析相对门路,可以用他来解析性能文件
console.log(path.resolve('a','b','/'));//resolve不允许/会解析成根门路
console.log(path.join(__dirname,'a'))
console.log(path.extname('1.js'))
console.log(path.dirname(__dirname));//解析父目录
vm运转代码
字符串如何能变成JS口头呢?
eval中的代码口头时的作用域为以后作用域。它可以访问到函数中的部分变量。
global.test1='123'
functionb(){
eval('console.log(test)');//localscope
newFunction('console.log(test1)')()//123
newFunction('console.log(test)')()//globalscope
2.newFunction
newFunction()创立函数时,不是援用以后的词法环境,而是援用全局环境,Function中的表白式经常使用的变量要么是 传入的参数 要么是 全局的值
Function可以失掉全局变量,所以它还是或许会有变量污染的状况产生
functiongetFn(){
letfn=newFunction('console.log(value)')
global.a=100//挂在到全局对象global上
newFunction("console.log(a)")()//100
前面两种方式,咱们不时强调一个概念,那就是 变量的污染
VM的特点就是不受环境的影响,也可以说他就是一个 沙箱环境
在Node中全局变量是在多个模块下共享的,所以尽量不要在global中定义属性
所以,vm.runInThisContext可以访问到global上的全局变量,但是访问不到自定义的变量。而vm.runInNewContext访问不到global,也访问不到自定义变量,他存在于一个全新的口头高低文
constvm=require('vm')
global.a=1
//vm.runInThisContext("console.log(a)")
vm.runInThisContext("a=100")//沙箱,独立的环境
console.log(a)//1
vm.runInNewContext('console.log(a)')
console.log(a)//aisnotdefined
Node模块化的成功
node中是自带模块化机制的,每个文件就是一个独自的模块,并且它遵照的是CommonJS规范,也就是经常使用require的方式导入模块,经过module.export的方式导出模块。
node模块的运转机制也很繁难,其实就是在每一个模块外层包裹了一层函数,有了函数的包裹就可以实现代码间的作用域隔离。
咱们先在一个js文件中间接打印arguments,失掉的结果如下图所示,咱们先记住这些参数。
console.log(arguments)//exports,require,module,__filename,__dirname
Node中经过modules.export导出,require引入。其中require依赖node中的fs模块来加载模块文件,经过fs.readFile读取到的是一个字符串。
在javascrpt中可以经过eval或许newFunction的方式来将一个字符串转换成js代码来运转。但是前面提到过,他们都有一个致命的疑问,就是 变量的污染 。
成功require模块加载器
首先导入依赖的模块path,fs,vm,并且创立一个Require函数,这个函数接纳一个modulePath参数,示意要导入的文件门路
constpath=require('path');
constfs=require('fs');
constvm=require('vm');
//定义导入类,参数为模块门路
functionRequire(modulePath){
在Require中失掉到模块的相对门路,经常使用fs加载模块,这里读取模块内容经常使用newModule来形象,经常使用tryModuleLoad来加载模块内容,Module和tryModuleLoad稍后成功,Require的前往值应该是模块的内容,也就是module.exports。
//定义导入类,参数为模块门路
functionRequire(modulePath){
//失掉以后要加载的相对门路
letabsPathname=path.resolve(__dirname,modulePath);
//创立模块,新建Module实例
constmodule=newModule(absPathname);
//加载以后模块
tryModuleLoad(module);
//前往exports对象
returnmodule.exports;
Module的成功就是给模块创立一个exports对象,tryModuleLoad口头的时刻将内容参与到exports中,id就是模块的相对门路。
//定义模块,参与文件id标识和exports属性
functionModule(id){
this.id=id;
//读取到的文件内容会放在exports中
this.exports={};
node模块是运转在一个函数中,这里给Module挂载静态属性wrer,外面定义一下这个函数的字符串,wrapper是一个数组,数组的第一个元素就是函数的参数部分,其中有exports,module,Require,__dirname,__filename,都是模块中罕用的全局变量.
第二个参数就是函数的完结部分。两部分都是字符串,经常使用的时刻将他们包裹在模块的字符串外部就可以了。
//定义包裹模块内容的函数
Module.wrapper=[
"(function(exports,module,Require,__dirname,__filename){",
_extensions用于针对不同的模块裁减名经常使用不同的加载方式,比如JSON和javascript加载方式必需是不同的。JSON经常使用JSON.parse来运转。
javascript经常使用vm.runInThisContext来运转,可以看到fs.readFileSync传入的是module.id也就是Module定义时刻id存储的是模块的相对门路,读取到的content是一个字符串,经常使用Module.wrapper来包裹一下就相当于在这个模块外部又包裹了一个函数,也就成功了私有作用域。
经常使用call来口头fn函数,第一个参数扭转运转的this传入module.exports,前面的参数就是函数外面包裹参数exports,module,Require,__dirname,__filename。/
//定义裁减名,不同的裁减名,加载方式不同,成功js和json
Module._extensions={
'.js'(module){
constcontent=fs.readFileSync(module.id,'utf8');
constfnStr=Module.wrapper[0]+content+Module.wrapper[1];
constfn=vm.runInThisContext(fnStr);
fn.call(module.exports,module.exports,module,Require,__filename,__dirname);
'.json'(module){
constjson=fs.readFileSync(module.id,'utf8');
module.exports=JSON.parse(json);//把文件的结果放在exports属性上
tryModuleLoad函数接纳的是模块对象,经过path.extname来失掉模块的后缀名,而后经常使用Module._extensions来加载模块。
//定义模块加载方法
functiontryModuleLoad(module){
//失掉裁减名
constextension=path.extname(module.id);
//经事先缀加载以后模块
Module._extensions[extension](module);//战略形式???
到此Require加载机制基本就写完了。Require加载模块的时刻传入模块称号,在Require方法中经常使用path.resolve(__dirname,modulePath)失掉到文件的相对门路。而后经过newModule实例化的方式创立module对象,将模块的相对门路存储在module的id属性中,在module中创立exports属性为一个json对象。
经常使用tryModuleLoad方法去加载模块,tryModuleLoad中经常使用path.extname失掉到文件的裁减名,而后依据裁减名来口头对应的模块加载机制。
最终将加载到的模块挂载module.exports中。tryModuleLoad口头终了之后module.exports曾经存在了,间接前往就可以了。
接上去,咱们给模块参与缓存。就是文件加载的时刻将文件放入缓存中,再去加载模块时先看缓存中能否存在,假设存在间接经常使用,假设不存在再去从新加载,加载之后再放入缓存。
//定义导入类,参数为模块门路
functionRequire(modulePath){
//失掉以后要加载的相对门路
letabsPathname=path.resolve(__dirname,modulePath);
//从缓存中读取,假设存在,间接前往结果
if(Module._cache[absPathname]){
returnModule._cache[absPathname].exports;
//创立模块,新建Module实例
constmodule=newModule(absPathname);
//参与缓存
Module._cache[absPathname]=module;
//加载以后模块
tryModuleLoad(module);
//前往exports对象
returnmodule.exports;
参与性能:省略模块后缀名。
智能给模块参与后缀名,成功省略后缀名加载模块,其实也就是假设文件没有后缀名的时刻遍历一下一切的后缀名看一下文件能否存在。
//定义导入类,参数为模块门路
functionRequire(modulePath){
//失掉以后要加载的相对门路
letabsPathname=path.resolve(__dirname,modulePath);
//失掉一切后缀名
constextNames=Object.keys(Module._extensions);
letindex=0;
//存储原始文件门路
constoldPath=absPathname;
functionfindExt(absPathname){
if(index===extNames.length){
returnthrownewError('文件不存在');
fs.accessSync(absPathname);
returnabsPathname;
}catch(e){
constext=extNames[index++];
findExt(oldPath+ext);
//递归追加后缀名,判别文件能否存在
absPathname=findExt(absPathname);
//从缓存中读取,假设存在,间接前往结果
if(Module._cache[absPathname]){
returnModule._cache[absPathname].exports;
//创立模块,新建Module实例
constmodule=newModule(absPathname);
//参与缓存
Module._cache[absPathname]=module;
//加载以后模块
tryModuleLoad(module);
//前往exports对象
returnmodule.exports;
源代码调试
咱们可以经过VSCode调试Node.js
步骤
创立文件a.js
module.exports='abc'
1.文件test.js
letr=require('./a')
console.log(r)
1.性能debug,实质是性能.vscode/launch.json文件,而这个文件的实质是能提供多个启动命令入口选用。
一些经常出现参数如下:
修正launch.json,skipFiles指定单步伐试跳过的代码
梳理代码步骤
1.首先进入到进入到require方法:Module.prototype.require
2.调试到Module._load方法中,该方法前往module.exports,Module._resolveFilename方法前往处置之后的文件地址,将文件改为相对地址,同时假设文件没有后缀就加上文件后缀。
3.这里定义了Module类。id为文件名。此类中定义了exports属性
4.接着调试到module.load方法,该方法中经常使用了战略形式,Module._extensions[extension](this,filename)依据传入的文件后缀名不同调用不同的方法
5.进入到该方法中,看到了外围代码,读取传入的文件地址参数,拿到该文件中的字符串内容,口头module._compile
6.此方法中口头wrapSafe方法。将字符串前后参与函数前后缀,并用Node中的vm模块中的runInthisContext方法口头字符串,便间接口头到了传入文件中的console.log代码行内容。
至此,整个Node中成功require方法的整个流程代码曾经调试终了,经过对源代码的调试,可以协助咱们学习其成功思绪,代码格调及规范,有助于协助咱们成功工具库,优化咱们的代码思绪,同时咱们知道关系原理,也对咱们处置日常开发上班中遇到的疑问提供协助。
如何让node运行es6模块文件及其原理详解
最新版的 node 支持最新版 ECMAScript 几乎所有特性,但有一个特性却一直到现在都还没有支持,那就是从 ES2015 开始定义的模块化机制。 而现在我们很多项目都是用 es6 的模块化规范来写代码的,包括 node 项目,所以,node 不能运行 es6 模块文件就会很不便。 让 node 运行 es6 模块文件的方式有两种: 转码 es6 模块为 commonjs 模块 hook node 的 require 机制,直接让 node 的 require 加载 import/export1. 转码 es6 模块为 commonjs 模块因为 node 支持几乎所有除 import/export 外的语法,所以我们只需要将 import/export 转码成 require/exports,而不需要转码其他语法。 比如下面的项目:- - src/ - - - ...# { main: lib/ # 由工具转码 src 目录下源文件到 lib 目录下}# src/ print from ./print;print(index);export default print;# src/ default str => { (print: + str);};因为 src 目录下的源文件都是 es6 模块化规范的,node 并不能直接运行,所以需要转码成 commonjs 规范的代码。 这个过程有两个方案: 如果不会单独使用 src 目录下的某个文件,而仅仅是以 src/ 为入口文件使用,可以把 src 目录下的文件打包成一个文件到 lib/:这种方式推荐使用工具 rollup 如果需要单独使用 src 目录下的文件,那就需要把 src 目录下的文件一对一的转码到 lib 目录下:这种方式推荐使用工具 gulp + babel1.1 用 rollup 把 src 目录下的文件打包成一个文件到 lib/相关文件:# default { input: src/, output: { file: lib/, format: cjs, },};# { scripts: { build: rollup -c }, devDependencies: { rollup: ^0.66.4 }}运行命令:npm run build结果:# lib/ strict;var print = str => { (print: + str);};print(index); = print;1.2 用 gulp + babel 把 src 目录下的文件一对一的转码到 lib 目录下相关文件:# gulp = require(gulp);const babel = require(gulp-babel);(babel, () => (src/**/*) (babel({ plugins: [@babel/plugin-transform-modules-commonjs] })) ((lib)));(babel)();# { scripts: { build: node }, devDependencies: { @babel/core: ^7.1.2, @babel/plugin-transform-modules-commonjs: ^7.2.0, gulp: ^4.0.0, gulp-babel: ^8.0.0 }}运行命令:npm run build结果:# lib/ strict;(exports, __esModule, { value: true}); = void 0;var _print = _interopRequireDefault(require(./print));function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }(0, _)(index);var _default = _; = _default;# lib/ strict;(exports, __esModule, { value: true}); = void 0;var _default = str => { (print: + str);}; = _default;2. hook node 的 require 机制,直接加载 import/export这种机制一般是通过对 node 的 require 机制进行 hook,劫持 require 抓取的源文件代码,把源代码转码成 commonjs 规范之后,再传送给 require 机制原本的代码流中。 pirates 之类的第三方 npm 包提供了这种添加 hook 的功能。 babel-register 便是使用这种方式达到 node 运行 es6 模块文件的目的的。 2.1 使用 babel-register 直接运行 es6 模块文件示例目录:- - src/ - # 这里多了一个入口文件,专门用于注册 babel-register - - - ...相关文件:# { scripts: { run: node src/ }, devDependencies: { @babel/core: ^7.1.2, @babel/plugin-transform-modules-commonjs: ^7.2.0, @babel/register: ^7.0.0 }}# src/ # 入口文件必须使用 commonjs 规范来写,因为还没有注册 hookrequire(@babel/register)({ plugins: [@babel/plugin-transform-modules-commonjs]});require(./index);# src/ print from ./print;print(index);# src/ default str => { (print: + str);};运行:npm run run结果:# 命令行打印print: index这种方式因为中间转码会有额外的性能损耗,所以不建议在生产环境下使用,只建议在开发模式下使用。 2.2 使用babel-node 直接运行 es6 模块文件babel-node 对 babel-register 进行了封装,提供了在命令行直接运行 es6 模块文件的便捷方式。 示例目录:- - src/ - - - ...相关文件:# { scripts: { run: babel-node src/ --plugins @babel/plugin-transform-modules-commonjs }, devDependencies: { @babel/core: ^7.1.2, @babel/node: ^7.2.0, @babel/plugin-transform-modules-commonjs: ^7.2.0 }}# src/ print from ./print;print(index);# src/ default str => { (print: + str);};运行:npm run run结果:# 命令行打印print: index这种方式也不建议在生产环境下使用,只建议在开发模式下使用。 3. 链接es6 就是指 ECMAScript 2015es7 就是指 ECMAScript 2016es8 就是指 ECMAScript 2017es9 就是指 ECMAScript 2018到写这篇文章为止,已发布了 ECMAScript 2018。
《Node.js权威指南》epub下载在线阅读,求百度网盘云资源
《权威指南》(陆凌牛)电子书网盘下载免费在线阅读
资源链接:
链接:权威指南-陆凌牛
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。