3

The code

I write the following code and save it as test.js:

var foo = 'I am local';
global.foo = 'I am global';

function print () {
     console.log(this.foo);
};

print(); 
console.log (this.foo); 

I then run it in the terminal with the command node test.js and it returns:

I am global
undefined

The question

Why does it not return:

I am global
I am global

?

rabbitco
  • 2,436
  • 3
  • 13
  • 33
  • Node modules do not run in the global scope. – Bergi Feb 17 '16 at 13:13
  • Thank you @Bergi. How would you phrase it then when console.log is called in a file outside of any functions, objects etc. Would you say it is called in a "local context" ? – rabbitco Feb 17 '16 at 13:30
  • It's known as the "module scope". – Bergi Feb 17 '16 at 14:01
  • I guess this is a duplicate of [What is the top level `this` object and where do module vars live in Node.Js? \[duplicate\]](http://stackoverflow.com/q/33653359/218196) (which is itself marked as a duplicate though. – Felix Kling Feb 18 '16 at 16:09
  • I am not sure it is a duplicate - but thank you for the link. It linked to very valuable information on how Node.js is designed. – rabbitco Feb 19 '16 at 11:29

3 Answers3

3

Inside a Node module, this by design refers to module's exports object:

console.log(this === exports); // true

Making console.log(this.foo) equivalent to console.log(exports.foo).

In other words, neither does this refer to the global object nor do local variables magically become properties of exports.

Since exports.foo doesn't exist, you get undefined.

rabbitco
  • 2,436
  • 3
  • 13
  • 33
Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • I think it is fair to wonder why the this operator refers to the exports object. It cannot be explained by the standard behaviour of the operator. As is appears it is caused by a choice by the designers of Node to set the value to the exports object. Good to know. The local variables do not "magically appear" on the exports object, because they are actually private variables in the function wrapping a Node module - also good to know. Thank you for helping me elevate my knowledge of Node. – rabbitco Feb 19 '16 at 11:39
  • No argument here. The value of `this` depends on how the function is called, so would have to know how the module wrapper is called to know what `this` refers to. – Felix Kling Feb 19 '16 at 14:37
  • Well with the added remark - as I understand it - that in the module scope `this`by design always refers to the `exports`object ... ? – rabbitco Feb 22 '16 at 13:09
  • What: The executing context of this in the module scope is the exports object. Why: Because this context has explicitly been set by the developers of Node and therefore the standard behaviour of `this`cannot be used to explain the value of `this`. – rabbitco Feb 22 '16 at 15:13
  • *"and therefore the standard behaviour of `this` cannot be used to explain the value of `this`"* The standard behavior is that the value of `this` depends on *how* a function is called. A module is a just a function, and the module function is invoked with `myModule.call(module.exports, module, module.exports)`, or something like that. The tricky part might be that it's not *you* who invokes the function, but Node, but this is still standard behavior. – Felix Kling Feb 22 '16 at 15:50
  • Not sure if there is any disagreement: but as (1) code in the module scope is clearly not a property on the exports object by default (`var foo = "boo";` does not create `exports.foo`) and (2) since `this` irrespective of this refers to the exports object: this reference is caused "by design (`bind`, `call` or `apply`)". – rabbitco Feb 22 '16 at 19:56
1

All script files in Node.js are executed in their own execution context, while browsers execute all script files within the global execution context. When calling a function without a specific context, it will normally be defaulted to the global object in Node.

print(); //global execution context -> 'I am global'
console.log (this.foo); // no context -> undefined
Kertis van Kertis
  • 624
  • 1
  • 7
  • 21
1

The this property of a function is set when the function is called and by default points to the object calling the function unless the value is set by methods such as bind, apply or call.

It is worth to note that a module (equivalent to a file) in Node is wrapped in a function() like this:

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

This means that all the code snippets below are actually executed inside this wrapper function. See Where are vars stored in Nodejs for more detailed information.

Console.log(this) inside a function

The following code:

var apple = ‘red’;          // private variable in the wrapper function 
global.apple = ‘yellow’;    // property on the global object 

var foo = function () {

    var apple = ‘green’;
    console.log (this.apple);
}

foo();

returns yellow because an inner function cannot access the this value of any outer functions and in case of such inner functions it is standard behaviour of this to default to the global object (the window object in browsers).

Console.log(this) inside an object

The following code:

var apple = ‘red’;          // private variable in the wrapper function
global.apple = ‘yellow’;    // property on the global object 

var myObject = {

    orange: ‘orange’,
    print: function () {

    console.log (this.orange);
    console.log (this.melon);   
}}

myObject.print();

returns orange and undefined because it is myObject calling print. It returns undefined in relation to this.melon, because myObject has no property with the name melon.

Console.log(this) in module scope

The console.log command is a property on Node´s global object with the value of a function and therefore you would expect the following code

global.apple = ‘yellow’;
global.console.apple = 'yellow';

console.log(this.apple);

to return yellow as console.log() is the same as global.console.log(). This means that console.log() is called by the global object and therefore you would expect this to point to either global.apple or global.console.apple. However some of the functions on the global object is actually executed in module scope (see Global objects) and in this scope the designers of Node have chosen to set the value of this to the object exports, which is passed as a parameter to the function wrapping a Node module.

The above code therefore returns undefined because exports does not have a property with the name apple.

Community
  • 1
  • 1
rabbitco
  • 2,436
  • 3
  • 13
  • 33
  • Your analysis is strange. The value of `this` *inside* `console.log` (i.e. what `log` is bound to) has nothing to do with the `this` you are refer to in `this.apple`. – Felix Kling Feb 18 '16 at 15:59
  • @FelixKling: Well not that strange I think. See edited content above. If you set global.foo = function () {console.log(this.apple); and global.apple = 'green' and call foo(); it will return 'green'. So why shouldn't global.console.log(this.apple); also return 'green'. Because the designers of Node choose to let console.log run in module scope and in this scope to set the value of this to the exports object passed to the wrapper function as a parameter. Not easy to figure out if you do not already know it. – rabbitco Feb 19 '16 at 11:42
  • 1
    Well, you are focusing a lot on `console.log`, but the issue has nothing to do with it. The first two examples just demonstrate how `this` works in two specific situations: normal function call (globule object), object method (object). This is neither specific to Node nor `console.log`. The last example is especially confusing und shows that you haven't fully understood the situation (no offense) *"you would expect the following code [...] to return `yellow`"* I highly doubt this what the majority of devs would expect. In order to log `yellow`, `this` would have to refer to `console`... – Felix Kling Feb 19 '16 at 14:44
  • ... Did you really expect `this` inside a module to refer to `console`? *"This means that console.log() is called by the global object"* It's not even clear what that means. Objects don't call functions. Where `console` is defined and how/where it is called is completely irrelevant. The value you pass *to* `console.log` is evaluated *before* it is even passed to the function. Maybe this makes it easier to understand: `var foo = this.apple; console.log(foo);`. *"some of the functions on the global object is actually executed in module scope"* Again, it has nothing to do with the function ... – Felix Kling Feb 19 '16 at 14:53
  • ... you call. You can replace `console.log` with *any* other arbitrary function and you would get the same result. All that matters is that you are accessing `this` at the top level of a module. The fact that you are focusing so much on `console.log` and his it is defined is the reason why I think your analysis is strange and confusing. I don't think it would helpful for other visitors and you shouldn't accept your own answer in that case. – Felix Kling Feb 19 '16 at 14:55
  • @FelixKling: I much appreciate you efforts to explain these things to me. I can see that my failure to understand to some extent frustrates you - sorry about that :) This is what still eludes me: `global.console.hasOwnProperty(‘log’); // true;` `typeof global.console === ‘object’ //true;` `typeof global.console.log === ‘function’ // true`. So when calling `console.log(this.foo)`, `this` - without the use of `bind`, `apply`, `call`- is set within a function executing in the context of the object `global.console`. – rabbitco Feb 22 '16 at 12:58
  • You would therefore expect the value of `this` to be global.console based on the standard behaviour of `this`. Are you saying that `this` is set within another execution context before the function `console.log()` is called? If yes which execution context is that? – rabbitco Feb 22 '16 at 12:59
  • It seems you are confusing the `this` *inside* `console.log` with the `this` you are "passing" to it. They are completely independent. *Inside* `console.log`, `this` will refer to `console`. Imagine you have two function calls, such as `console.log(this.foo); somethingElse(this.foo)`. `this` won't magically refer to two different values, depending on which function you call. The value of `this` is completely independent of the function call. All the function gets is a value, i.e. the result of evaluating `this.foo`. Maybe I'm also just misunderstanding you. – Felix Kling Feb 22 '16 at 15:44
  • Again, you are very focused on the specifics of `console.log`, but I keep telling you that `this` has nothing to do with `console.log`. Which function you call or whether you even call a function at all is completely irrelevant. Lets simplify your example: `var foo = 0; global.foo = 1; exports.foo = 2; var bar = this.foo + 1;`. What is the value of `bar`? It's `3` because `exports === this`. – Felix Kling Feb 22 '16 at 15:46
  • And then came the moment of comprehension: `this` is an argument passed to `console.log()` and not part of its function body. Cannot believe that simple fact eluded me for so long. Thank you for sticking around to hammer home the point for me. – rabbitco Feb 22 '16 at 19:45
  • @Felix Kling Maybe naive to expect this thread is alive but I will try: why is console.log(this) outside of the function expression results in 'undefined'? how is it different from being called inside function expression? – esentai Sep 02 '20 at 16:26