在node.js program

时间:2015-09-03 08:36:53

标签: javascript node.js

我被告知全局范围内的'this'表示全局对象,在全局范围中使用'var'将定义全局对象中的属性。但是,我不知道为什么在node.js程序中使用'var'和'this'时会出现以下行为。我发现了类似的stackoverflow question,但我不认为我的问题与吊装有关。有人可以解释一下吗?谢谢。 (我正在使用node.js版本0.12.4)

yyy = 20;
global.zzz = 30;

var xxx = 10;
this.nnn = 40;

var v = function() {
    //console.log(global);  // <-- There is 'yyy' in the global object as expected
    console.log(this.yyy);  // <-- 20 as expected
    console.log(yyy);       // <-- 20 as expected

    //console.log(global);  // <-- There is 'zzz' in the global object as expected
    console.log(this.zzz);  // <-- 30 as expected
    console.log(zzz);       // <-- 30 as expected

    //console.log(global);  // <-- There is no 'xxx' in the global object.   WHY??? where is 'xxx' located?
    console.log(this.xxx);  // <-- undefined as expected, because there is not 'xxx' in the global object.
    console.log(xxx);       // <-- 10.   WHY??? where is 'xxx' located?

    //console.log(global);  // <-- There is no 'nnn' in the global object.  WHY??? where is 'nnn' located?
    console.log(this.nnn);  // <-- undefined as expected, because there is not 'nnn' in the global object.
    console.log(nnn);       // <-- ReferenceError: nnn is not defined.   WHY ReferenceError instead of 'undefined'???
}

v();

我将上面的代码包装到HTML文件中,如下所示,并在Chrome中进行测试(版本44.0.2403.157 m)。结果都是预期的。我在Node.js中缺少哪些重要概念?

<html>
    <head></head>
    <body>
        <script>
         yyy = 20;
         window.zzz = 30;            // change global.zzz to window.zzz

         var xxx = 10;
         this.nnn = 40;

         var v = function() {
             console.log(this.yyy);  // <-- 20 as expected
             console.log(yyy);       // <-- 20 as expected

             console.log(this.zzz);  // <-- 30 as expected
             console.log(zzz);       // <-- 30 as expected

             console.log(this.xxx);  // <-- 10 as expected
             console.log(xxx);       // <-- 10 as expected

             console.log(this.nnn);  // <-- 40 as expected
             console.log(nnn);       // <-- 40 as expected
         }

         v();
        </script>
    </body>
</html>

=============更新==============

在深入研究node.js源代码之后,我想我已经理解了示例代码产生这样的输出的原因。简而言之,当node.js使用“node.js xxx.js”启动xxx.js时,node.js将以“模块加载”的方式加载xxx.js。

1)在node.js源代码中的src / node.js中:

如果以“node xxx.js”的形式启动javascript文件,节点将使用Module.runMain()执行js文件;

} else if (process.argv[1]) {
      ...
      } else {
        // Main entry point into most programs:
        Module.runMain();
      }

2)在lib / module.js中:

此文件中的呼叫流程为:“Module.runMain()”==&gt; “Module._load()”==&gt; “Module.load()”==&gt; “Module._compile()”==&gt; “compiledWrapper.apply(self.exports,...)”

// bootstrap main module.
Module.runMain = function() {
  // Load the main module--the command line argument.
  Module._load(process.argv[1], null, true);
  ...
};

