Utils.globalFunction()
Creating a global function is easy in JavaScript. Any function declared outside a class definition is a global function. This rule changes when we are writing JavaScript modules. Function declared inside a module are local to that module and not global. If we want to define a global function from within a module, we need to attach the function to the "global" object. Unfortunately this "global" object has a different name depending on whether our JavaScript code is executing in the browser or in node.js. The following function can be used to create a global function in both environments.
Utils.mjs
function info(msg) { if (!!window.console && !!window.console.info) {console.info(msg);} } class Utils { /****************************************************************************** FUNCTION: globalFunction(name, func) This function will add the global function passed. Example: Utils.globalFunction("isConstructor", function(fn) {return typeof fn !== 'function' && !!fn.prototype;}); Now in any context you can call... var b = isConstructor(someFunctionName); @param {string} name Name of method to attach. @param {function} func Function object to use as the method. @returns true if the method was added. False otherwise. @type boolean @author Richard Lesh @version 2024/06/30 ******************************************************************************/ static globalFunction(name, func) { if (window === undefined) { if (global[name] === undefined) { info("Adding global method: " + name); global[name] = func; return true; } } else { if (window[name] === undefined) { info("Adding global method: " + name); window[name] = func; return true; } } info("Existing global method: " + name); return false; } }
This static class method allows you to define and name a function that will be attached to the global
object in node.js or the window
object in the web browser. If the name specifies an object that already exists, it will not be added leaving the existing definition in place. This is handy if you are writing polyfills. Polyfills are functions that are not currently implemented in the language or environment but will probably be in the near future. It also helps when a function is being rolled out as a standard but support in browsers is uneven. Our globalFunction()
allows a polyfill function to be added only if it is not already implemented.
For example, here are global methods for string trimming and creating repeated strings.
Utils.mjs
/****************************************************************************** This function returns a copy of the string with the whitespace and control characters trimmed from the beginning and end of the string. If the passed object is not a string, then it is first stringified then trimmed. Undefined objects return the empty string. @param {String} s String to trim. @param {Boolean} trimLeft True to trim left side. Default true. @param {Boolean} trimRight True to trim right side. Default true. @returns Trimmed string. @type {String} @author Richard Lesh @version 2024/07/15 ******************************************************************************/ Utils.globalFunction("trim",function trim(s, trimLeft = true, trimRight = true) { var start = 0, end = 0, ws = "\t\n\x0b\f\r \x85\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\ufeff"; if (isDefined(s)) { s = String(s); // Force primitives and objects to be Strings } else { return ""; } end = s.length - 1; if (!!trimLeft) {while (ws.indexOf(s[start]) > -1) { ++start; } } if (!!trimRight) {while (end > start && ws.indexOf(s[end]) > -1) { --end; } } return s.slice(start, end + 1); }); /****************************************************************************** This function returns string that is the concatenation of the passed string repeatedly. Uses a fast power approach. @param {String} s String to repeat. @param {number} n Number of times to repeat the string. @returns Replicated string. @type {String} @throws @requires JavaScript @author Richard Lesh @version 2013/04/10 ******************************************************************************/ Utils.globalFunction("repeat",function repeat(s, n) { var nMask, sDiff = "", sBase2 = n > 0 ? String(s) : ""; for (nMask = n; nMask > 0; nMask >>>= 1) { if (nMask & 1) { sDiff += sBase2; } sBase2 += sBase2; } return sDiff; });
And here is how you can use these methods once defined...
import Utils from "Utils.mjs"; let s = " \n\t\rABCDEFG \n"; var trimmed = Utils.trim(s); var leftTrimmed = Utils.trim(s, true, false); var rightTrimmed = Utils.trim(s, false, true); let separator = Utils.repeat("=", 80);
Utils.addMethod()
The next method can be used to add methods to existing classes that you cannot modify, i.e. ones that are already defined by JavaScript or the environment. For example, maybe we want to add some additional methods to the String
class or the Array
class. This is helpful for polyfills of methods that are new to the standards but not widely implemented yet.
Utils.mjs
/****************************************************************************** FUNCTION: addMethod(name, func) This function will add the method to the class prototype. Used on class constructor functions. This is equivalent to defining an instance method in a JavaScript class definition. Example: String.addMethod("trim",function() {return this.replace(/^\s+|\s+$/g,"");}); Now on any String you can call trim()... var s = someString.trim(); @param {string} name Name of method to attach. @param {function} func Function object to use as the method. @returns true if the method was added. False otherwise. @type boolean @author Richard Lesh @version 2024/06/30 ******************************************************************************/ function isConstructor(fn) { return typeof fn !== 'function' && !!fn.prototype; } Function.prototype.addMethod = function (name, func) { if (isConstructor(this)) { throw new TypeError("addMethod(" + name + ") can only be applied to class constructors!"); } if (this.prototype[name] === undefined) { info("Adding method: " + this.name + "." + name); this.prototype[name] = func; return true; } info("Existing method: " + this.name + "." + name); return false; }
The addMethod()
is added to the prototype of the JavaScript intrinsic Function
class so that it can be invoked on any constructor function. If it is not invoked on a function that is a constructor function, i.e. one that has a prototype
member, then it throws an exception. So that it can be used to create polyfills, it will not overwrite a method with the same name if it already exists for the class, i.e. if the constructor function's prototype already has the method.
And here is how you would use addMethod()
.
import Utils from "Utils.mjs"; String.addMethod("trim", function() {return window.trim(this,true,true);}); String.addMethod("trimLeft", function() {return window.trim(this,true,false);}); String.addMethod("trimRight", function() {return window.trim(this,false,true);}); String.addMethod("repeat", function(n) {return window.repeat(this,n);}); let s = " \n\t\rABCDEFG \n"; var trimmed = s.trim(); var leftTrimmed = s.trimLeft(); var rightTrimmed = s.trimRight(); let separator = "=".repeat(80);
Notice that because these functions are designed to be instance methods they will always be invoked on an object. This object can then be referenced in the function using the this
variable.
Utils.addClassMethod()
The final method illustrated here is the addClassMethod()
. It is used to add the equivalent of a static
method to a class. In JavaScript this means that the method is added to the constructor function itself.
utils.mjs
/****************************************************************************** FUNCTION: addClassMethod(name, func) This function will add the class method to the class. Used on class constructor functions. This is equivalent to defining a method as static in a JavaScript class definition. Example: <pre>String.addClassMethod("valueOf",function(x) {return JSON.stringify(x);});</pre> @param {string} name Name of class method to attach. @param {function} func Function object to use as the class method. @returns true if the method was added. False otherwise. @type boolean @author Richard Lesh @version 2024/06/30 ******************************************************************************/ Function.prototype.addClassMethod = function (name, func) { if (isConstructor(this)) { throw new TypeError("addClassMethod(" + name + ") can only be applied to class constructors!"); } if (!this.hasOwnProperty(name)) { info("Adding class method: " + this.name + "." + name); this[name] = func; return true; } info("Existing class method: " + this.name + "." + name); return false; }
Because it creates the functional equivalent of a static
method, the new method is invoked using the class name, i.e. constructor function. These functions can not reference the this
variable because no object was used to invoke the method. For example:
import Utils from "Utils.mjs"; String.addClassMethod("stringily",function(x) {return JSON.stringify(x);}); let car = { brand: "Tesla", model: "Model S", year: 2024, isElectric: true }; let carAsString = String.stringify(car);