1、程序入口
先写一个非常简单的例子来认识 Node 的总入口
// file hello.js
(function(){
console.log('Hello Node.js')
})()
在命令行下执行 node hello.js 即可看到输出 Hello Node.js
2、VM模块
在 Node.js 核心模块中,有一个用于执行JavaScript代码的 VM虚拟机模块。该模块与JavaScript全局函数eval()类似,提供了一个JavaScript代码执行的沙箱环境。通过VM,JavaScript代码可以被编译后立即执行,也可以编译后保存稍后执行。
// file hello.js
(function(){
console.log('Hello NodeJS~')
}) // 注意这里与前一个例子区别是 没有最后一个()
// file main.js
const vm = require('vm');
const fs = require('fs');
const path = require('path');
var prt = path.resolve(__dirname, '.', 'hello.js');
function stripBOM(content){
if(content.charCodeAt(0) === 0xFEFF){
content = content.slice(1);
}
return content;
}
var wrapper = stripBOM(fs.readFileSync(prt, 'utf8'));
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: prt,
lineOffset: 0,
displayErrors: true
});
compiledWrapper();
此时运行 node main.js 即可看到输出 Hello NodeJs~
下面对可能遇到的问题总结一下:
1)关于 .charCodeAt(0) === 0xFEFF
- 0xFEFF —— 零宽度非换行的空格 (每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格”,用FEFF表示。这正好是两个字节,而且FF比FE大1)
- 0x200B —— 零宽度空格
- 0x200C —— 零宽度非连字空格
- 0x200D —— 零宽度连字空格
2)fs.readFileSync(filename, [encoding])
3)VM模块包含了三个常用的方法:
vm.runInThisContext(code[, options]) :创建一个独立的沙箱环境,以执行对参数code的编译,运行并返回结果。没有权限访问本地作用域,但是可以访问Global全局对象。
- filename: 指定这个脚本产生的堆栈跟踪信息所使用的文件名
- lineOffset:指定这个脚本产生的堆栈跟踪信息的行号偏移量
- columnOffset:指定这个脚本产生的堆栈跟踪信息的列号偏移量
- displayErrors: 当被设置为真的时候,如果在编译代码时发生了错误,造成错误的行号会被附加到堆栈跟踪信息中。默认为 true。
- timeout: 指定中断执行前代码执行的最长时间,如果执行被中断,就会抛出一个错误
- breakOnSigint: 如果为真,当接收到(Ctrl+C)时,执行会被中断。在脚本执行期间,连接到process.on(“SIGINT”)上存在的事件处理程序将会被禁用,但是在这之后脚本会继续工作。如果执行被中断了,就会抛出一个错误。
vm.runInContext(code, contextifiedSandbox[, options]) :若要使执行的代码不可访问Global全局对象可以使用vm.runInContext()方法执行代码,执行前需要用vm.createContext([sandbox])方法创建一个沙箱环境。
- vm.runInNewContext(code[, sandbox][, options]) :如果提供了沙箱环境,则将将沙箱环境上下文化并使用,否则创建一个新的沙箱环境,将沙盒作为全局变量运行代码并返回执行结果。
3、模块加载与缓存
用Node.js编写程序,一个js文件对应一个模块,在module.js文件中,其构造函数定义如下
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加载一个js文件时,会先new 一个 Module
var module = new Module(filename, parent);
然后读入Js代码并对文件进行头尾包装。如此这般之后,Js文件里面的代码变成这个匿名函数内部的语句。
(function (exports, require, module, __filename, __dirname) {
//原始文件内容
});
上述形式的代码实际上是一个函数字面量,说的直白点,就是一个定义匿名函数的表达式。Node使用V8编译并运行上面的代码,也就是对以上表达式求值,其值是一个函数对象。V8将结果返回给Node。
//右侧表达式的值是一个函数对象
var fn = (function(){
}) ;
Node得到返回的函数对象,使用apply方法,指定上下文,传入 module.exports,require方法, module,文件名以及路径作为参数,执行函数。在这一步,开始执行Js文件内部的代码。
var args = [this.exports, require, this, filename, dirname];
var result = compiledWrapper.apply(this.exports, args); //compiledWrapper 为函数对象
由源代码可知,Js文件运行的上下文环境是module.exports,因此在文件中,也可以直接使用this导出对象。一旦一个文件加载之后,其对应的模块被缓存,其他文件又require的时候,直接取缓存。
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
4、正确导出模块
我已在之前的 【NodeJS】浅析 exports 与 module.exports 的区别 及 export default 与 export 的区别 分析的很详细
本文介绍了NodeJS的程序入口,重点解析了VM模块,包括VM的三个常用方法以及如何在不同环境中执行JavaScript代码。同时,文章详细阐述了NodeJS的模块加载与缓存机制,解释了模块执行的过程和上下文。最后,提到了正确导出模块的方法,为读者理解NodeJS模块系统提供了深入的见解。

2万+

被折叠的 条评论
为什么被折叠?



