6

I'm trying to define the global object in JavaScript in a single line as follows:

var global = this.global || this;

The above statement is in the global scope. Hence in browsers the this pointer is an alias for the window object. Assuming that it's the first line of JavaScript to be executed in the context of the current web page, the value of global will always be the same as that of the this pointer or the window object.

In CommonJS implementations, such as RingoJS and node.js the this pointer points to the current ModuleScope. However, we can access the global object through the property global defined on the ModuleScope. Hence we can access it via the this.global property.

Hence this code snippet works in all browsers and in at least RingoJS and node.js, but I have not tested other CommomJS implementations. Thus I would like to know if this code will not yield correct results when run on any other CommonJS implementation, and if so how I may fix it.

Eventually, I intend to use it in a lambda expression for my implementation independent JavaScript framework as follows (idea from jQuery):

(function (global) {
    // javascript framework
})(this.global || this);
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299

3 Answers3

5

this is in no way relevant to scope.

(function(){
    (function(){
        (function(){

            (function(){
            alert( this ); //global object
            })()

        }).bind({})()
    }).apply({})
}).call({})

this is only resolved during the call time of the function and comes down to few simple rules.

  1. If the function is called as a property of some object, then that object will be this inside the function
  2. If the function is called as is, this will be undefined so under non strict mode it will be the global object
  3. If the function is called with .call/.apply then this is explicitly set by yourself.

So as you can see, it would fall under rule #2, which resolves to undefined. And since there is no "use strict";:

set the ThisBinding to the global object

Edit: I have now ran some quick tests in RingoJS and they infact put the "global object" inside the actual global object (as defined by standards) which is ModuleScope. Just because the actual global object in most js implementations has Object and String and so on, doesn't make an object global if it has those objects under it as well. The reason you can access String and Object in RingoJS is because they put them into the ModuleScope prototype:

var logs = require('ringo/logging').getLogger("h");

logs.info( Object.getPrototypeOf( this ) === this.global );
//true

Further proof that ModuleScope is the actual global object:

this.property = "value";
logs.info( property );
//"value"

So nothing is gained from this kind of trickery, it doesn't fix anything:

function injectGlobal(){
globalProperty = "value"; // "use strict" would fix this!
}

injectGlobal()

logs.info( globalProperty );
//"value"

Rant over, this refers to the actual global object already according to the rules given earlier in this post. this.global is not the real global object as defined by standards, it's just a container.

Additionally you could emulate this behavior in browsers:

Consider scopehack.js

this.global = window.global || top.global || {};

Consider main.html:

<script src="scopehack.js"></script>
<script>
this.global.helloWorld = "helloWorld"; //"global scope"
this.helloWorld = "helloWorld" //"ModuleScope"
</script>

<iframe src="module.html"></iframe>

And finally a "module" module.html:

<script src="scopehack.js"></script>
<script>
    with( this.global ) { //poor mans RhinoJS scope injection, doesn't work for writing
        console.log( helloWorld ); //"global scope" - "helloWorld"
        console.log( this.helloWorld ); //"ModuleScope" undefined
    }
</script>

Which one is an actual global object in both module.html and main.html? It is still this.

TLDR:

var obj = {
"String": String,
"Object": Object,
.....
};

