Constructor chaining in javascript - javascript

I would like to be able to concatenate strings by creating an object and passing down the constructor, so every time it is called it keeps the references of the previous one. I would like to achieve something like this:
foo = Chain("h")
bar = foo("e")("l")("l")("o")
foo.toString() == "h"
bar.toString() == "hello"
bar.ancestor.toString() == "hell"
What I have so far is a method chaining, when is rather similar but it is not quite what I want to accomplish here. I've been following the following documentation:
Bind
Method binding
Inheritance and prototype chain
function ChainParent() {
}
function Chain(letter) {
this.letter = letter;
this.ancestor = this.letter.substring(0, this.letter.length - 1);
}
Chain.prototype = Object.create(ChainParent.prototype)
Chain.prototype.constructor = Chain;
Chain.prototype.create = function create(letter) {
const letra = this.letter.concat(letter);
return new this.constructor(letra)
}
Chain.prototype.toString = function() {
console.log(this.letter);
}
const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
foo.toString();
bar.toString();
bar.ancestor.toString(); //This won't work

Here's a solution using How to extend Function with ES6 classes? to make the class callable, so there's less boilerplate with the chaining.
In this example, each instance of the class keeps the the letter that it was given to start with, then merges them together using recursion in the toString function. So it acts as a linked list with a reference to the parent only.
// https://stackoverflow.com/a/36871498/13175138
class ExtensibleFunction extends Function {
constructor(f) {
return Object.setPrototypeOf(f, new.target.prototype);
}
}
class Chain extends ExtensibleFunction {
constructor(letter, chain = null) {
super((letter) => new Chain(letter, this));
this.ancestor = chain;
this.letter = letter;
}
toString() {
if (!this.ancestor) {
return this.letter;
}
return this.ancestor.toString() + this.letter;
}
}
const foo = new Chain('h');
const bar = foo('e')('l')('l')('o');
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
const foobar = new Chain('hel');
const barfoo = foobar('ll')('o');
console.log(foobar.toString());
console.log(barfoo.toString());
console.log(barfoo.ancestor.toString());

Here's one possible implementation of chain -
const chain = (s = "") =>
Object.assign
( next => chain(s + next)
, { toString: _ => s }
, { ancestor: _ => chain(s.slice(0, -1)) }
)
const foo = chain("h")
const bar = foo("e")("l")("l")("o")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
h
hello
hell
hel
Per #IbrahimMahrir's comment, we can make an adjustment to chain to accommodate input strings of any length -
function chain (s = "")
{ const loop = (s = []) =>
Object.assign
( next => loop([ ...s, next ])
, { toString: _ => s.join("") }
, { ancestor: _ => loop(s.slice(0, -1)) }
)
return loop([s])
}
const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell
And here's another implementation of chain I thought about over lunch -
const chain = (s = "", ...ancestor) =>
Object.assign
( next => chain(next, s, ...ancestor)
, { toString: _ => [...ancestor].reverse().join("") + s }
, { ancestor: _ => chain(...ancestor) }
)
const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")
console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell

You should just set the ancestor to the current instance (this).
Note that toString is a special method, as it gets called automatically when the JavaScript engine needs to convert an instance to a primitive value. So you should stick to the "interface" for it: it should return the string (not output it).
Here is an implementation of your final block using the modern class syntax. I allows letter to actually be a string with more than one character. The ancestor property will really return what the string is before the most recent extension:
class Chain {
constructor(letter, ancestor=null) {
this.letter = letter;
this.ancestor = ancestor;
}
create(letter) {
return new Chain(this.letter + letter, this);
}
toString() {
return this.letter;
}
}
const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
If you don't want a constructor -- so not new -- then the above translates to:
function chain(letter, ancestor=null) {
let that = {
letter,
ancestor,
create(newLetter) {
return chain(letter + newLetter, that);
},
toString() {
return letter;
}
}
return that;
}
const foo = chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());

Related

Add a method to a string variable's prototype

Expected: When a variable is created using createGreetable it should have an additional property greet set by greetable(text). The usual string methods should still be callable on this variable.
What I Tried:
const greetablePrototype = {
greetable: function(greeting) {
this.greet = `${greeting}, ${ this.valueOf() }!`
}
}
function createGreetable(str) {
const result = new String(str);
Object.setPrototypeOf(result, greetablePrototype)
return result;
}
const t = new createGreetable("world");
t.greetable("hello");
console.log(t.greet);
console.log(t.length);
Output:
hello, [object String]!
5
Expected Output:
hello, world!
5
You can extend the string class:
class Greetable extends String {
greet (greeting) {
return `${greeting}, ${this}!`;
}
}
const g = new Greetable('world');
g.greet('hello'); // hello, world!
g.length // 5
A few modifications to your code were needed to get it to work as intended:
Adding this.length in the greetable prototype function to update the string length each time (remember this is a string object).
Changing the const result line to this.greet in the constructor function, setting the prototype of this to the greetablePrototype as intended, then returning the instance of our object return this.
const greetablePrototype = {
greetable: function(greeting) {
this.greet = `${greeting}, ${this.greet}!`
this.length = this.greet.length;
}
}
function createGreetable(str) {
this.greet = String(str);
Object.setPrototypeOf(this, greetablePrototype)
return this;
}
const t = new createGreetable("world");
t.greetable("hello");
console.log(t.greet);
console.log(t.length);

Is there any way to intercept methods triggered with [] in js? [duplicate]

