5

I'm writing an implementation of ES Harmony Symbol/Name in ES5. I'm going to use the name Symbol, but I want the browser to use any pre-existing Symbol it has in the case that it already exists (in future browsers). I want my code to be ES5 strict compliant and portable to other projects.

Here's one way (of many) to do what I want to in ES3/ES5 non-strict:

(function() {

    // If Symbol already exists, we're done.
    if(typeof Symbol != 'undefined') return;

    // This becomes global because it wasn't declared with var
    Symbol = function() {
        // ...
    };

})();

However, it's not ES5 strict compliant because Symbol is not defined explicitly.

Other ways to accomplish this would involve accessing the window object (window.Symbol = ...), but this is no good either because I don't want my code to assume its running in a browser environment.

How can this be done in ES5 strict?

Nathan Wall
  • 10,530
  • 4
  • 24
  • 47

4 Answers4

4

The answers posted by other users led me to a similar StackOverflow question which gave me the right terms to search in Google to find my answer. The solution:

I eventually was able to solve this problem with the use of an indirect eval, described here.

Using an indirect eval, covered in detail in the article linked above, executes code in global scope, as per the ES5 spec. I have chosen to go with this approach because it conforms to the ES5 spec and it allows the code to literally be dropped anywhere, even inside another function by a package manager, and still find the global object (which the other answers provided could not do).

The solution is something along these lines:

(function() {

    'use strict';

    var _global = (0, eval)('this');

    // If Symbol is already defined, there's nothing to do.
    if(_global.Symbol) return;

    _global.Symbol = function() {
        // ...
    };

})();

The key is to use an indirect eval to retrieve the global object (this in the context of an indirect eval).

This should work in anything which is ES5 compliant, including modern browsers and non-browser environments, as I had wanted.

Thanks for all the help everyone!

The only caveat is that it does seem a little hackish to have to use eval (that's bad enough) in this indirect way (now its worse) in order to access the global object. Should a global identifier or some other method to access the global object not be in the spec?

Community
  • 1
  • 1
Nathan Wall
  • 10,530
  • 4
  • 24
  • 47
  • It seems to be purposeful so "strict mode makes it impossible to accidentally create global variables" _(Converting mistakes into errors)_ and because "exposing the global object in browsers is a security hazard". _("Securing" JavaScript)_ https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/Strict_mode – Paul S. Oct 12 '12 at 11:17
1
'use strict';

var Symbol = 1; // try to comment this line and run the script again

var Symbol = (function(Symbol) {


    if(typeof Symbol != 'undefined') return Symbol;

    Symbol = function() {
        // ...
    };

    return Symbol;

})(Symbol);

alert(typeof Symbol);

http://jsfiddle.net/f0t0n/yATJW/

'use strict';

(function(g) { // g is a global context (this passed)

    if(typeof g.Array != 'undefined') {
        return;
    }

    g.Array = function() {
        // ...
    };

    g.Array.prototype.foo = function() {
        console.log('bar');
    };
})(this);

console.log(this.Array);​

http://jsfiddle.net/f0t0n/prwaP/

Eugene Naydenov
  • 7,165
  • 2
  • 25
  • 43
  • "Other ways to accomplish this would involve accessing the window object (window.Symbol = ...), but this is no good either because I don't want my code to assume its running in a browser environment." – Nathan Wall Oct 11 '12 at 22:30
  • It's a nice attempt, but I don't think it really works due to `var` hoisting. Replace `Symbol` with something that really does already exist, like `Array`, to see what I mean: http://jsfiddle.net/6jhFr/ – Nathan Wall Oct 11 '12 at 22:47
  • This looks like an attractive possibility. I have a couple questions. (1) Is this really ES5-strict compliant? Will `this` by default be the global object in ES5 if it's run from the global scope? (2) Will this work in non-browser environments (like Node.js)? Does `this` in the global scope in Node refer to the global object? Is that behavior part of the ES5 spec? Thanks! – Nathan Wall Oct 11 '12 at 23:21
  • No, in Node `this` does not reffer to the `global` object. In node you can explicity use `global` in my example instead of `window` and `this`. In Node `console.log(this, this === global, this === module);` will output `{} false false` – Eugene Naydenov Oct 11 '12 at 23:32
  • I installed Node and tested it out. `this === global` is `true` for me. +1, though. Thanks for the help. Ultimately not the solution I chose, but it helped point me where in some good places. – Nathan Wall Oct 12 '12 at 03:50
  • Strange, my node is saying that `this` is not `global`. :) – Eugene Naydenov Oct 12 '12 at 14:34
  • I discovered it depends if you're running the code in a module or not. If in a module, `this` is `module`, not `global`. However, the method I described still runs code in global scope in Node: `(0, eval)('this')` is `global`. – Nathan Wall Oct 12 '12 at 14:37
1

Why does it need to be in an anonymous function?

// assuming global context
if (typeof this.Symbol === 'undefined') {
    this.Symbol = function () {
        // ...
    };
}

or in a function, pass this as described here

(function (t) {
    if (typeof t.Symbol === 'undefined') {
        t.Symbol = function () {
            // ...
        };
    }
})(this);
Community
  • 1
  • 1
Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • 2
    +1, Thanks. The link you posted, and a couple Google searches which it inspired eventually led me to the answer I was looking for. – Nathan Wall Oct 12 '12 at 03:46
1

How about you pass in the global scope that you want Symbol to be added to?

(function(global){
 if(typeof global.Symbol != 'undefined') return;

    // This becomes global because it wasn't declared with var
    global.Symbol = function() {
        // ...
    };

})(window);  

this adds it to window, but could be some other scope or var.

mrk
  • 4,999
  • 3
  • 27
  • 42
  • I wrote "global scope" but doesn't necessarily have to be a global variable, depending on where/how the wrapper is called. – mrk Oct 11 '12 at 22:35
  • I considered that, and it may be my best option, but it's not ideal because it requires an edit to the file for non-browser environments. – Nathan Wall Oct 11 '12 at 22:35
  • I'm quite familiar with AMD (the model requirejs uses), as I've written an implementation myself for work. How does that solve my problem? You just mean the ability to use exports? I want to write something that can literally be dropped anywhere. I don't want to depend on a module model. – Nathan Wall Oct 11 '12 at 22:49
  • Yep, I meant ability to use exports. You want Symbol to get defined somewhere and picked and used elsewhere. Creating global vars can be bad practice, so if you agree to follow some pattern/impl, like requirejs, then at least there's a documented, semi-standardish way you are following so others that may have to maintain the code don't need to noodle thru spaghetti code. – mrk Oct 11 '12 at 23:02
  • But I don't really want to define a global variable. I want to provide a shim for something that will be available in ES6 so that other libs can use it today. In ES6 they won't have to `require` it. As a shim, it shouldn't have to be `require`d. – Nathan Wall Oct 11 '12 at 23:16