This is the 7th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021


Regular study notes, including ES6, Promise, Node.js, Webpack, HTTP Principles, Vue buckets, and possibly more Typescript, Vue3, and common interview questions.

Reference the vm virtual machine | Node’s official website

In our last article, we touched on a problem.

How can strings be executed as JS?

We covered two methods in detail, the eval Function and the new Function.

Again, functions created by the Function constructor do not create closures of the current environment. They are always created in the global environment, so at run time they can only access global variables and their local variables, not the variables of the scope in which they were created by the Function constructor. This is different from the code that creates the function using eval.

global.a = 100; // Attach to the global object
var b = 200; // this ! == global
new Function("console.log(a)") ()/ / 100
new Function("console.log(b)") ()// b is not defined
Copy the code

Function can fetch global variables, so it can still pollute variables. Function is the realization principle of module engine, and I will explain it separately in a follow-up article.

Another solution, which we did not expand on in detail in the last article, is the VM module.

The vm module

In the above text, I have been emphasizing the concept of variable pollution.

VM characteristics are not affected by the environment, it can be said that it is a sandbox environment (sandbox mode provides modules with an environment to run without affecting other modules and their private sandboxes).

const vm = require('vm')
global.a = 100;
// Run in the current environment [current scope]
vm.runInThisContext('console.log(a)'); / / 100
// Run in a new environment [other scope]
vm.runInNewContext('console.log(a)'); // a is not defined
Copy the code

It is important to note here that since global variables are shared across multiple modules in Node, try not to define attributes in global. The definition in the Demo is for ease of understanding.

Suppose we have a file in the parent directory 1.js that defines global.a = 100; . Now let’s introduce this file

requrie(./1);
console.log(a); / / 100
Copy the code

We can see that in the current file we do not define variable A, just associate the two module files together. This is what I mentioned above, global variables in Node are shared across multiple modules.

It works because in the context of Node, there is a global execution context.

// Simulate Node's global environment
// vm.runInThisContext is executed in the current global environment, but no new functions are generated
- function(exports.module.require, __dirname, __filename){ // ... }
- vm.runInThisContext ...
// vm.runInNewContext is executed outside the global environment
vm.runInNewContext ...
Copy the code

So, vm.runInThisContext can access global variables on global, but not custom variables. While vm.runInNewContext does not access global or custom variables, it exists in a completely new execution context.

The require is implemented via vm.runInThisContext.

There are four main steps to implement require.

  1. Read the file to import.
  2. After reading the file, wrap the code into a function.
  3. throughvm.runInThisContextConvert it to JS syntax.
  4. Code calls.

Suppose we now have the following two files. They are A.js and B.js

Module. exports exports a variable that is received in file B using require.
// a.js
module.exports = "a"
// b.js
let a = require('./a');
console.log(a); // a
Copy the code

We can analyze the implementation logic of import and export through the above four steps.

  1. Read the file.

    This is what happens when you introduce the contents of the file you want to import into the file you want to receive

    let a = module.exports = "a";
    Copy the code

    However, Node cannot parse this form at all, so we need to proceed to step 2.

  2. Encapsulate the read file as a function.

    let a = (function(exports.module.require, __dirname, __filename){
      module.exports = "a";
      return module.exports })(... args)// exports, module, require, __dirname, __filename pass five arguments
    Copy the code

    For the reason of encapsulation as a function, we can refer to the following example.

    Suppose that instead of a string, we now pass in a function.

    // a.js
    var a = 100;
    module.exports = function(){}
    Copy the code

    So when we parse, it will be parsed in the following format

    let a = (function(exports.module.require, __dirname, __filename){
      var a = 100;
      module.exports = function(){};
      return module.exports })(... args)// exports, module, require, __dirname, __filename pass five arguments
    Copy the code

    We exported module.exports, so the variable A defined in the module file belongs only to the current execution context.

    When parsing, the variable A is put into the function. True separation of scope is achieved.

  3. vm.runInThisContextParsing into executable Js code

    The code we are working with will be in the form of a string, so we need to parse the string through vm.runInThisContext.

  4. Make code calls

    Before we do that, we actually need to debug the code.

The next article will cover module debugging in Node in more detail.

This article was created by Mo Xiaoshang. If there are any problems or omissions in the article, welcome your correction and communication. You can also follow my personal site, blog park and nuggets, AND I will upload the article to these platforms after it is produced. Thank you for your support!