I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?
I was thinking on the lines of ...
MyClass.operator.lookup(index)
{
return myArray[index];
}
or am I not looking at the right things.
You can do this with ES6 Proxy (available in all modern browsers)
var handler = {
get: function(target, name) {
return "Hello, " + name;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123
Check details on MDN.
You can't overload operators in JavaScript.
It was proposed for ECMAScript 4 but rejected.
I don't think you'll see it anytime soon.
The simple answer is that JavaScript allows access to children of an Object via the square brackets.
So you could define your class:
MyClass = function(){
// Set some defaults that belong to the class via dot syntax or array syntax.
this.some_property = 'my value is a string';
this['another_property'] = 'i am also a string';
this[0] = 1;
};
You will then be able to access the members on any instances of your class with either syntax.
foo = new MyClass();
foo.some_property; // Returns 'my value is a string'
foo['some_property']; // Returns 'my value is a string'
foo.another_property; // Returns 'i am also a string'
foo['another_property']; // Also returns 'i am also a string'
foo.0; // Syntax Error
foo[0]; // Returns 1
foo['0']; // Returns 1
Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:
var handler = {
get: function(target, name) {
if (name in target) {
return target[name];
}
if (name == 'length') {
return Infinity;
}
return name * name;
}
};
var p = new Proxy({}, handler);
p[4]; //returns 16, which is the square of 4.
We can proxy get | set methods directly. Inspired by this.
class Foo {
constructor(v) {
this.data = v
return new Proxy(this, {
get: (obj, key) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key]
else
return obj[key]
},
set: (obj, key, value) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key] = value
else
return obj[key] = value
}
})
}
}
var foo = new Foo([])
foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:
var obj = {
get attr() { alert("Getter called!"); return 1; },
set attr(value) { alert("Setter called!"); return value; }
};
obj.attr = 123;
The same for IE8+:
Object.defineProperty("attr", {
get: function() { alert("Getter called!"); return 1; },
set: function(value) { alert("Setter called!"); return value; }
});
For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.
The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.
one sneaky way to do this is by extending the language itself.
step 1
define a custom indexing convention, let's call it, "[]".
var MyClass = function MyClass(n) {
this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
...
var foo = new MyClass(1024);
console.log(foo["[]"](0));
step 2
define a new eval implementation. (don't do this this way, but it's a proof of concept).
var MyClass = function MyClass(length, defaultValue) {
this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));
var mini_eval = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = eval(values[0]);
var i = eval(values[2]);
// higher priority than []
if (target.hasOwnProperty('[]')) {
return target['[]'](i);
} else {
return target[i];
}
return eval(values[0])();
} else {
return undefined;
}
} else {
return undefined;
}
} else {
return undefined;
}
};
mini_eval("foo[33]");
the above won't work for more complex indexes but it can be with stronger parsing.
alternative:
instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.
var compile = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = values[0];
var i = values[2];
// higher priority than []
return `
(${target}['[]'])
? ${target}['[]'](${i})
: ${target}[${i}]`
} else {
return 'undefined';
}
} else {
return 'undefined';
}
} else {
return 'undefined';
}
};
var result = compile("foo[0]");
console.log(result);
console.log(eval(result));
You need to use Proxy as explained, but it can ultimately be integrated into a class constructor
return new Proxy(this, {
set: function( target, name, value ) {
...}};
with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]
So you're hoping to do something like
var whatever = MyClassInstance[4];
?
If so, simple answer is that Javascript does not currently support operator overloading.
Have a look at Symbol.iterator. You can implement a user-defined ##iterator method to make any object iterable.
The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.
Example:
class MyClass {
constructor () {
this._array = [data]
}
*[Symbol.iterator] () {
for (let i=0, n=this._array.length; i<n; i++) {
yield this._array[i]
}
}
}
const c = new MyClass()
for (const element of [...c]) {
// do something with element
}

How to achieve privacy for value saved in `this` in constructor function?

What options do I have to achieve privacy on values I need to save in this in a constructor function? For example, a simple Stack implementation:
function Stack(){
this._stack = {}
this._counter = 0
}
Stack.prototype.push = function (item){
this._stack[this._counter++] = item
return this
}
Stack.prototype.pop = function (){
Reflect.deleteProperty(this._stack, --this._counter);
return this
}
Stack.prototype.peek = function (){
return this._stack[this._counter - 1]
}
Stack.prototype.length = function (){
return Object.values(this._stack).length
}
If these methods are not defined as prototype methods, I can easily private them like this:
function Stack(){
let _stack = {}
let _counter = 0
this.push = function (item){
_stack[_counter++] = item
return this
}
this.pop = function (){
Reflect.deleteProperty(_stack, --_counter);
return this
}
this.peek = function (){
return _stack[_counter - 1]
}
this.length = function (){
return Object.values(_stack).length
}
}
This way _stack and _counter are not exposed, but then these methods are not on prototype chain.
Is it possible to achieve privacy, while the protected values are saved in this?
Here is an example of using private fields. You can make them static with the static keyword, but that is not necessary in this example.
class test {
#lol = 29;
#mas = 15;
constructor() {
this.#lol++;
this.#mas--;
return {
value: this.#lol - this.#mas
};
}
};
console.log(new test().value); // --> 16
MDN provides an example of private properties in their Keyed collections guide.
WeakMap object
The WeakMap object is a collection of key/value pairs in which
the keys are objects only and the values can be arbitrary values.
The object references in the keys are held weakly, meaning that they
are a target of garbage collection (GC) if there is no other reference
to the object anymore. The WeakMap API is the same as the Map API.
One difference to Map objects is that WeakMap keys are not
enumerable (i.e., there is no method giving you a list of the keys).
If they were, the list would depend on the state of garbage
collection, introducing non-determinism.
For more information and example code, see also "Why WeakMap?" on
the WeakMap reference page.
One use case of WeakMap objects is to store private data for an
object, or to hide implementation details. The following example is
from Nick Fitzgerald's blog post "Hiding Implementation Details with
ECMAScript 6 WeakMaps". The private data and methods belong inside
the object and are stored in the privates WeakMap object.
Everything exposed on the instance and prototype is public; everything
else is inaccessible from the outside world because privates is
not exported from the module.
const privates = new WeakMap();
function Public() {
const me = {
// Private data goes here
};
privates.set(this, me);
}
Public.prototype.method = function () {
const me = privates.get(this);
// Do stuff with private data in `me`...
};
module.exports = Public;
Applied to your scenario this could look like this:
const Stack = (function () {
const privates = new WeakMap();
function Stack() {
privates.set(this, { stack: {}, counter: 0 });
}
Stack.prototype.push = function (item) {
const _ = privates.get(this);
_.stack[_.counter++] = item;
return this;
};
Stack.prototype.pop = function () {
const _ = privates.get(this);
Reflect.deleteProperty(_.stack, --_.counter);
return this;
};
Stack.prototype.peek = function () {
const _ = privates.get(this);
return _.stack[_.counter - 1];
};
Stack.prototype.length = function () {
const _ = privates.get(this);
return Object.values(_.stack).length;
};
return Stack;
})();
This answer does not create private properties. However if the intent of the question is to prevent a user from accidentally accessing "private" properties or to prevent property conflict you can use symbols.
A property conflict happens when your function expects property A, while a library (or any other code) also expects property A but for another purpose.
const Stack = (function () {
const stack = Symbol("_stack");
const counter = Symbol("_counter");
function Stack() {
this[stack] = {};
this[counter] = 0;
}
Stack.prototype.push = function (item) {
this[stack][this[counter]++] = item;
return this;
};
Stack.prototype.pop = function () {
Reflect.deleteProperty(this[stack], --this[counter]);
return this;
};
Stack.prototype.peek = function () {
return this[stack][this[counter] - 1];
};
Stack.prototype.length = function () {
return Object.values(this[stack]).length;
};
return Stack;
})();
The above code does not prevent a user from accessing the properties, but makes it somewhat hard. You could still access them using the following code:
const stack = new Stack();
const [_stack, _counter] = Object.getOwnPropertySymbols(stack);
stack[_stack] // gives access to the stack
stack[_counter] // gives access to the counter
Symbol properties are excluded from a lot of common functions like Object.keys(), Object.values(), Object.entries(), and also from for...in loops.
I created a function that has access to private data and returns a function with a closure containing methods for working with them (keeps everything in one place) external functions serve only as a kind of pointers to the internal functions of the provider.
class Stack{
constructor(){
this.provider = this.provider('init', this)
}
provider(type, args){
const state = {}
if (type === 'init'){
state._stack = [];
state._counter = 0;
state.this = args;
}
return function (type, args) {
switch(type){
case 'push':
return _push(args)
case 'pop':
return _pop()
case 'peek':
return _peek()
case 'length':
return _length()
}
function _push(item){
state._stack.push(item)
return state.this
}
function _pop(){
const item = state._stack.pop()
console.log(item)
return state.this
}
function _peek(){
return state._stack[state._stack.length-1]
}
function _length(){
return Object.values(state._stack).length
}
}
}
push(item){
return this.provider('push', item)
}
pop(){
return this.provider('pop')
}
peek(){
return this.provider('peek')
}
length(){
return this.provider('length')
}
}
tests:
s = new Stack();
g = new Stack();
s.push(1).push(2).push(3)
console.log('length s:', s.length()) // 3
s.pop(/* 3 */).pop(/* 2*/)
console.log(s.peek())
s.pop(/* 1 */)
console.log('length s:', s.length()) // 0
g.push('a').push('b').push('c').pop(/* c */).push('d')
g.pop(/* d */)
console.log('g.peek()', g.peek(), /* b */)
g.pop(/* b */)
g.pop(/* a */)
console.log('length g:', g.length()) // 0

Get Object's method that have a particular parameter [duplicate]

Is there a way to get the function parameter names of a function dynamically?
Let’s say my function looks like this:
function doSomething(param1, param2, .... paramN){
// fill an array with the parameter name and value
// some other code
}
Now, how would I get a list of the parameter names and their values into an array from inside the function?
The following function will return an array of the parameter names of any function passed in.
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
var fnStr = func.toString().replace(STRIP_COMMENTS, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
if(result === null)
result = [];
return result;
}
Example usage:
getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []
Edit:
With the invent of ES6 this function can be tripped up by default parameters. Here is a quick hack which should work in most cases:
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;
I say most cases because there are some things that will trip it up
function (a=4*(5/3), b) {} // returns ['a']
Edit:
I also note vikasde wants the parameter values in an array also. This is already provided in a local variable named arguments.
excerpt from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments:
The arguments object is not an Array. It is similar to an Array, but does not have any Array properties except length. For example, it does not have the pop method. However it can be converted to a real Array:
var args = Array.prototype.slice.call(arguments);
If Array generics are available, one can use the following instead:
var args = Array.slice(arguments);
Below is the code taken from AngularJS which uses the technique for its dependency injection mechanism.
And here is an explanation of it taken from http://docs.angularjs.org/tutorial/step_05
Angular's dependency injector provides services to your controller
when the controller is being constructed. The dependency injector also
takes care of creating any transitive dependencies the service may
have (services often depend upon other services).
Note that the names of arguments are significant, because the injector
uses these to look up the dependencies.
/**
* #ngdoc overview
* #name AUTO
* #description
*
* Implicit module which gets automatically added to each {#link AUTO.$injector $injector}.
*/
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn')
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
Here is an updated solution that attempts to address all the edge cases mentioned above in a compact way:
function $args(func) {
return (func + '')
.replace(/[/][/].*$/mg,'') // strip single-line comments
.replace(/\s+/g, '') // strip white space
.replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments
.split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters
.replace(/=[^,]+/g, '') // strip any ES6 defaults
.split(',').filter(Boolean); // split & filter [""]
}
Abbreviated test output (full test cases are attached below):
'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []
function $args(func) {
return (func + '')
.replace(/[/][/].*$/mg,'') // strip single-line comments
.replace(/\s+/g, '') // strip white space
.replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments
.split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters
.replace(/=[^,]+/g, '') // strip any ES6 defaults
.split(',').filter(Boolean); // split & filter [""]
}
// test cases
document.getElementById('console_info').innerHTML = (
[
// formatting -- typical
function(a,b,c){},
function(){},
function named(a, b, c) {
/* multiline body */
},
// default values -- conventional
function(a /* = 1 */, b /* = true */) { a = a||1; b=b||true; },
function fprintf(handle, fmt /*, ...*/) { },
// default values -- ES6
"function( a, b = 1, c ){}",
"function (a=4*(5/3), b) {}",
// embedded comments -- sardonic
function(a, // single-line comment xjunk) {}
b //,c,d
) // single-line comment
{},
function(a /* fooled you{*/,b){},
function /* are you kidding me? (){} */(a /* function() yes */,
/* no, */b)/* omg! */{/*}}*/},
// formatting -- sardonic
function ( A, b
,c ,d
)
{
},
// by reference
this.jQuery || function (a,b){return new e.fn.init(a,b,h)},
$args,
// inadvertent non-function values
null,
Object
].map(function(f) {
var abbr = (f + '').replace(/\n/g, '\\n').replace(/\s+|[{]+$/g, ' ').split("{", 1)[0] + "...";
return " '" + abbr + "' // returns " + JSON.stringify($args(f));
}).join("\n") + "\n"); // output for copy and paste as a markdown snippet
<pre id='console_info'></pre>
Solution that is less error prone to spaces and comments would be:
var fn = function(/* whoa) */ hi, you){};
fn.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
.split(/,/)
["hi", "you"]
A lot of the answers on here use regexes, this is fine but it doesn't handle new additions to the language too well (like arrow functions and classes). Also of note is that if you use any of these functions on minified code it's going to go πŸ”₯. It will use whatever the minified name is. Angular gets around this by allowing you to pass in an ordered array of strings that matches the order of the arguments when registering them with the DI container. So on with the solution:
var esprima = require('esprima');
var _ = require('lodash');
const parseFunctionArguments = (func) => {
// allows us to access properties that may or may not exist without throwing
// TypeError: Cannot set property 'x' of undefined
const maybe = (x) => (x || {});
// handle conversion to string and then to JSON AST
const functionAsString = func.toString();
const tree = esprima.parse(functionAsString);
console.log(JSON.stringify(tree, null, 4))
// We need to figure out where the main params are. Stupid arrow functions πŸ‘Š
const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params
: maybe(_.first(tree.body)).params;
// extract out the param names from the JSON AST
return _.map(params, 'name');
};
This handles the original parse issue and a few more function types (e.g. arrow functions). Here's an idea of what it can and can't handle as is:
// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. πŸ’ͺ', () => {
const test = (func) => {
const expectation = ['it', 'parses', 'me'];
const result = parseFunctionArguments(toBeParsed);
result.should.equal(expectation);
}
it('Parses a function declaration.', () => {
function toBeParsed(it, parses, me){};
test(toBeParsed);
});
it('Parses a functional expression.', () => {
const toBeParsed = function(it, parses, me){};
test(toBeParsed);
});
it('Parses an arrow function', () => {
const toBeParsed = (it, parses, me) => {};
test(toBeParsed);
});
// ================= cases not currently handled ========================
// It blows up on this type of messing. TBH if you do this it deserves to
// fail πŸ˜‹ On a tech note the params are pulled down in the function similar
// to how destructuring is handled by the ast.
it('Parses complex default params', () => {
function toBeParsed(it=4*(5/3), parses, me) {}
test(toBeParsed);
});
// This passes back ['_ref'] as the params of the function. The _ref is a
// pointer to an VariableDeclarator where the βœ¨πŸ¦„ happens.
it('Parses object destructuring param definitions.' () => {
function toBeParsed ({it, parses, me}){}
test(toBeParsed);
});
it('Parses object destructuring param definitions.' () => {
function toBeParsed ([it, parses, me]){}
test(toBeParsed);
});
// Classes while similar from an end result point of view to function
// declarations are handled completely differently in the JS AST.
it('Parses a class constructor when passed through', () => {
class ToBeParsed {
constructor(it, parses, me) {}
}
test(ToBeParsed);
});
});
Depending on what you want to use it for ES6 Proxies and destructuring may be your best bet. For example if you wanted to use it for dependency injection (using the names of the params) then you can do it as follows:
class GuiceJs {
constructor() {
this.modules = {}
}
resolve(name) {
return this.getInjector()(this.modules[name]);
}
addModule(name, module) {
this.modules[name] = module;
}
getInjector() {
var container = this;
return (klass) => {
console.log(klass);
var paramParser = new Proxy({}, {
// The `get` handler is invoked whenever a get-call for
// `injector.*` is made. We make a call to an external service
// to actually hand back in the configured service. The proxy
// allows us to bypass parsing the function params using
// taditional regex or even the newer parser.
get: (target, name) => container.resolve(name),
// You shouldn't be able to set values on the injector.
set: (target, name, value) => {
throw new Error(`Don't try to set ${name}! πŸ˜‘`);
}
})
return new klass(paramParser);
}
}
}
It's not the most advanced resolver out there but it gives an idea of how you can use a Proxy to handle it if you want to use args parser for simple DI. There is however one slight caveat in this approach. We need to use destructuring assignments instead of normal params. When we pass in the injector proxy the destructuring is the same as calling the getter on the object.
class App {
constructor({tweeter, timeline}) {
this.tweeter = tweeter;
this.timeline = timeline;
}
}
class HttpClient {}
class TwitterApi {
constructor({client}) {
this.client = client;
}
}
class Timeline {
constructor({api}) {
this.api = api;
}
}
class Tweeter {
constructor({api}) {
this.api = api;
}
}
// Ok so now for the business end of the injector!
const di = new GuiceJs();
di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);
var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));
This outputs the following:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Its wired up the entire application. The best bit is that the app is easy to test (you can just instantiate each class and pass in mocks/stubs/etc). Also if you need to swap out implementations, you can do that from a single place. All this is possible because of JS Proxy objects.
Note: There is a lot of work that would need to be done to this before it would be ready for production use but it does give an idea of what it would look like.
It's a bit late in the answer but it may help others who are thinking of the same thing. πŸ‘
I know this is an old question, but beginners have been copypasting solutions that extract parameter names from the string representation of a function as if this was good practice in any code. Most of the time, this just hides a flaw in the logic.
Writing parameter names in the parentheses of a function declaration can be seen as a shorthand syntax for variable creation. This:
function doSomething(foo, bar) {
console.log("does something");
}
...is akin to this:
function doSomething() {
var foo = arguments[0];
var bar = arguments[1];
console.log("does something");
}
The variables themselves are stored in the function's scope, not as properties in an object. Just like you can't manipulate the name of a variable with code, there is no way to retrieve the name of a parameter as it is not a string, and it could be eliminated during JIT compilation.
I always thought of the string representation of a function as a tool for debugging purposes, especially because of this arguments array-like object. You are not required to give names to the arguments in the first place. If you try parsing a stringified function, it doesn't actually tell you about extra unnamed parameters it might take.
Here's an even worse and more common situation. If a function has more than 3 or 4 arguments, it might be logical to pass it an object instead, which is easier to work with.
function saySomething(obj) {
if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}
saySomething({sender: "user123", message: "Hello world"});
In this case, the function itself will be able to read through the object it receives and look for its properties and get both their names and values, but trying to parse the string representation of the function would only give you "obj" for parameters, which isn't useful at all.
I have read most of the answers here, and I would like to add my one-liner.
new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)').exec(Function.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '')
or
function getParameters(func) {
return new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}
or for a one-liner function in ECMA6
var getParameters = func => new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
__
Let's say you have a function
function foo(abc, def, ghi, jkl) {
//code
}
The below code will return "abc,def,ghi,jkl"
That code will also work with the setup of a function that Camilo Martin gave:
function ( A, b
,c ,d
){}
Also with Bubersson's comment on Jack Allan's answer:
function(a /* fooled you)*/,b){}
__
Explanation
new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')
This creates a Regular Expression with the new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). I have to use new RegExp because I am injecting a variable (Function.name, the name of the function being targeted) into the RegExp.
Example If the function name is "foo" (function foo()), the RegExp will be /foo\s*\((.*?)\)/.
Function.toString().replace(/\n/g, '')
Then it converts the entire function into a string, and removes all newlines. Removing newlines helps with the function setup Camilo Martin gave.
.exec(...)[1]
This is the RegExp.prototype.exec function. It basically matches the Regular Exponent (new RegExp()) into the String (Function.toString()). Then the [1] will return the first Capture Group found in the Regular Exponent ((.*?)).
.replace(/\/\*.*?\*\//g, '').replace(/ /g, '')
This will remove every comment inside /* and */, and remove all spaces.
This also now supports reading and understanding arrow (=>) functions, such as f = (a, b) => void 0;, in which Function.toString() would return (a, b) => void 0 instead of the normal function's function f(a, b) { return void 0; }. The original regular expression would have thrown an error in its confusion, but is now accounted for.
The change was from new RegExp(Function.name+'\\s*\\((.*?)\\)') (/Function\s*\((.*?)\)/) to new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') (/(?:Function\s*|^)\((.*?)\)/)
If you want to make all the parameters into an Array instead of a String separated by commas, at the end just add .split(',').
Since JavaScript is a scripting language, I feel that its introspection should support getting function parameter names. Punting on that functionality is a violation of first principles, so I decided to explore the issue further.
That led me to this question but no built-in solutions. Which led me to this answer which explains that arguments is only deprecated outside the function, so we can no longer use myFunction.arguments or we get:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
Time to roll up our sleeves and get to work:
⭐ Retrieving function parameters requires a parser because complex expressions like 4*(5/3) can be used as default values. So Gaafar's answer or James Drew's answer are so far the best approaches.
I tried the babylon and esprima parsers but unfortunately they can't parse standalone anonymous functions, as pointed out in Mateusz Charytoniuk's answer. I figured out another workaround though by surrounding the code in parentheses, so as not to change the logic:
const ast = parser.parse("(\n" + func.toString() + "\n)")
The newlines prevent issues with // (single-line comments).
⭐ If a parser is not available, the next-best option is to use a tried-and-true technique like Angular.js's dependency injector regular expressions. I combined a functional version of Lambder's answer with humbletim's answer and added an optional ARROW boolean for controlling whether ES6 fat arrow functions are allowed by the regular expressions.
Here are two solutions I put together. Note that these have no logic to detect whether a function has valid syntax, they only extract the arguments. This is generally ok since we usually pass parsed functions to getArguments() so their syntax is already valid.
I will try to curate these solutions as best I can, but without effort from the JavaScript maintainers, this will remain an open problem.
Node.js version (not runnable until StackOverflow supports Node.js):
const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);
function getArguments(func) {
const maybe = function (x) {
return x || {}; // optionals support
}
try {
const ast = parser.parse("(\n" + func.toString() + "\n)");
const program = parserName == 'babylon' ? ast.program : ast;
return program
.body[0]
.expression
.params
.map(function(node) {
return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
});
} catch (e) {
return []; // could also return null
}
};
////////// TESTS //////////
function logArgs(func) {
let object = {};
object[func] = getArguments(func);
console.log(object);
// console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}
console.log('');
console.log('////////// MISC //////////');
logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});
console.log('');
console.log('////////// FUNCTIONS //////////');
logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});
console.log('');
console.log('////////// STRINGS //////////');
logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');
Full working example:
https://repl.it/repls/SandybrownPhonyAngles
Browser version (note that it stops at the first complex default value):
function getArguments(func) {
const ARROW = true;
const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
const FUNC_ARG_SPLIT = /,/;
const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
.split(FUNC_ARG_SPLIT)
.map(function(arg) {
return arg.replace(FUNC_ARG, function(all, underscore, name) {
return name.split('=')[0].trim();
});
})
.filter(String);
}
////////// TESTS //////////
function logArgs(func) {
let object = {};
object[func] = getArguments(func);
console.log(object);
// console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}
console.log('');
console.log('////////// MISC //////////');
logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});
console.log('');
console.log('////////// FUNCTIONS //////////');
logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});
console.log('');
console.log('////////// STRINGS //////////');
logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');
Full working example:
https://repl.it/repls/StupendousShowyOffices
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')
=> [ "a", "b", "c" ]
You can also use "esprima" parser to avoid many issues with comments, whitespace and other things inside parameters list.
function getParameters(yourFunction) {
var i,
// safetyValve is necessary, because sole "function () {...}"
// is not a valid syntax
parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
params = parsed.body[0].expression.right.params,
ret = [];
for (i = 0; i < params.length; i += 1) {
// Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
if (params[i].type == 'AssignmentPattern') {
ret.push(params[i].left.name)
} else {
ret.push(params[i].name);
}
}
return ret;
}
It works even with code like this:
getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]
The proper way to do this is to use a JS parser. Here is an example using acorn.
const acorn = require('acorn');
function f(a, b, c) {
// ...
}
const argNames = acorn.parse(f).body[0].params.map(x => x.name);
console.log(argNames); // Output: [ 'a', 'b', 'c' ]
The code here finds the names of the three (formal) parameters of the function f. It does so by feeding f into acorn.parse().
I've tried doing this before, but never found a praticial way to get it done. I ended up passing in an object instead and then looping through it.
//define like
function test(args) {
for(var item in args) {
alert(item);
alert(args[item]);
}
}
//then used like
test({
name:"Joe",
age:40,
admin:bool
});
I don't know if this solution suits your problem, but it lets you redefine whatever function you want, without having to change code that uses it. Existing calls will use positioned params, while the function implementation may use "named params" (a single hash param).
I thought that you will anyway modify existing function definitions so, why not having a factory function that makes just what you want:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var withNamedParams = function(params, lambda) {
return function() {
var named = {};
var max = arguments.length;
for (var i=0; i<max; i++) {
named[params[i]] = arguments[i];
}
return lambda(named);
};
};
var foo = withNamedParams(["a", "b", "c"], function(params) {
for (var param in params) {
alert(param + ": " + params[param]);
}
});
foo(1, 2, 3);
</script>
</head>
<body>
</body>
</html>
Hope it helps.
Taking the answer from #jack-allan I modified the function slightly to allow ES6 default properties such as:
function( a, b = 1, c ){};
to still return [ 'a', 'b' ]
/**
* Get the keys of the paramaters of a function.
*
* #param {function} method Function to get parameter keys for
* #return {array}
*/
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
var fnStr = func.toString().replace(STRIP_COMMENTS, '');
var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
var result = argsList.match( ARGUMENT_NAMES );
if(result === null) {
return [];
}
else {
var stripped = [];
for ( var i = 0; i < result.length; i++ ) {
stripped.push( result[i].replace(/[\s,]/g, '') );
}
return stripped;
}
}
As this has not yet been mentioned, if you are using Typescript you can emit meta-data when using Decorators which will allow you to get the parameter types.
Metadata will only be emitted if the class/function/prop has a decorator on it.
It doesn't matter which decorator.
This feature can be enabled by setting emitDecoratorMetadata to true inside tsconfig.json
{
"compilerOptions": {
"emitDecoratorMetadata": true
}
}
As the metadata is still an early proposal the reflect-metadata package must be installed or Reflect.getMetadata will not be defined.
npm install reflect-metadata
You can use it as follows:
const AnyDecorator = () : MethodDecorator => {
return target => { }
}
class Person{
#AnyDecorator()
sayHello(other: Person){}
}
const instance = new Person();
// This returns: Function
const funcType = Reflect.getMetadata('design:type', instance.sayHello);
// Returns an array of types, here it would be: [Person]
const funcParams = Reflect.getMetadata('design:paramtypes', instance.sayHello);
In newer versions of Angular for instance this is used to determine what to inject -> https://stackoverflow.com/a/53041387/1087372
I don't know how to get a list of the parameters but you can do this to get how many it expects. Note this only counts arguments without a default value in the signature:
function foobar(a, b, c) {}
function foobar2(a, b=false, c=false) {}
console.log(foobar.length); // prints 3
console.log(foobar2.length); // prints 1
//See this:
// global var, naming bB
var bB = 5;
// Dependency Injection cokntroller
var a = function(str, fn) {
//stringify function body
var fnStr = fn.toString();
// Key: get form args to string
var args = fnStr.match(/function\s*\((.*?)\)/);
//
console.log(args);
// if the form arg is 'bB', then exec it, otherwise, do nothing
for (var i = 0; i < args.length; i++) {
if(args[i] == 'bB') {
fn(bB);
}
}
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5
a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});
// see, this shows you how to get function args in string
The answer to this requires 3 steps:
To get the values of the actual parameters passed to the function (let's call it argValues). This is straight forward as it will be available as arguments inside the function.
To get the parameter names from the function signature (let's call it argNames). This not as easy and requires parsing the function. Instead of doing the complex regex yourself and worrying about edge cases (default parameters, comments, ...), you can use a library like babylon that will parse the function into an abstract syntax tree from which you can obtain the names of parameters.
The last step is to join the 2 arrays together into 1 array that has the name and value of all the parameters.
The code will be like this
const babylon = require("babylon")
function doSomething(a, b, c) {
// get the values of passed argumenst
const argValues = arguments
// get the names of the arguments by parsing the function
const ast = babylon.parse(doSomething.toString())
const argNames = ast.program.body[0].params.map(node => node.name)
// join the 2 arrays, by looping over the longest of 2 arrays
const maxLen = Math.max(argNames.length, argValues.length)
const args = []
for (i = 0; i < maxLen; i++) {
args.push({name: argNames[i], value: argValues[i]})
}
console.log(args)
// implement the actual function here
}
doSomething(1, 2, 3, 4)
and the logged object will be
[
{
"name": "a",
"value": 1
},
{
"name": "c",
"value": 3
},
{
"value": 4
}
]
And here's a working example https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a
function getArgs(args) {
var argsObj = {};
var argList = /\(([^)]*)/.exec(args.callee)[1];
var argCnt = 0;
var tokens;
while (tokens = /\s*([^,]+)/g.exec(argList)) {
argsObj[tokens[1]] = args[argCnt++];
}
return argsObj;
}
Wow so many answers already.. Im pretty sure this gets buried. Even so I figured this might be useful for some.
I wasn't fully satisfied with the chosen answers as in ES6 it doesn't work well with default values. And it also does not provide the default value information. I also wanted a lightweight function that does not depend on an external lib.
This function is very useful for debugging purposes, for example: logging called function with its params, default param values and arguments.
I spent some time on this yesterday, cracking the right RegExp to solve this issue and this is what I came up with. It works very well and I'm very pleased with the outcome:
const REGEX_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const REGEX_FUNCTION_PARAMS = /(?:\s*(?:function\s*[^(]*)?\s*)((?:[^'"]|(?:(?:(['"])(?:(?:.*?[^\\]\2)|\2))))*?)\s*(?=(?:=>)|{)/m
const REGEX_PARAMETERS_VALUES = /\s*(\w+)\s*(?:=\s*((?:(?:(['"])(?:\3|(?:.*?[^\\]\3)))((\s*\+\s*)(?:(?:(['"])(?:\6|(?:.*?[^\\]\6)))|(?:[\w$]*)))*)|.*?))?\s*(?:,|$)/gm
/**
* Retrieve a function's parameter names and default values
* Notes:
* - parameters with default values will not show up in transpiler code (Babel) because the parameter is removed from the function.
* - does NOT support inline arrow functions as default values
* to clarify: ( name = "string", add = defaultAddFunction ) - is ok
* ( name = "string", add = ( a )=> a + 1 ) - is NOT ok
* - does NOT support default string value that are appended with a non-standard ( word characters or $ ) variable name
* to clarify: ( name = "string" + b ) - is ok
* ( name = "string" + $b ) - is ok
* ( name = "string" + b + "!" ) - is ok
* ( name = "string" + Ξ» ) - is NOT ok
* #param {function} func
* #returns {Array} - An array of the given function's parameter [key, default value] pairs.
*/
function getParams(func) {
let functionAsString = func.toString()
let params = []
let match
functionAsString = functionAsString.replace(REGEX_COMMENTS, '')
functionAsString = functionAsString.match(REGEX_FUNCTION_PARAMS)[1]
if (functionAsString.charAt(0) === '(') functionAsString = functionAsString.slice(1, -1)
while (match = REGEX_PARAMETERS_VALUES.exec(functionAsString)) params.push([match[1], match[2]])
return params
}
// Lets run some tests!
var defaultName = 'some name'
function test1(param1, param2, param3) { return (param1) => param1 + param2 + param3 }
function test2(param1, param2 = 4 * (5 / 3), param3) {}
function test3(param1, param2 = "/root/" + defaultName + ".jpeg", param3) {}
function test4(param1, param2 = (a) => a + 1) {}
console.log(getParams(test1))
console.log(getParams(test2))
console.log(getParams(test3))
console.log(getParams(test4))
// [ [ 'param1', undefined ], [ 'param2', undefined ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '4 * (5 / 3)' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '"/root/" + defaultName + ".jpeg"' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '( a' ] ]
// --> This last one fails because of the inlined arrow function!
var arrowTest1 = (a = 1) => a + 4
var arrowTest2 = a => b => a + b
var arrowTest3 = (param1 = "/" + defaultName) => { return param1 + '...' }
var arrowTest4 = (param1 = "/" + defaultName, param2 = 4, param3 = null) => { () => param3 ? param3 : param2 }
console.log(getParams(arrowTest1))
console.log(getParams(arrowTest2))
console.log(getParams(arrowTest3))
console.log(getParams(arrowTest4))
// [ [ 'a', '1' ] ]
// [ [ 'a', undefined ] ]
// [ [ 'param1', '"/" + defaultName' ] ]
// [ [ 'param1', '"/" + defaultName' ], [ 'param2', '4' ], [ 'param3', 'null' ] ]
console.log(getParams((param1) => param1 + 1))
console.log(getParams((param1 = 'default') => { return param1 + '.jpeg' }))
// [ [ 'param1', undefined ] ]
// [ [ 'param1', '\'default\'' ] ]
As you can tell some of the parameter names disappear because the Babel transpiler removes them from the function. If you would run this in the latest NodeJS it works as expected (The commented results are from NodeJS).
Another note, as stated in the comment is that is does not work with inlined arrow functions as a default value. This simply makes it far to complex to extract the values using a RegExp.
Please let me know if this was useful for you! Would love to hear some feedback!
This package uses recast in order to create an AST and then the parameter names are gathered from their, this allows it to support pattern matching, default arguments, arrow functions and other ES6 features.
https://www.npmjs.com/package/es-arguments
Here's my solution -- it works for named and unnamed functions, async and non-async functions, async and non-async lambdas, and lambdas with and without parens.
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*.*\*\/))/mg;
const STRIP_KEYWORDS = /(\s*async\s*|\s*function\s*)+/;
const ARGUMENT_NAMES = /\(([^)]+)\)\s*=>|([a-zA-Z_$]+)\s*=>|[a-zA-Z_$]+\(([^)]+)\)|\(([^)]+)\)/;
const ARGUMENT_SPLIT = /[ ,\n\r\t]+/;
function getParamNames(func) {
const fnStr = func.toString()
.replace(STRIP_COMMENTS, "")
.replace(STRIP_KEYWORDS, "")
.trim();
const matches = ARGUMENT_NAMES.exec(fnStr);
var match;
if (matches) {
for (var i = 1; i < matches.length; i++) {
if (matches[i]) {
match = matches[i];
break;
}
}
}
if (match === undefined) {
return [];
}
return match.split(ARGUMENT_SPLIT).filter(part => part !== "");
}
How I typically do it:
function name(arg1, arg2){
var args = arguments; // array: [arg1, arg2]
var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");
You can even ref the args by the functions name like:
name.arguments;
Hope this helps!
I'll give you a short example below:
function test(arg1,arg2){
var funcStr = test.toString()
var leftIndex = funcStr.indexOf('(');
var rightIndex = funcStr.indexOf(')');
var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
var params = paramStr.split(',');
for(param of params){
console.log(param); // arg1,arg2
}
}
test();
I have modified the version taken from AngularJS that implements a dependency injection mechanism to work without Angular. I have also updated the STRIP_COMMENTS regex to work with ECMA6, so it supports things like default values in the signature.
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
fn.$inject = $inject;
}
} else {
throw Error("not a function")
}
return $inject;
}
console.log("function(a, b)",annotate(function(a, b) {
console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
console.log(a, b, c, d)
}))
annotate({})
You can access the argument values passed to a function using the "arguments" property.
function doSomething()
{
var args = doSomething.arguments;
var numArgs = args.length;
for(var i = 0 ; i < numArgs ; i++)
{
console.log("arg " + (i+1) + " = " + args[i]);
//console.log works with firefox + firebug
// you can use an alert to check in other browsers
}
}
doSomething(1, '2', {A:2}, [1,2,3]);
It's pretty easy.
At the first there is a deprecated arguments.callee β€” a reference to called function.
At the second if you have a reference to your function you can easily get their textual representation.
At the third if you calling your function as constructor you can also have a link via yourObject.constructor.
NB: The first solution deprecated so if you can't to not use it you must also think about your app architecture.
If you don't need exact variable names just use inside a function internal variable arguments without any magic.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee
All of them going to call toString and replace with re so we can create a helper:
// getting names of declared parameters
var getFunctionParams = function (func) {
return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}
Some examples:
// Solution 1. deprecated! don't use it!
var myPrivateFunction = function SomeFuncName (foo, bar, buz) {
console.log(getFunctionParams(arguments.callee));
};
myPrivateFunction (1, 2);
// Solution 2.
var myFunction = function someFunc (foo, bar, buz) {
// some code
};
var params = getFunctionParams(myFunction);
console.log(params);
// Solution 3.
var cls = function SuperKewlClass (foo, bar, buz) {
// some code
};
var inst = new cls();
var params = getFunctionParams(inst.constructor);
console.log(params);
Enjoy with JS!
UPD: Jack Allan was provided a little bit better solution actually. GJ Jack!
Whatever the solution, it must not break on wierd functions, whose toString() looks just as wierd:
function ( A, b
,c ,d
){}
Also, why use complex regular expressions? This can be done like:
function getArguments(f) {
return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}
This works everywhere with every function, and the only regex is whitespace removal that doesn't even process the whole string due to the .split trick.
Ok so an old question with plenty of adequate answers.
here is my offering that does not use regex, except for the menial task of stripping whitespace . (I should note that the "strips_comments" function actually spaces them out, rather than physically remove them. that's because i use it elsewhere and for various reasons need the locations of the original non comment tokens to stay intact)
It's a fairly lengthy block of code as this pasting includes a mini test framework.
function do_tests(func) {
if (typeof func !== 'function') return true;
switch (typeof func.tests) {
case 'undefined' : return true;
case 'object' :
for (var k in func.tests) {
var test = func.tests[k];
if (typeof test==='function') {
var result = test(func);
if (result===false) {
console.log(test.name,'for',func.name,'failed');
return false;
}
}
}
return true;
case 'function' :
return func.tests(func);
}
return true;
}
function strip_comments(src) {
var spaces=(s)=>{
switch (s) {
case 0 : return '';
case 1 : return ' ';
case 2 : return ' ';
default :
return Array(s+1).join(' ');
}
};
var c1 = src.indexOf ('/*'),
c2 = src.indexOf ('//'),
eol;
var out = "";
var killc2 = () => {
out += src.substr(0,c2);
eol = src.indexOf('\n',c2);
if (eol>=0) {
src = spaces(eol-c2)+'\n'+src.substr(eol+1);
} else {
src = spaces(src.length-c2);
return true;
}
return false;
};
while ((c1>=0) || (c2>=0)) {
if (c1>=0) {
// c1 is a hit
if ( (c1<c2) || (c2<0) ) {
// and it beats c2
out += src.substr(0,c1);
eol = src.indexOf('*/',c1+2);
if (eol>=0) {
src = spaces((eol-c1)+2)+src.substr(eol+2);
} else {
src = spaces(src.length-c1);
break;
}
} else {
if (c2 >=0) {
// c2 is a hit and it beats c1
if (killc2()) break;
}
}
} else {
if (c2>=0) {
// c2 is a hit, c1 is a miss.
if (killc2()) break;
} else {
// both c1 & c2 are a miss
break;
}
}
c1 = src.indexOf ('/*');
c2 = src.indexOf ('//');
}
return out + src;
}
function function_args(fn) {
var src = strip_comments(fn.toString());
var names=src.split(')')[0].replace(/\s/g,'').split('(')[1].split(',');
return names;
}
function_args.tests = [
function test1 () {
function/*al programmers will sometimes*/strip_comments_tester/* because some comments are annoying*/(
/*see this---(((*/ src//)) it's an annoying comment does not help anyone understand if the
,code,//really does
/**/sucks ,much /*?*/)/*who would put "comment\" about a function like (this) { comment } here?*/{
}
var data = function_args(strip_comments_tester);
return ( (data.length==4) &&
(data[0]=='src') &&
(data[1]=='code') &&
(data[2]=='sucks') &&
(data[3]=='much') );
}
];
do_tests(function_args);
Here's one way:
// Utility function to extract arg name-value pairs
function getArgs(args) {
var argsObj = {};
var argList = /\(([^)]*)/.exec(args.callee)[1];
var argCnt = 0;
var tokens;
var argRe = /\s*([^,]+)/g;
while (tokens = argRe.exec(argList)) {
argsObj[tokens[1]] = args[argCnt++];
}
return argsObj;
}
// Test subject
function add(number1, number2) {
var args = getArgs(arguments);
console.log(args); // ({ number1: 3, number2: 4 })
}
// Invoke test subject
add(3, 4);
Note: This only works on browsers that support arguments.callee.

How to share data with child classes

I need to be able to access a parent (not super) class from child classes.
In my case the data structure becomes exactly 3 levels deep:
Class A contains an array with x * Class B containing an array with y * Class C.
instances of Class C need to be able to access properties from Class A.
My idea was to pass down a callback, that would return the needed values.
I can not pass down static values from class A, since they can change over runtime.
Is this the proper way of handling the data flow between my instances, or can this be achieved in a more elegant way?
I set up a fiddle for this case (for the sake of simplicity only 2 levels deep): https://jsfiddle.net/2chngzqg/
class A {
constructor() {
this.foo = "bar";
}
shareProps = () => {
return this.foo;
}
createSubClass() {
this.sub = new B(this.shareProps);
}
}
class B {
constructor(callback) {
this.logShared = callback;
}
}
const a = new A();
a.createSubClass();
console.log(a.sub.logShared())
a.foo = 'baz';
console.log(a.sub.logShared())
How about that?.. In case the already mentioned Cart type contains a list of Product types and each product does list its Option types, and as seen from the example, each type is in control of how to create and/or add other depended types to it's list, then ... just provide this information about the hosting type to the constructor/factory function of the hosted type, thus every option features a product slot/property and every product features a cart slot/property.
The changed code of the OP's provided fiddle then might look like that ...
class Foo {
constructor(host) {
this.foo = "foo";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
class Bar {
constructor(host) {
this.bar = "bar";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
class Baz {
constructor(host) {
this.baz = "baz";
this.host = host;
}
createMember(Member) {
return (this.member = new Member(this));
}
}
var foo = (new Foo);
console.log('foo : ', foo);
console.log('foo.foo : ', foo.foo);
console.log('foo.member : ', foo.member);
console.log('foo.host : ', foo.host);
var bar = foo.createMember(Bar);
console.log('bar : ', bar);
console.log('bar.bar : ', bar.bar);
console.log('bar.member : ', bar.member);
console.log('bar.host : ', bar.host);
console.log('bar.host.host : ', bar.host.host);
var baz = bar.createMember(Baz);
console.log('baz : ', baz);
console.log('baz.baz : ', baz.baz);
console.log('baz.member : ', baz.member);
console.log('baz.host : ', baz.host);
console.log('baz.host.host : ', baz.host.host);
console.log('baz.host.host.host : ', baz.host.host.host);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Although not es6 - you can achieve this using functional inheritance.
Using the Object that to hold the class properties. In this example B is instantiated onto the scope of A.
const A = () => {
let that = {}
// Public variable
that.foo = 'bar';
// Public function
that.shareProps = () => (that.foo);
// Subclass functions attached to this classes scope
that.createSubClass = () => {
that = B(that);
}
return that;
}
const B = (that) => {
that.logShared = () => (that.shareProps());
return that;
}
const a = A();
a.createSubClass();
console.log(a.logShared())
a.foo = 'baz';
console.log(a.logShared())
The 'sub classes' can be attached onto other public variables within the function if needs be.
JSFiddle
This is more flexible when you inherit from the 'parent' class in the 'child' classes because you always have access to whatever functions and variables are added to that.
const A = () => {
let that = {}
that.foo = 'bar';
that.shareProps = () => (that.foo);
return that;
}
const B = () => {
// A is inherited
let that = A();
that.logShared = () => (that.shareProps());
return that;
}
const C = () => {
// B is inherited
let that = B();
return that;
}
const c = C();
// Calls the function in B() => that.logShared()
console.log(c.logShared())
// Changes the value in A() => that.foo
c.foo = 'baz';
console.log(c.logShared())
See here for more examples of functional inheritance.

Categories

Resources