VM模組是NodeJS裡面的核心模組,支撐了require方法和NodeJS的運作機制,我們有些時候可能也要用到VM模板來做一些特殊的事情。這篇文章就來帶大家詳細了解下Node中的VM模組,希望對大家有幫助!
參考文獻vm 虛擬機器| Node 官網
http://nodejs.cn/api/vm.html
在上一篇文章中,我們提到了一個問題。
字串如何變成 JS 執行?
我們詳細介紹了兩種方法,分別是 eval函數 和 new Function 。
在這裡我們需要再強調一下, 由Function
建構器創建的函數不會創建當前環境的閉包,它們總是被創建於全域環境,因此在運行時它們只能存取全域變數和自己的局部變量,不能存取它們被Function
建構器建立時所在的作用域的變數。這一點與使用 eval
執行建立函數的程式碼不同。
global.a = 100; // 挂在到全局对象global上 var b = 200; // this !== global new Function("console.log(a)")() // 100 new Function("console.log(b)")() // b is not defined
Function
可以取得全域變量,所以他還是可能會有變數污染的情況出現。 Function
是 模組引擎的實作原理 ,後續我會出一篇文章進行單獨講解。
還有一個解決方案,我們在上一次文章中沒有進行詳細的展開,那就是 vm
模組 。
在上述文字中,我一直在強調一個概念,那就是 變數的污染。
VM的特點就是不受環境的影響,也可以說他就是一個沙箱環境 (沙箱模式提供模組一個環境運作而不影響其它模組和它們私有的沙箱)。
const vm = require('vm') global.a = 100; // 运行在当前环境中[当前作用域] vm.runInThisContext('console.log(a)'); // 100 // 运行在新的环境中[其他作用域] vm.runInNewContext('console.log(a)'); // a is not defined
在這裡我們要強調一下,因為 在Node.js中全域變數是在多個模組下共享的,所以盡量不要在global定義屬性。 Demo中的定義是為了方便理解。
假設我們在同級目錄下有一個檔案 1.js ,裡面定義了 global.a = 100;
。現在我們引入這個檔案
requrie(./1); console.log(a); // 100
我們可以發現,在目前檔案中我們並沒有定義變數a,只是只把兩個模組檔案關聯在了一起。這就是我上面提到的,Node中全域變數是在多個模組下共享的。
他的原理是因為在 Node 的環境中,全域中有一個執行上下文。
// 模拟一下Node的全局环境 // vm.runInThisContext在当前全局环境执行,但不会产生新函数 - function(exports, module, require, __dirname, __filename){ // ... } - vm.runInThisContext ... // vm.runInNewContext在全局环境之外执行 vm.runInNewContext ...
所以,vm.runInThisContext
可以存取到 global
上的全域變量,但是存取不到自訂的變數。而 vm.runInNewContext
存取不到 global
,也存取不到自訂變量,他存在於一個全新的執行上下文。
而我們require
就是透過 vm.runInThisContext
實現的。
實作require
主要可以分成以下四個步驟。
讀取需要引入的檔案。
讀取到檔案後,將程式碼封裝成函數。
透過 vm.runInThisContext
將他轉換成 JS 語法。
程式碼呼叫。
假設我們現在有以下兩個檔案。分別是 a.js 和 b.js
// 文件a通过module.exports导出一个变量,在文件b中使用require进行接收。 // a.js module.exports = "a" // b.js let a = require('./a'); console.log(a); // a
我們可以透過上面的四個步驟,分析一下導入的實作邏輯是什麼樣的。
讀取檔案。
將需要引入的文件內容引入到需要接收的文件裡,就會變成這個樣子
let a = module.exports = "a";
但是這種形式,Node根本解析不了,所以我們就需要進行第二步。
將讀取的檔案封裝成函數。
let a = (function(exports, module, require, __dirname, __filename){ module.exports = "a"; return module.exports })(...args) // exports, module, require, __dirname, __filename 将五个参数传入
封装成函数的原因,我们可以参考下面这个例子。
假设我们现在传入的不是字符串,而是一个函数。
// a.js var a = 100; module.exports = function(){}
这样我们在解析的时候,就会被解析成下面这种格式
let a = (function(exports, module, require, __dirname, __filename){ var a = 100; module.exports = function(){}; return module.exports })(...args) // exports, module, require, __dirname, __filename 将五个参数传入
我们导出的是 module.exports
,所以在模块文件中定义的变量a,也只属于当前这个执行上下文。
在解析的时候,变量a 会被放到函数中。真正的实现了 作用域分离。
vm.runInThisContext
解析成可执行的Js代码
我们处理过的代码会以字符串的形式存在,所以我们需要通过vm.runInThisContext
将字符串进行解析。
进行代码调用
在此之前,我们其实还需要对代码进行调试。
更多node相关知识,请访问:nodejs 教程!!
以上是深入了解Node.js中的VM模組的詳細內容。更多資訊請關注PHP中文網其他相關文章!