Utilizing a class instance as a static property - a step-by-step guide

In my code, I am trying to establish a static property for a class called OuterClass. This static property should hold an instance of another class named InnerClass.

The InnerClass definition consists of a property and a function as shown below:

// InnerClass.gs

function InnerClass() {
  this.myProperty = 42;
}

InnerClass.prototype.myFunction = function() {
  return 43;
};

On the other hand, here is the OuterClass definition which only contains the static property:

// OuterClass.gs

function OuterClass() {
}
OuterClass.innerClass = new InnerClass();

However, when I attempt to invoke methods from the inner class, I encounter the following error message:

TypeError: Cannot find function myFunction in object [object Object].

// myScript.gs

function myScript() {

  console.log(OuterClass.innerClass.myProperty);   // 42.0
  console.log(OuterClass.innerClass.myFunction()); // TypeError: Cannot find function myFunction in object [object Object].

  var anotherInnerClassInstance = new InnerClass();
  console.log(anotherInnerClassInstance.myFunction()); // 43.0
}

Based on my analysis, it seems that the issue lies with the static property OuterClass.innerClass due to the following reasons:

  1. The constructor for InnerClass gets hoisted, while InnerClass.prototype.myFunction does not.
  2. During the instantiation of OuterClass.innerClass, it becomes incomplete because InnerClass.prototype.myFunction was not hoisted and therefore not attached to the created instance yet.

I wonder if there is a way to utilize a class instance as a static variable? It's worth noting that I have to work with prototype-based classes since I'm using Google Apps Script which relies on an outdated version of JavaScript.


If you are unable to reproduce this issue, here is the link to the Google Sheet causing the error: https://docs.google.com/spreadsheets/d/1Gxylcrbg9rWHGmc68CgHFmZqJ20E5-pLgA6fmHkxhAA/edit?usp=sharing

Additionally, here is a direct link to the script project: https://script.google.com/d/1V0FYrgiB3a4rTtvd9StyDtWAZ13AqlPl4rpgauCWSKk46UbcdIj9nqJC/edit?usp=sharing

Answer №1

To work around the issue, I opted for using a getter to lazily initialize the static property class instance. While it may be more verbose, this approach ensures that hoisting is not a concern.

// OuterClass.gs

function OuterClass() {
}

OuterClass.getInnerClass = function() {
  if (OuterClass.innerClass === undefined) {
    OuterClass.innerClass = new InnerClass();
  }
  return OuterClass.innerClass;
}

Answer №2

Parsing sequence

While the exact internal parsing process of script files in Rhino runtime remains unknown to me (assuming you utilized it instead of the newer V8 as indicated by your project), the migration guide states that file order does not impact Rhino's execution. It is implied that the codebase is concatenated before being parsed.

The process appears to rely somewhat on file creation order. Through simple closures examining call orders, I consistently replicated the issue in Rhino. Here is an illustration:

//bbbb.gs - created first

function Parent() {}
Parent.child = (function () {
  console.log("child assigned");
  return new Child();
})();

//aaaa.gs

function Child() {
  console.log("child constructor");
}
Child.myMethod = (function () {
  console.log("child static method assigned");
  return function () {}
})();

Child.prototype.myMethod = (function () {
  console.log("child method assigned");
  return function () {}
})();

function testCP() {
  console.log( Parent.child.myMethod );  
}

When running testCP, the expected call order in the log is observed:

https://i.stack.imgur.com/xj2of.png

Switching the code between files alters the logs (achieving the desired outcome):

https://i.stack.imgur.com/HjGNz.png

Alternate resolution

Your suggested lazy loading (singleton pattern) solution should suffice; however, incorporating dependency inversion for added flexibility and making InnerClass a strict dependency could be beneficial:

//bbbb.gs

function Parent() {}
Parent.setChild = function (child) {
  Parent.child = new child();
}

//aaaa.gs (unchanged code)

function testCP() {
  Parent.setChild(Child);
  
  console.log(Parent.child.myMethod); //function () {}
}

Now, the functionality operates smoothly regardless of the direction:

https://i.stack.imgur.com/tWwBU.png

Answer №3

After reading through the comments on this question, it seems that your code functions perfectly for me whether it's all in one script or spread across three separate files. The only adjustment I made (as mentioned by Diego) was swapping out console for Logger.

When I run the function myScript(), the logs generated in Apps Script are as follows:

[20-07-11 09:45:12:345 PST] 42.0

[20-07-11 09:45:12:346 PST] 43.0

[20-07-11 09:45:12:347 PST] 44.0

If you prefer to write your code initially in either pure JavaScript or TypeScript before transitioning to Apps Script, I recommend utilizing Clasp.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountered an error while attempting to access property 'someProperty' of an undefined value in a React application

Currently, I am developing an application that involves passing variable values within a Navlink using state from one component to another. Once the values are received, they need to be loaded into input fields and then upon clicking the submit button in t ...

What is the best way to show static files from the backend in a React application?

Currently, my setup involves a React application connected to an Express-Node.js backend through a proxy. Within the backend, there are some static files in a specific directory. When I make requests and embed the response link in the "src" attribute of an ...

explore the route with the help of jquery scrolling feature

Is there a way to implement scrolling functionality for tab headers similar to this demo? I have a list of elements within a div that I need to scroll like the tabs in the demo. I attempted to achieve this by setting the position of the inner elements to ...

How can I set up flat-pickr for date and time input with Vue.js?

I'm currently working with flat-pickr. Let's dive into the configuration part of it. I have a start date field and an end date field. My goal is to make it so that when a time is selected in the start date field, it defaults to 12h AM, and when a ...

Methods for updating the value of a `<select>` element in an AngularJS controller

Within my HTML code, I have a select element with options ranging from 1 to 10: <select id="selVal" ng-model="product.quantity" ng-options="o as o for o in quantityValues" ng-change="updateDelta(product.quantity, {{product.quantity}}, product.selec ...

Error: Vue.js application requires the "original" argument to be a Function type

I am facing an issue when trying to call a soap webservice using the 'soap' module in my Vue SPA. Strangely, I encounter an error just by importing the module. Despite my extensive search efforts, I have not been able to find a solution yet. Her ...

Guide to toggling the anchor tag functionality as a button with JavaScript

I am trying to dynamically enable or disable an anchor tag that is used as a button using JavaScript. Within a certain condition, I want to control the state of this button. Here is the specific button in question: <div class="row my-2 text-right& ...

Stopping a build programmatically in Next.js involves implementing specific steps that aim to halt

Is there a method to programmatically halt the execution of npm run build in Next.js when a specific Error occurs within the getStaticProps function? Simply throwing an Error does not seem to stop the build process. ...

Tips for Drawing Lines and Preserving Them When a Condition is Met

I am currently utilizing Node.Js in an attempt to outline objects within an image based on certain conditions being met. My goal is to draw lines around specific portions of the image received from an API response. Whenever the response includes the keywor ...

What could be causing my callback function to fail when used within a nested function?

I am currently utilizing the callback function in conjunction with Socket.io as shown below: loadData(callback) { var client = new SyncClient(this.socket, this.project); this.client = client; //From my data function client.on("connected", () => { ...

Regex: Enabling commas in the name of an Excel file

My JavaScript code is set up to extract data from an excel file. As a first step, I define a regular expression and assign it to a variable named regex var regex = /^([a-zA-Z0-9\s_!()\\.\-:])+(.xls|.xlsx)$/; Following this, there is s ...

The data type 'string[]' cannot be assigned to the data type '[{ original: string; }]'

I have encountered an issue while working on the extendedIngredients in my Recipe Interface. Initially, I tried changing it to string[] to align with the API call data structure and resolve the error. However, upon making this change: extendedIngredients: ...

Creating a pie chart with a legend in the realm of the dojo

Apologies for any language errors. I am looking to develop a web application where users can fill out a form and submit it to the server. The server will then respond with the requested data in JSON format. Using this data, I want to create a diagram and ...

Creating a simulation of a JavaScript callback within a C# host program

Currently, I am in the process of developing a c# application with an embedded web browser control. In this project, I'm facing a challenge where I need to call a C# method from JavaScript and pass a JavaScript callback using the dynamic technique exp ...

Looking to incorporate CGST and SGST into the Subtotal using JQuery?

I have come across this particular HTML Code in an Invoice: <tr> <td colspan="3" class="blank"></td> <td colspan="2" class="total-line">Subtotal Rs.</td> <td td class="t ...

Utilizing the Twitter API 1.1 to retrieve a list of tweets

As I work on updating my CMS component, I am incorporating integration with the Twitter API to fetch and showcase a list of tweets related to a user or search query. I have chosen to utilize the Twitter Restful API v1.1 as the 1.0 version is set to be disc ...

Why does getElementById work when getElementsByClassName doesn't?

I created a script with the purpose of hiding images one and two, while keeping image 3 visible and moving it into their place. The script functions correctly when using div Id's instead of div Classes. However, I prefer to use div classes for groupin ...

Assessing the validity of a boolean condition as either true or false while iterating through a for loop

I am facing an issue that involves converting a boolean value to true or false if a string contains the word "unlimited". Additionally, I am adding a subscription to a set of values and need to use *NgIf to control page rendering based on this boolean. &l ...

The AJAX request fails to trigger following the onbeforeunload event except when the page is manually refreshed

I'm currently working on implementing a solution for handling the onbeforeunload event to display a custom message when the user tries to close the browser tab. I want a prompt like: Are you sure you want to leave this page? (I don't want to use ...

Ways to identify when the scroll bar reaches the end of the modal dialog box

I have been working on a modal that should display an alert when the scrollbar reaches the bottom. Despite my efforts to research a solution, I am struggling to detect this specific event within the modal. The desired outcome is for an alert to pop up once ...