Reputation: 99
I have been told that the 'this' in the global scope means the global object, and using 'var' in the global scope will define properties in the global object. However, I have no idea why I get the following behaviors when using 'var' and 'this' in the node.js program. I find a similar stackoverflow question about this, but I don't think my question is related to the hoisting. Could someone explain it? Thanks. (I'm using node.js version 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();
I wrap the above code to an HTML file, like below, and test in the Chrome (version 44.0.2403.157 m). The results are all as expected. Are there any important concepts I'm missing in 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>
After digging into the node.js source code, I think I've understood the reason why the sample code would produce such outputs. Briefly speaking, when launch xxx.js by node.js with "node.js xxx.js", the xxx.js would be loaded by node.js in a "module loading" way.
1) In src/node.js in the node.js source codes:
If launching javascript file with the form of "node xxx.js", node will execute the js file by using Module.runMain();
} else if (process.argv[1]) {
...
} else {
// Main entry point into most programs:
Module.runMain();
}
2) in lib/module.js:
The call flow in this file are: "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);
}
Because "_compile()" function is in the prototype structure of the Module object, the "self" (or "this") variable in "_compile()" must be the Module itself. Therefore, the whole javascript file loading process of "node xxx.js" is just like to execute a function in which the content of the function is xxx.js, and the "this" in that function would be the "Module.exports" (because Module.exports is the first argument passed into the "apply()" function at the end of _compile())
Hence, the whole loading process is equivalent to the following javascript codes:
function (exports, require, module, __filename, __dirname) {
/* the content of xxx.js, and "this" would refer to "module.exports" */
}
After knowing this, then the original example codes could be understood with the following 2 simple and basic javascript rules:
The "this" object in a function would be the "global object" if the function is called in "xxx();" form rather than "y.xxx();" form.
Assigning a value to an undeclared variable implicitly creates it as a global variable (it becomes a property of the global object).
Hence, the detailed explanations of the example codes line by line are as follows:
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();
Upvotes: 0
Views: 1714
Reputation: 198324
The question is a bit misguided, since in global context, all four pairs return the same result, both in Node and (replacing window
for global
) in a browser. This is because var
has no meaning in global scope, and defines properties on this
; since this
is the global object, this.nnn
, global.zzz
and yyy
all also define properties on global
. When reading, nnn
, zzz
and yyy
are not recognised as local variables, thus they are looked up on global object (window
here instead of global
so it would run in Stack Overflow instead of Node, but same principle will apply):
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>
However, enclose that in some non-global scope, and things change for xxx
- notice how it is no longer defined on the global object:
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>
Everything stays the same except for the this.xxx
/xxx
part. this.xxx
should be undefined
, since xxx
is a local variable, and not defined on the global object like the other three. However, as a local variable, it is getting captured by any functions created in that scope: we say that "v
closes over xxx
", or "v
is a closure over xxx
", so the local variable xxx
can be seen from v
.
In fact, even cooler, closure means that all local variables that are visible at the scope where v
is created are visible by v
as well, wherever we call v
from - illustrated in the following snippet:
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>
Note that here we can still read xxx
as 10
inside v
, even though the place where we are calling the function v has no idea about xxx
.
Note that the only way to get the global scope in Node is to execute your program interactively (node < stuff.js
); if you execute it as a file (node stuff.js
), a file will execute in its own scope, and you will not see this effect:
// a.js
var xxx = 10;
console.log(this.xxx);
$ node < a.js
10
$ node a.js
undefined
Upvotes: 1