【问题标题】:Using 'var' and 'this' in the global scope in node.js program在 node.js 程序的全局范围内使用 'var' 和 'this'
【发布时间】:2015-11-29 00:05:18
【问题描述】:

有人告诉我,全局范围内的“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时,xxx.js会被node.js以“模块加载”的方式加载。

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

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

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

2) 在 lib/module.js 中:

这个文件中的调用流程是: "Module.runMain()" ==> "Module._load()" ==> "Module.load()" ==> "Module._compile()" == > "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. 为未声明的变量赋值隐式地将其创建为全局变量(它成为全局对象的属性)。

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

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();

【问题讨论】:

  • var 声明一个语言环境变量,如果你不使用var 它创建一个全局变量
  • @Haketo:但这并不是真正的问题。
  • xxx → 可通过简单的闭包作用域获得; nnn → 好吧,在范围内的任何地方都没有定义裸露的var nnn
  • @MadaraUchiha 那么有什么意义呢? “全局对象中没有‘xxx’。WHY???WHY???‘xxx’在哪里?”
  • xxx 在您的示例中是一个局部变量,因为您没有在全局上下文中执行。

标签: javascript node.js


【解决方案1】:

这个问题有点误导,因为在全局上下文中,所有四对都返回相同的结果,无论是在 Node 中还是在浏览器中(用 global 替换 window)。这是因为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 的关闭”,所以本地变量xxx可以从v看到。

事实上,更酷的是,闭包意味着在创建v 的范围内可见的所有局部变量也对v 可见,无论我们从哪里调用v - 如下 sn-p 所示:

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 读为v 中的10,即使我们调用函数v的位置不知道关于xxx

请注意,在 Node 中获取全局作用域的唯一方法是交互式执行程序 (node &lt; stuff.js);如果你把它作为一个文件执行(node stuff.js),一个文件会在它自己的作用域内执行,你不会看到这个效果:

// a.js
var xxx = 10;
console.log(this.xxx);

$ node < a.js
10

$ node a.js
undefined

【讨论】:

  • 谢谢~。对于这种说法:“这是因为var在全局范围内没有意义。”,为什么this page中还有一条语句提到我可以在全局范围内使用'var'?
  • 你可以使用它,但结果会和你没有使用它一样。
  • 再次感谢~如果在全局范围内使用'var'和没使用一样,为什么'var xxx = 10'和'xxx = 10'的结果不同在 node.js 文件执行模式下?为什么 'console.log(xxx)' 在 node.js 文件执行模式下会导致 10?为什么在 node.js 文件执行模式的全局范围内使用“this.nnn”无法在文件自己的范围对象中的某些“对象”中定义属性?
  • @WeiHu 阅读到答案的最后 :) 你一开始没有全局范围。
  • @Amadan - 在全局范围内使用 var 与不使用它不同。 var 在全局对象上创建的属性无法删除。通过分配给未声明的标识符(我称之为The Horror of Implicit Globals)或通过分配给this 上的属性创建的属性可以被删除。
猜你喜欢
  • 1970-01-01
  • 2012-11-05
  • 2017-08-18
  • 2015-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多