Module._load = function(request, parent, isMain) {
  ...
  try {
    module.load(filename);
    ...
  } finally {
  ...
}

Module.prototype.load = function(filename) {
  ...
  Module._extensions[extension](this, filename);
  ...
}

Module._extensions['.js'] = function(module, filename) {
  ...
  module._compile(stripBOM(content), filename);
};

NativeModule.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

Module.prototype._compile = function(content, filename) {
  var self = this;
  ...
  // create wrapper function
  var wrapper = Module.wrap(content); // wrap in the above 
                                      // "NativeModule.wrapper"

  var compiledWrapper = runInThisContext(wrapper, { filename: filename });
  ...
  var args = [self.exports, require, self, filename, dirname];
  return compiledWrapper.apply(self.exports, args);
}

因为“_compile()”函数位于Module对象的原型结构中,所以“_compile()”中的“self”(或“this”)变量必须是Module本身。因此,“node xxx.js”的整个javascript文件加载过程就像执行一个函数,其中函数的内容是xxx.js,而该函数中的“this”将是“Module.exports” (因为Module.exports是传递给_compile()末尾的“apply()”函数的第一个参数。

因此,整个加载过程相当于以下javascript代码:

function (exports, require, module, __filename, __dirname) {
  /* the content of xxx.js, and "this" would refer to "module.exports" */
}

知道了这一点后,可以通过以下2个简单和基本的javascript规则来理解原始示例代码:

  1. 如果在“xxx();”中调用该函数,函数中的“this”对象将是“全局对象”。形式而不是“y.xxx();”形式。

  2. 为未声明的变量赋值会隐式将其创建为全局变量(它将成为全局对象的属性)。

  3. 因此,逐行详细解释示例代码如下:

    yyy = 20; // Assign a value to an undeclared variable implicitly creates
              // it as a property of the global object, hence, "yyy" would be 
              // located in the global object.
    
    global.zzz = 30; // explicitly add a property named "zzz" in the global 
                     // object.
    
    var xxx = 10; // "xxx" will be a local variable in the module, and is not  
                  // assigned to the "module.exports". Hence, it actually acts as 
                  // a local variable in the function implicitly created by 
                  // node.js to load the file as a module. Due to lexical 
                  // scoping rule, it can only be accessed from the function 
                  // defined in this file (module).
    
    this.nnn = 40; // This file is loaded as a module by "applying" a function
                   // and pass module.exports as the first argument of apply(), 
                   // hence, the "this" here would refer to "module.exports".
    
    console.log("this === module.exports? " + (this === module.exports)); // true
    
    console.log(module); // you can see a "exports: { nnn: 40 }" lines in the 
                         // module object
    
    var v = function() {
        // according to the call site syntax (v();), the "this" object here would 
        // be the global object.
        console.log("this === global? " + (this === global)); // true
    
        console.log(this.yyy);  // <-- 20 as expected (global objects has "yyy", 
                                // and "this" refers to the global object. 
                                // Bingo~!)
    
        console.log(yyy);       // <-- 20 as expected (according to lexical 
                                // scoping rule, it could find "yyy" in the 
                                // global VariableObject (equals to global 
                                // object). Bingo~!)
    
        console.log(this.zzz);  // <-- 30 as expected (global object has "zzz", 
                                // and "this" refers to the global object. 
                                // Bingo~!)
    
        console.log(zzz);       // <-- 30 as expected (according to lexical 
                                // scoping rule, it could find "zzz" in the 
                                // global VariableObject (equals to global 
                                // object). Bingo~!)
    
        console.log(this.xxx);  // <-- undefined as expected ("xxx" is not 
                                // defined in the global object, "xxx" is just a 
                                // local variable in the function implicitly 
                                // created by node.js to load this file, and 
                                // "this" here refers to the module.exports. 
                                // Hence, "undefined")
    
        console.log(xxx);       // <-- 10 as expected (according to lexical 
                                // scoping rule, it could find "xxx" in the outer 
                                // environment context of this function. Bingo~!)
    
        console.log(this.nnn);  // <-- undefined as expected ("nnn" is actually 
                                // defined as a property of "module.exports", and 
                                // "this" here refers to the global object, 
                                // hence, "undefined")
    
        console.log(nnn);       // <-- ReferenceError: nnn is not defined. 
                                // (according to the lexical scoping rule, it 
                                // could not find any "variable name" equals to 
                                // "nnn" in the scope chain. Because nnn is just 
                                // the property of the module.exports, it could 
                                // not be found in the lexical scoping searching. 
                                // hence, "ReferenceError")
    }
    
    v();
    

1 个答案:

答案 0 :(得分:1)

这个问题有点误导,因为在全局环境中,所有四对都在Node中返回相同的结果,并在浏览器中替换window global}。这是因为var在全局范围内没有意义,并且在this上定义属性;由于this是全局对象,this.nnnglobal.zzzyyy都在global上定义属性。在阅读时,nnnzzzyyy不会被识别为局部变量,因此会在全局对象(window此处而不是global上查找它们,因此它将在Stack Overflow而不是Node中运行,但同样的原则将适用):

yyy = 20;
window.zzz = 30;

var xxx = 10;
this.nnn = 40;

var v = function() {
    snippet.log(Object.keys(window));

    snippet.log("this.yyy: " + this.yyy);
    snippet.log("yyy: " + yyy);

    snippet.log("this.zzz: " + this.zzz);
    snippet.log("zzz: " + zzz);

    snippet.log("this.xxx: " + this.xxx);
    snippet.log("xxx: " + xxx);

    snippet.log("this.nnn: " + this.nnn);
    snippet.log("nnn: " + nnn);
};

v();
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

但是,将它包含在某些非全局范围内,xxx的内容会发生变化 - 请注意它在全局对象上的定义:

function vv() {

  yyy = 20;          // = this.yyy a.k.a. window.yyy
  window.zzz = 30;

  var xxx = 10;      // local variable xxx
  this.nnn = 40;     // = window.nnn

  var v = function() {
      snippet.log(Object.keys(window));

      snippet.log("this.yyy: " + this.yyy);
      snippet.log("yyy: " + yyy);

      snippet.log("this.zzz: " + this.zzz);
      snippet.log("zzz: " + zzz);

      snippet.log("this.xxx: " + this.xxx);
      snippet.log("xxx: " + xxx);

      snippet.log("this.nnn: " + this.nnn);
      snippet.log("nnn: " + nnn);
  };

  v();
  
}

vv();
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

this.xxx / xxx部分外,所有内容都保持不变。 this.xxx应该是undefined,因为xxx是一个局部变量,而不是像其他三个一样在全局对象上定义。但是,作为局部变量,它会被在该范围内创建的任何函数捕获:我们说“v关闭xxx”,或者“vxxx的封闭1}}“,因此可以从xxx看到局部变量v

事实上,即使是更酷的闭包意味着v也可以看到在创建v的范围内可见的所有局部变量,在我们调用{{1}的任何地方来自 - 在以下代码段中说明:

v
var v;

function vv() {

  yyy = 20;          // = this.yyy a.k.a. window.yyy
  window.zzz = 30;

  var xxx = 10;      // local variable xxx
  this.nnn = 40;     // = window.nnn

  v = function() {
      snippet.log(Object.keys(window));

      snippet.log("this.yyy: " + this.yyy);
      snippet.log("yyy: " + yyy);

      snippet.log("this.zzz: " + this.zzz);
      snippet.log("zzz: " + zzz);

      snippet.log("this.xxx: " + this.xxx);
      snippet.log("xxx: " + xxx);

      snippet.log("this.nnn: " + this.nnn);
      snippet.log("nnn: " + nnn);
  };

}

vv();

// snippet.log("xxx before calling v: " + xxx); // ReferenceError
v();
// snippet.log("xxx after calling v: " + xxx); // ReferenceError

请注意,我们仍然可以在<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 --> <script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>内阅读xxx 10,即使我们调用 函数 v < / em>不知道v

请注意,在Node中获取全局范围的唯一方法是以交互方式执行您的程序(xxx);如果您将其作为文件(node < stuff.js)执行,则文件将在其自己的范围内执行,您将看不到此效果:

node stuff.js