3

In a nutshell, I'm trying to define a variable in the window which stays in sync with myValue within the class. So window.myGlobalVarName should always equal myValue within the class.

I'm using Object.defineProperty to try to prevent the variable from being re-assigned in the window.

However, whenever get() is called, this is the window object, not this within the class. Am I missing something obvious here? There's plenty of examples of using get() to return values within classes using this - what am I doing differently?

class MyClass {
  constructor() {
    this.myValue = 1
    this.globalVarName = "myGlobalVarName"
    this.setGlobalObject()
  }

  setGlobalObject() {
    if (typeof window !== "undefined") {
      Object.defineProperty(window, this.globalVarName, {
        configurable: false,
        enumerable: true,
        get() { return this.myValue } // "this" is actually "window"
      })
    }
  }
}

I can resolve this returning the value of a function, but could I be doing this a better way?

  setGlobalObject() {
    const getMyValue = () => this.myValue // Return current value of this.myValue
    if (typeof window !== "undefined") {
      Object.defineProperty(window, this.globalVarName, {
        configurable: false,
        enumerable: true,
        get() { return getMyValue() } // Returns the actual value from the class
      })
    }
  }
Oli
  • 639
  • 5
  • 8
  • 2
    But what if there is more than one instance of the class using the same name for the global? The `Object.defineProperty` call will throw. Constructor functions (whether created the old way or via `class`) are primarily for creating factories of similar objects. If you just want a singleton object, you can create it directly instead. (Separately: It's best to avoid globals, which is much easier these days thanks to proper modules, which are supported in modern browsers and by bundlers like Webpack and RollupJS that can create bundles for older browsers.) – T.J. Crowder Dec 02 '19 at 12:35
  • 1
    Good observation, in my actual code I have it wrapped in a try/catch, and also check if the variable has been defined already - I just left this out to keep the code example cleaner. – Oli Dec 02 '19 at 12:36

1 Answers1

2

However, whenever get() is called, this is the window object, not this within the class.

That's how most functions and methods work in JavaScript, this is set by how they're called, not where they're defined. Details in the answers to this question. Your get function is the same, because it's a traditional function (written with method syntax).

To get this from where the function is defined, use an arrow function:

class MyClass {
  constructor() {
    this.myValue = 1
    this.globalVarName = "myGlobalVarName"
    this.setGlobalObject()
  }

  setGlobalObject() {
    if (typeof window !== "undefined") {
      Object.defineProperty(window, this.globalVarName, {
        configurable: false,
        enumerable: true,
        get: () => this.myValue  // ***
      })
    }
  }
}

Arrow functions don't have their own this, they close over the this in the scope where they're defined, exactly as though it were a variable they were closing over. The setGlobalObject above is essentially the same as this pre-ES2015 function:

// Pre-ES2015 example for comparison
MyClass.prototype.setGlobalObject = function setGlobalObject() {
  var obj = this;                              // ***
  if (typeof window !== "undefined") {
    Object.defineProperty(window, this.globalVarName, {
      configurable: false,
      enumerable: true,
      get: function() { return obj.myValue; }  // ***
    })
  }
};

(Arrow functions also don't have their own arguments object, new.target, or super.)

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639