Does not make obj the global object.

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • That's good, but I already knew that. It's the reason I am using `this` and not `window` in my code - it works on all JavaScript implementations. However, in RingoJS and node.js the `this` pointer doesn't point to the `global` object. It points to the `ModuleScope`. Hence I had to write `this.global || this`. It's not really the answer I expected. Sorry. =( – Aadit M Shah Nov 26 '11 at 17:57
  • @AaditMShah, right. `this.global` is just as good as `this` because if `global` is a global variable then `this` must be global as well for `this.global` to work. You could just use `global` anywhere and it will resolve to global object unless it is overwritten locally... I don't really understand then what your question is :( – Esailija Nov 26 '11 at 18:02
  • BTW, when I said that "The above statement is in the global scope. Hence in browsers the `this` pointer is an alias for the `window` object.", I was trying to convey that it's not written inside a function and hence it can only be the `window` object (rule #1 and #3 don't apply). Rule #2 is also partially incorrect in my case because the `this` pointer is not within a function. When we use `this` in the global context, it's always the `window` object in browsers and nothing else. – Aadit M Shah Nov 26 '11 at 18:04
  • @Esailija - It's really difficult to explain. See, `this.global` is a local variable which points to the `global` object, and `this.global !== this` will return `true` because in RingoJS and node.js the `this` pointer never points to the `global` object (like you said in rule #2). Instead it points to the `ModuleScope`. On the `ModuleScope` there's a property called `global` which points to the `global` object. There's no way to access the `global` object directly like in browser environments. – Aadit M Shah Nov 26 '11 at 18:08
  • @AaditMShah, inside a module you could try doing: `var a = (function(){return this;})();` Is `a` still the module object? It should be either undefined or global, according to javascript spec. – Esailija Nov 26 '11 at 18:24
  • @Esailija - It'll still be the `ModuleScope` object in RingoJS and node.js as these are CommonJS implementations. They don't create their own JavaScript engine. RingoJS uses Mozilla's Rhino engine, and node.js uses Google's V8 engine. However they build on top of these and to solve the global variable problem in JavaScript, they created the `ModuleScope` object. It prevents accidental JavaScript global variable leaks. – Aadit M Shah Nov 27 '11 at 04:41
  • @Esailija - I have to disagree with you on this case. The `ModuleScope` and the `global` object are not the same. The reason is that a single program may have multiple modules, each having their own `ModuleScope`. However, there's only one `global` object in the entire program. Consider two modules - `test.js` and `ctrl.js`. If I define a variable in `test.js` as follows: `t = "local";` and print it in `ctrl.js` as follows: `print(t);` then it will print `undefined`. However, if I define it as `global.t = "global";`, then it will print `global`. Hence they are different. – Aadit M Shah Nov 30 '11 at 07:37
  • @Esailija - You shall confess that you are deceived. This [wiki page](http://ringojs.org/wiki/Modules_and_Scopes "Modules and Scopes - Wiki - RingoJS") should help clarify your doubts about modules and scopes in RingoJS. – Aadit M Shah Nov 30 '11 at 07:53
  • @Esailija - The difference between RingoJS and browsers is that in browsers there's just one top-level scope which also happens to be the `global` object. However, in RingoJS there are multiple top-level scopes - `ModuleScope`, and only one `global` object. Each `ModuleScope` has it's internal `[[proto]]` property set to the `global` object, as you already said. I want to define the `global` object, not the top-level scope. They are not the same. I'll try to explain it in more detail. – Aadit M Shah Nov 30 '11 at 08:02
  • @AaditMShah, that's exactly what happened in the iframes as well but you wouldn't go say that the distinct top level scopes weren't actual global objects. You can define global variables in one iframe and they won't be seen in another. However, defining something in the `fake` global object will be seen in another iframe. Just because it is seen in all the iframes and the actual global objects are short lived, doesn't change anything. You may test the iframe example and it works exactly the same way as RingoJS for the purpose. – Esailija Nov 30 '11 at 13:29
  • @AaditMShah, but it doesn't matter it's just semantics. I understand that you need the object that is the same and visible in all modules (this is not the definition of global object in javascript). The specs for CommonJS are not loading for me at the moment, so I cannot really tell if `"String" in Object.getPrototypeOf(this)` is a good test. `Object.getPrototypeOf(this)` will return the all-visible object for me in RingoJS but I cannot speak for all CommonJS because the specs don't load. – Esailija Nov 30 '11 at 13:41
  • @Esailija - I understand what you're trying to say, but you didn't understand my question. Every single web page is like a new program in JavaScript. It has it's own `global` object with its own separate core objects like `Object`, `Array`, `Function`, etc. It's a different issue that these "programs" can communicate with each other. However, what I'm trying to define an alias for is the `global` object within each "program". Hence for a page with an iframe, I'll be defining the `global` object twice. Once for the top page, and once for the page in the iframe. Once for all modules in RingoJS. – Aadit M Shah Nov 30 '11 at 16:45
  • @AaditMShah, the scopehack.js defines a common pseudo global object for all the iframes as the test shows, just like RhinoJS defines it internally but in a more sophisticated way that is not apparent right away. Anyways in this kind of implementation `Object.getPrototypeOf(this)` returns the `this.global` pseudo global object. But I cannot say if this is in CommonJS standards at all. Their page doesn't load for me. – Esailija Nov 30 '11 at 16:50
2

Implementation independant version is not trivial

(function (global) {
    // javascript framework
})(
   this && this.global || // ringoJS
   typeof root !== "undefined" && root || // node.js
   typeof global !== "undefined" && global || // more node.js
   typeof GLOBAL !== "undefined" && GLOBAL || // more node.js
   typeof window !== "undefined" && window || // browsers
   this // either undefined or some global default?
);

Your going to have to hard code in feature detection for every environment.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • That's good, but it doesn't directly answer my question. In addition, `this || this.global` is logically equivalent to your expression. I already tested it. – Aadit M Shah Nov 30 '11 at 07:25
  • 1
    `root` is the actual global object in Node. –  Nov 30 '11 at 12:35
  • 1
    @AaditMShah no it's not. `this || this.global` will return `this` instead of `this.global` – Raynos Nov 30 '11 at 13:00
  • @Raynos - Ah, yes. My mistake. I typed it out wrong. It's written correctly in my question: `this.global || this`. Thank you for pointing it out for me. – Aadit M Shah Nov 30 '11 at 13:12
  • @BrandonBenvie - Thank you for the information. It's the only new info I received on my question. However, is `global` the same as `root`? – Aadit M Shah Nov 30 '11 at 13:15
  • 1
    `root === GLOBAL` should always be true. `root === global` will depend on what context you're currently in. In the REPL for example if you run in the global context then it's true, otherwise it's not. –  Nov 30 '11 at 13:18
  • 1
    It's actually a pretty important distinction because it changes what happens when you do things like extend native prototypes. If you're not in the global context, `root.Object` will return undefined, but if you are then it will point to the `Object` that houses the `Object.prototype` that all the Node objects inherit from. `this` also can point to something completely arbitrary, not `root` or `global` or `GLOBAL`, or it could point to `root`. –  Nov 30 '11 at 13:24
  • @AaditMShah `this.global || this` fails if `this` is `undefined` – Raynos Nov 30 '11 at 13:29
  • @benvie what's the difference between GLOBAL, global and root. Where is this documented? Maybe discuss it in #node.js – Raynos Nov 30 '11 at 13:30
  • @Raynos - Actually, I'll be executing `this.global || this` in the top-level scope. Hence `this` will always point to the top-level scope's activation object. That will be equivalent to the `window` object in browsers and the `ModuleScope` in RingoJS. I'm not so sure about node.js because @benvie got me confused. – Aadit M Shah Nov 30 '11 at 16:37
  • @benvie - Perhaps you could explain or link us to difference between the `root`, the `global`, and the `GLOBAL` objects in node.js like @Raynos asked? It'll be really helpful. – Aadit M Shah Nov 30 '11 at 16:39
  • 1
    Ah sorry we ended up finishing this on IRC. The difference comes in when your module isn't running in the global context. For example, using vm.runInNewContext will have you running in a mostly normal context with all the global objects, but `global` will actually point to a sandbox. This happens if you start the REPL without specifying that it should run in the global context and also if you have the flag set to run all modules in their own context. In this case `root` may be available to you, but `root.Object` will return undefined, where it will reference Object if you're not sandboxed. –  Nov 30 '11 at 19:44
  • 1
    It's even worse if you have a script then was run using vm.runInContext, in which case `global` could be any arbitrary object. Currently node's implementation of contexts is problematic because it simply copies all the property references from the given sandbox object to a new object, so there's a lot of potential holes. V8 isolates are being implemented now which should improve this, as they provide true separation of environment. –  Nov 30 '11 at 19:50
  • 2
    This isn't documented anywhere as far as I know, but a large portion of Node's code is implemented in JavaScript so it's pretty approachable. The relevent bits of code are https://github.com/joyent/node/blob/master/lib/module.js#L394 https://github.com/joyent/node/blob/master/src/node.js#L125 https://github.com/joyent/node/blob/master/src/node_script.cc#L112 –  Nov 30 '11 at 19:59
  • @benvie - So how do you propose we define the `global` object in node.js? Will @Raynos' script work for node.js in the global context and other contexts as well? – Aadit M Shah Dec 01 '11 at 16:32
  • @AaditMShah it depends, which order do you want. Do you want to favour root over global? What kind of global do you want. The real issue here is that theres multiple different types of globals and _you_ need to decide which one you want – Raynos Dec 01 '11 at 17:19
  • @Raynos - I'm really confused between `root` and `global`. I believe that `root` and `GLOBAL` are always the same right? So all I have to do is decide whether I wish to use `root` or `global`. I still don't get the difference between the two. – Aadit M Shah Dec 01 '11 at 17:37
  • @AaditMShah root and global are the same unless you run code in a sandbox. Within a sandbox _you_ can define global do be whatever you want. – Raynos Dec 01 '11 at 17:38
  • @Raynos - So I guess it's always better to use `root`. I'm looking for the object which expresses `true` for the following test: `object.hasOwnProperty("Object")`. I believe this will be `root` unless it's not executed in the global context. How do I handle that? – Aadit M Shah Dec 01 '11 at 17:42
  • @AaditMShah you don't sandboxes should have the ability to not give you global scope. Your program simply doesn't work in those sandboxes – Raynos Dec 01 '11 at 17:59
  • Time to move this to a chat room folks. Thanks. – Kev Dec 01 '11 at 18:12
1

After reading Esailija and Raynos' answers I understood that my code this.global || this will not work for all cases in node.js; and that it may even fail in browsers if a variable called global already exists in the global scope.

Esailija pointed out that this.global is not really the global object, stating instead that this is the global object in RingoJS; and although I understand his arguments, for my purposes I require this.global and not this.

Raynos suggested that I hard code feature detection for every CommonJS environment. However since I'm currently only supporting RingoJS and node.js, I only need to test for global and window. Hence I decided to stick with this.global || this.

Nevertheless, as I said before this.global || this doesn't work for all cases in node.js as I understood from benvie's comments. In the node.js REPL I realized that I require this and not this.global. However, this.global || this expresses this.global. In a node.js module I require this.global and not this. However, it expresses this since this.global is undefined. Hence to solve this problem I finally decided to use the following code:

(function (global) {
    // javascript framework
})(typeof global !== "undefined" && global || this);

The reason I'm using this code is because in node.js modules this.global is undefined. Hence we must use global directly. Thus we use typeof global !== "undefined" && global to get the global object in both RingoJS and node.js; and we use this as the global object in browsers (window) and as a default fallback.

Note: I didn't provide any logic for finding the global object in the node.js REPL because I don't believe that my framework will be used directly within the REPL anyway. However, writing the logic to find it should be fairly trivial once one understands the complications of finding the global object in node.js as benvie pointed out. I know that I don't.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299