Skip to content

Instantly share code, notes, and snippets.

@tmillr
Last active March 10, 2021 03:07
Show Gist options
  • Save tmillr/e2de08d253c4168ee7d7a1baa4e337f3 to your computer and use it in GitHub Desktop.
Save tmillr/e2de08d253c4168ee7d7a1baa4e337f3 to your computer and use it in GitHub Desktop.
Computed Class or Function Names in Javascript (Without "eval" or "Function" Constructor)
/**
* NOTE:
* Just now realized this is achievable via `Object.defineProperty` and then passing the function you want to
* rename (or change the 'name' property of)... can't believe I didn't realize there was such a simple solution!
*/
/*
* Introduction
*
* Creating a named function is easy enough: */ function fName() {} /*.
* But what if we don't know the name we are going to or want to assign it beforehand?
* I'm sure the solution to this has already been discovered,
* but I stumlbed upon a solution on my own and thought I'd share it here.
* It's alot more interesting and rewarding to just solve things yourself
* instead of looking up the answer if you've got the time to do so.
* In javascript (or at least in current javascript) once a function has been created,
* it's "name" property is set in stone and cannot be changed.
*
* A function declaration already requires a name and the name provided is automatically set as
* the function's name (the "name" property). Providing a function expression with a name is optional,
* but if it is not provided, then the function's "name" property is set to the identifier/variable that
* references it.
*
* For example:
* - in */ var someVar = function() {}; someVar.name is "someVar" /*
* - in */ var someVar = function fName() {}; someVar.name is "fName" /*
* - in */ function() {} /* as used as an anonymous function (e.g. passed as a function argument),
* the "name" property of said function is the empty/null string "" (zero-length string)
* - and finally of course, in the function declaration */ function fName() {}; fName.name is "fName" /*
*
* Given this information, we cannot do */ var func = function() {}; func.name = "myFuncName" /*, and as far as I know,
* we can't do something like */ var someVar = "myFuncName"; function [someVar]() {} /* or */ var [someVar] = function() {} /*
* either.
*
*/
function createNamedFunction(name) {
return {
[name]: function() {
// a single literal/inline function implementation goes here
}
}[name];
}
function createNamedClass(name) {
return {
[name]: class {
// a single literal/inline class implementation goes here
}
}[name];
}
// And of course you could also use such a function to variably name multiple functions/classes at once
// by adding multiple members and parameters then simply removing the property accessor at the end so that
// the whole object gets returned.
// Or if we need to assign it to a variable without the variable implicitly assigning/changing the "name" property
// (as explained before).
function createNamedFunction(name) {
const func = {
[name]: function() {
// function implementation goes here
}
}[name];
// do stuff with function func
return func;
}
function createNamedClass(name) {
const cls = {
[name]: class {
// class implementation goes here
}
}[name];
// do stuff with class cls
return cls;
}
// Example of usage
const someRandomVar = Date.now();
const myNamedFunction = createNamedFunction(someRandomVar);
console.log(myNamedFunction.name);
/*
* Of course you can do this at the toplevel scope too, outside of any function scope...
*/
const someRandomVar2 = Date.now(); // the function name we'd like to use (a string or anything that is coercible to a string)
// My variably-named function
const func = {
[someRandomVar2]: function() {
// function implementation
}
}[someRandomVar2];
// My variably-named class
const cls = {
[someRandomVar2]: class {
// class implementation
}
}[someRandomVar2];
// Here's a way of taking a variable function, and a variable function name, and applying
// the latter to the former (as long as you don't mind wrapping your function in another function that is).
function nameFunction(nameToGiveFunc, funcToName) {
return {
[nameToGiveFunc]: function() {
funcToName();
}
}[nameToGiveFunc];
}
// And for classes...
function nameClass(nameToGiveClass, classToName) {
return {
[nameToGiveClass]: class extends classToName {}
}[nameToGiveClass];
}
// Example
class NotMyClass {}
const Mine = nameClass("MyClass", NotMyClass);
console.log(Mine); // logs: [class MyClass extends NotMyClass]
console.log(Mine.name); // logs: MyClass
// Or, globally name/rename a class (doesn't touch the old class/name of the old class)
function nameClassGlobal(nameToGiveClass, classToName) {
// Protect against overwriting a pre-existing global under the same name.
if (globalThis[nameToGiveClass] !== undefined)
throw new Error(
`${nameToGiveClass} is already defined in global scope`
);
globalThis[nameToGiveClass] = {
[nameToGiveClass]: class extends classToName {}
}[nameToGiveClass];
}
// Example of usage
nameClassGlobal("MyGlobalClass", NotMyClass);
console.log(MyGlobalClass); // logs: [class MyGlobalClass extends NotMyClass]
console.log(globalThis.MyGlobalClass); // logs: [class MyGlobalClass extends NotMyClass]
// And of course we could have also done window.MyGlobalClass in the browser to retrieve the class
/*
*
* These patterns of naming/renaming classes and functions probably could've been implmented via "Proxy" objects
* as well, but I like these patterns better because I imagine the Proxy route would have the same or slightly
* more overhead in terms of memory usage and "cpu cycles" (speed). Also with these patterns you are actually getting
* back real classes and functions, even if only named wrapper functions/classes wrapping the passed function/class in
* some cases.
*
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment