如何用node.js實現動態執行指令碼

螞蟻小編發表於2017-04-14

本章節介紹一下如何用node.js實現動態執行指令碼。

node.js新增了虛擬機器模組,其實也不能說是新增的,只是把一些內部介面暴露出來。

我們可以從node / src / node.js看到這些程式碼:

[JavaScript] 純文字檢視 複製程式碼
var Script = process.binding('evals').NodeScript;
var runInThisContext = Script.runInThisContext;
   
NativeModule.wrap = function(script) {
  return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
   
NativeModule.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
 ];
   
 NativeModule.prototype.compile = function() {
  var source = NativeModule.getSource(this.id);
  source = NativeModule.wrap(source);
   
  var fn = runInThisContext(source, this.filename, true);
  fn(this.exports, NativeModule.require, this, this.filename);
   
  this.loaded = true;
};

其中的Script物件,就與require('vm')返回的物件很相似,而實質上,vm模組就是對Script物件的封裝。

[JavaScript] 純文字檢視 複製程式碼
var Script = process.binding('evals').NodeScript;
console.log(Script)
/**
{ [Function: NodeScript]
 createContext: [Function],
 runInContext: [Function],
 runInThisContext: [Function],
 runInNewContext: [Function] }
   
*/
console.log(require('vm'))
{ Script: 
  { [Function: NodeScript]
   createContext: [Function],
   runInContext: [Function],
   runInThisContext: [Function],
   runInNewContext: [Function] },
 createScript: [Function],
 createContext: [Function],
 runInContext: [Function],
 runInThisContext: [Function],
 runInNewContext: [Function] }

其中,runInThisContext 相當於一個全新的環境中執行程式碼,不會影響當前作用域的物件。而runInNewContext與runInContext則能指定是上下文物件,區別是一個普通物件或一個context物件。換言之,runInNewContext與runInContext能區域性影響當前作用域的物件。要與當前環境完全進行互動的話,就需要用到危險的eval。在node.js自帶的載入體系中,顯然沒有這樣的勇氣,使用的是runInThisContext。並且在這之前做了許多工作,如把使用者的JS檔案裡面的內容再包一層( NativeModule.wrap),還有其他凌散操作,加之是同步操作,實際上是一種效率很糟的載入方式。唯一的好處是,使用了同步,讓程式碼編寫起來簡單多了。

在github中,已有人對這幾種動態執行指令碼的方法進行效能比較:

[JavaScript] 純文字檢視 複製程式碼
var vm = require('vm'),
 code = 'var square = n * n;',
 fn = new Function('n', code),
 script = vm.createScript(code),
 sandbox;
   
n = 5;
sandbox = { n: n };
   
benchmark = function(title, funk) {
 var end, i, start;
 start = new Date;
 for (i = 0; i < 5000; i++) {
  funk();
 }
 end = new Date;
 console.log(title + ': ' + (end - start) + 'ms');
}
   
var ctx = vm.createContext(sandbox);
benchmark('vm.runInThisContext',   function() { vm.runInThisContext(code); });
benchmark('vm.runInNewContext',   function() { vm.runInNewContext(code, sandbox); });
benchmark('script.runInThisContext', function() { script.runInThisContext(); });
benchmark('script.runInNewContext', function() { script.runInNewContext(sandbox); });
benchmark('script.runInContext', function() { script.runInContext(ctx); });
benchmark('fn',           function() { fn(n); });
/**
vm.runInThisContext: 212ms
vm.runInNewContext: 2222ms
script.runInThisContext: 6ms
script.runInNewContext: 1876ms
script.runInContext: 44ms
fn: 0ms
   
*/

由此可見,還是v8自帶的方法Function完勝。

相關文章