Skip to content

Native class of global changed in Node v7 #9274

Description

@kgryte
  • Version: 7.0
  • Platform: Darwin local 14.5.0 Darwin Kernel Version 14.5.0: Thu Apr 21 20:40:54 PDT 2016; root:xnu-2782.50.3~1/RELEASE_X86_64 x86_64

In Node.js version <= 6.x.x,

var nativeClass = Object.prototype.toString.call( global );
// returns '[object global]'

In Node.js version 7.0,

var nativeClass = Object.prototype.toString.call( global );
// returns '[object Object]'

Based on the changelog, this is not listed as a notable and/or breaking change. Why did the internal class change and is this a permanent/intentional change?

Background: one reason why this is a notable change is that environment sniffers (i.e., packages which try and detect if the runtime is Node versus, say, a browser) commonly use the internal class of global as means to identify a Node environment.

Activity

  1. MylesBorins commented on Oct 25, 2016

    @MylesBorins
    Contributor

    Digging in

    /cc @nodejs/ctc

  2. jasnell commented on Oct 25, 2016

    @jasnell
    Member

    I'm not sure if this is a change that we made or if it came from the v8 update.

  3. addaleax commented on Oct 25, 2016

    @addaleax
    Member

    I can start bisecting this

  4. added
    v8 engineIssues and PRs related to the V8 dependency.
    on Oct 25, 2016
  5. bnoordhuis commented on Oct 25, 2016

    @bnoordhuis
    Member

    It's a change that comes from V8. We don't muck around with the global object's prototype. Example with v6:

    > vm.runInNewContext('Object.prototype.toString.call(this)')
    '[object global]'
    

    With v7:

    > vm.runInNewContext('Object.prototype.toString.call(this)')
    '[object Object]'
    
  6. ofrobots commented on Oct 25, 2016

    @ofrobots
    Contributor

    /cc @nodejs/v8

  7. MylesBorins commented on Oct 25, 2016

    @MylesBorins
    Contributor

    I can confirm that this change came in somewhere between 8a24728...96933df

    which leads me to believe this came with the V8 upgrade

  8. addaleax commented on Oct 25, 2016

    @addaleax
    Member

    Btw, this can be fixed with global[Symbol.toStringTag] = 'global'. I’m not sure whether that should be applied to any of the objects generated by the vm module, though.

  9. bnoordhuis commented on Oct 25, 2016

    @bnoordhuis
    Member

    Can, but I don't know if we should. If the use case is feature detection, then sniffing for process seems like a better choice in the first place.

  10. kgryte commented on Oct 25, 2016

    @kgryte
    Author

    @bnoordhuis A use case is feature detection. Another time when the internal class matters is when type checking or handling special cases; e.g.,

    var toString = Object.prototype.toString;
    
    function foo( bar ) {
       var nativeClass = toString.call( bar );
       if ( nativeClass === '[object global]' ) {
         // ...do something...
       } else if ( nativeClass === '[object process]' ) {
         // ...do something...
       } else {
         // ...do something else
       }
    }

    And specifically re: environment detection: usually checking the internal class is combined with other checks including process in order to more confidently claim an environment is Node.

    Regardless, the change introduces an (unintended) inconsistency between Node versions.

  11. addaleax commented on Oct 25, 2016

    @addaleax
    Member

    I think I agree; if something like this is changed, it should be conscious decision we make.

    (I also agree that sniffing for process seems like the best way to detect Node.)

  12. mscdex commented on Oct 25, 2016

    @mscdex
    Contributor

    I would go a step further and say that checking that process.versions.node exists (checking each property in the path exists of course) and is a non-empty string is all that is really needed.

  13. bnoordhuis commented on Oct 25, 2016

    @bnoordhuis
    Member

    @kgryte My point is more that checking for this particular behavior is not a very good indicator of a node.js runtime because you'd see the same behavior in, say, plv8, or any V8-backed runtime that doesn't override the default.

  14. kgryte commented on Oct 25, 2016

    @kgryte
    Author

    Just to clarify and keep things focused: this issue is not about the right way to detect a Node environment, but about the fact that an unintended breaking change was introduced in Node v7 due to how V8 reports the internal class of global.

  15. 15 remaining items

  16. hashseed commented on Oct 28, 2016

    @hashseed
    Member

    I think this is the change that caused this:
    https://codereview.chromium.org/2080243003

    V8 is following the ES6 spec here, which does not leave much room for interpretation:
    https://tc39.github.io/ecma262/#sec-object.prototype.tostring

    In d8, we indeed define the toStringTag for the global object to be "global". In Chrome, window[Symbol.toStringTag] is set to "Window". Maybe this is the path node should take as well?

  17. sam-github commented on Oct 28, 2016

    @sam-github
    Contributor

    @hashseed Which path do you suggest node take, setting it to global, or setting it to Window?

  18. hashseed commented on Oct 28, 2016

    @hashseed
    Member

    To global to mimic the old behavior.

  19. dnalborczyk commented on Oct 28, 2016

    @dnalborczyk
    Contributor

    This proposals https://github.com/tc39/proposal-global (stage 3) intention is to unify the 'global' object independent of the runtime environment. The name for such an object is 'global' (the same as node.js is currently using, as supposed to self, System.global etc.). Therefore, I suspect that if the proposal is being accepted, and browsers implement it, global.toString() will likely return - according to the spec - '[object Object]' as well.

    If we revert this now to mimic pre-v7-behavior, we might need to revisit it again in the future to revert the reverted, if we want consistency between environments.

    @ljharb Is that assumption correct?

  20. ljharb commented on Oct 28, 2016

    @ljharb
    SponsorMember

    The JS spec will continue to not contain or require (nor prohibit, it must be noted) that global have a Symbol.toStringTag defined.

    So, despite my belief that there should be no such behavior, I must still acknowledge that there's no spec compliance issue here, either way. Browsers may and will continue to return [object Window], and node can choose to return whatever it wants.

    I also don't think there's a compatibility issue here either way, because with the advent of ES6 and Symbol.toStringTag, nobody should ever be relying on the output of Object.prototype.toString ever again for anything but debugging, and to do so is to make your code brittle.

  21. dnalborczyk commented on Oct 28, 2016

    @dnalborczyk
    Contributor

    Thanks @ljharb

    I should have been more specific, I meant the spec regarding https://tc39.github.io/ecma262/#sec-object.prototype.tostring referred above.

  22. ljharb commented on Oct 28, 2016

    @ljharb
    SponsorMember

    @dnalborczyk that spec says that if global[Symbol.toStringTag] === 'global', that Object.prototype.toString.call(global) should return '[object global]'

  23. kgryte commented on Oct 28, 2016

    @kgryte
    Author

    @ljharb Never using Object#toString is an unrealistic expectation. Far too much code which already exists in the wild uses Object#toString to check for a whole variety of things, not just '[object global]'. And this is not going to be fixed or stopped. That this would lead to "brittle" code should have been (and probably was) considered during the specification phase.

    My understanding is that one motivation, and intended byproduct, of Symbol.toStringTag was to allow people to customize the output of Object#toString and, thus, enable another means of identifying user defined classes. For example,

    if ( Object.prototype.toString.call( foo ) === '[object MyVerySpecialClass]' ) {
      // I've identified an instance of MyVerySpecialClass!
    } else {
      // Not an instance of MyVerySpecialClass
    }
    

    That this feature can also be used for ill is not necessarily a reason to avoid using the behavior. And people will use this behavior for identifying their own custom class instances, just as Chrome sets window[Symbol.toStringTag] = "Window". That Node may want to "customize" its own environmental global seems a realistic possibility.

    Truth is, similar to modifying a built-in prototype (that you can do so, by your definition, would make using prototypical methods "brittle" and to be avoided whenever possible, including for builtins!) modifying a foreign object's toStringTag should not be encouraged. But...this should not stop those who define the environment or introduce their own objects from customizing the output of Object#toString, particularly if it means easier identification of said objects, or, in this case, maintaining backward compatibility.

  24. hashseed commented on Oct 28, 2016

    @hashseed
    Member

    I think we can safely assume that pre-ES6 code that assumes String(global)=="[object global]" won't interfere with toStringTag, since the latter is also an ES6 feature.

  25. ljharb commented on Oct 28, 2016

    @ljharb
    SponsorMember

    @kgryte none the less, it's the reality since ES6 erased the unforgeability of Object#toString.

    Certainly the case you're describing - as a publicly available opt-in, is a fine use of it. But using it as a brand check is no longer reliable, no matter how much code is doing it.

    @hashseed except that in newer environments, global = { [Symbol.toStringTag]: 'global' }; can make that code go down unintended paths.

  26. hashseed commented on Oct 28, 2016

    @hashseed
    Member

    Sure. We would have to weigh cases that rely on the old behavior against ones that use toStringTag for other purposes.

  27. trusktr commented on Mar 28, 2018

    @trusktr
    Contributor

    Just noting something curious, but I'm in Node 8 and some functions that I create with new Function appear as [object global] in the console, and .bind() method is missing, which is strange.

    Look at the strange output I see when debugging:

    screenshot 2018-03-27 at 11 27 25 pm

    Not sure how to reproduce, otherwise I'll open a new issue. Made an issue with repro: #19651

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v8 engineIssues and PRs related to the V8 dependency.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions