Last active
March 10, 2021 03:07
-
-
Save tmillr/e2de08d253c4168ee7d7a1baa4e337f3 to your computer and use it in GitHub Desktop.
Computed Class or Function Names in Javascript (Without "eval" or "Function" Constructor)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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