I am working on a problem and I am not allowed to use ES6 syntax so I can't use the spread/rest operator. How can I write a function that will take any number of arguments without a spread/rest operator?
function memoize(func) {
var cache = {};
return function (...args) {
if (args in cache) {
return cache[args];
} else {
cache[args] = func(...args);
return func(...args);
}
};
};
Firstly you have to use the arguments object. It is an older feature which makes a variable called arguments available in non arrow functions. The value of arguments is all the arguments a function receive. It is an array-like object, not an array. This eliminates the use of rest operator.
Also, it is better to create a key, when you are creating a generic function.
Why? Javascript object keys can only be string. If an object is used as a key it gets auto converted to a string : [object Object]. So basically all your object keys will override each other. Here is a demo:
const obj1 = { a : 1};
const obj2 = { b : 2};
const x = {};
x[obj1] = 2;
console.log(x);
x[obj2] = 2;
console.log(x);
To generate hash key you could use any method. Below is not a bullet proof implementation but I have just joined all the arguments into a . separated array.
Now we have to call your function with these arguments, in the else condition. How? When you want to forward you arguments to another function, and they are in the form of an array, you can use something like apply. It is pre ES6 feature which lets you run a function in a different context, or lets you pass arguments as an array. Here we won't be changing the context, but the second use case is what we will be using.
function memoize(func) {
var cache = {};
return function () {
const key = Object.values(arguments).join('.');
console.log(arguments);
console.log(cache);
if (key in cache) {
return cache[key];
} else {
cache[key] = func.apply(null,arguments);
return cache[key];
}
};
};
const conc = (str,str2,str3) => `${str}_${str2}_${str3}`;
const memoizedSq = memoize(conc);
memoizedSq('hello','hi','hey');
memoizedSq('bye','see you','so long');
memoizedSq('hello','hi','hey');
I have used join to create the string.
Minor optimization I did by changing your code in the else condition is not calling function multiple times. I just saved it in the object and returned the same value.
Note: This could break for cases where if a function takes a string, and the string itself contains ..
Related
I ran across some code I'm trying to refactor where they were writing JavaScript as a string and putting it inside HTML script tags, then writing it to the DOM. Very ugly and not maintainable. But one of the things this allowed them to do was build a function call by appending to a string.
var methods = '';
for (key in obj) {
methods += 'func1("'+key+'", "'+obj[key]+'").';
}
var scriptString = '<script>func2().' + methods + 'func3();</script>'
The result could be:
'<script>func2().func1("key1", "value1").func1("key2", "value2").func3();</script>'
So, since I really disapprove of writing JavaScript inside an HTML string inside of JavaScript ... Does anyone know how to accomplish the same result with pure JavaScript? Is there a way to continuously append methods to a function call by iterating over an object?
Array.reduce() should do most of what you need.
The tricky part is the .func1() method call chain, where you depend on an object's key/value pairs. If you're not used to working with the Array.reduce() method, I would suggest reading through the MDN documentation, but it basically loops through an array, performing a transformation on the previous result until it reaches the end, where it returns the final result. This can be used to our advantage since method chaining is just a method call on the previous method call's return value. But, since that's an array method, we need to get the Object's entries into an array first...and that's where Object.entries() comes in.
Note that much of my syntax here involves new features, which may or may not be supported by your target browsers. Be sure to use a transpiler and polyfills to handle going backwards, if needed.
See below for an example:
const wrapperFunc = (obj) => {
// Start with func2()
const func2Result = func2()
// Chain to func1() for each entry in obj (the tricky part)
const func1Result = Object.entries(obj).reduce(
// Call func1 method on previous result to get the next result
(prevResult, [ key, val ]) => prevResult.func1(key, val),
// Initial "prevResult" value used above
func2Result
)
// Chain to func3()
return func1Result.func3()
}
// Inject object to be used for func1() calls
wrapperFunc({
key1: 'value1',
key2: 'value2'
})
Also, here's a second, more complex example with some implemented methods. Unlike the example above, this one actually runs.
class MyObject {
constructor() {
this.innerString = ''
}
// Chainable method (returns this)
func1(key, val) {
this.innerString += `${key}, ${val} `
return this
}
func3() {
return this.innerString.trim()
}
}
const func2 = function () {
return new MyObject()
}
const wrapperFunc = (obj) => {
// Start with func2()
const func2Result = func2()
// Chain to func1() for each entry in obj (the tricky part)
const func1Result = Object.entries(obj).reduce(
// Call func1 method on previous result to get the next result
(prevResult, [ key, val ]) => prevResult.func1(key, val),
// Initial "prevResult" value used above
func2Result
)
// Chain to func3()
return func1Result.func3()
}
// Inject object to be used for func1() calls
console.log(wrapperFunc({
key1: 'value1',
key2: 'value2'
}))
You can create a function that calls the func1 method repeatedly for all key/value pairs in the object.
var script = function(obj) {
var value = func2();
for (var key in obj) {
value = value.func1(key, obj[key]);
}
value.func3();
};
If I have this ES6 function declaration and invocation:
function myFunction (arg1, arg2 = "bob") {
console.log("arguments", arguments);
}
myFunction(1);
...the console.log() statement shows only one argument with a value of "1". "bob" is nowhere to be seen. Is this expected and/or desired behavior? I would expect that default values would be available in the arguments object. If not, is there a way to dynamically get all arguments + defaults in some other manner?
Thanks in advance!
Yes, this is expected and desired. The arguments object is a list of the values that were passed into the function, nothing else.
It is not implicily linked to the parameter variables (that get assigned the default values), like it was in sloppy mode.
Is there a way to dynamically get all arguments + defaults in some other manner?
No. What parameters you have and whether they have default initialisers is static, you don't need to do anything here dynamically. You can do Object.assign([], arguments, [arg1, arg2]) for your example function.
As you know by now, there is no native method to get both "passed arguments AND defaults where arguments are not passed". But there is a workaround:
This function (that I found here) gets all parameters of a given function:
function getArgs(func) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
return args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
};
So, combining this function with the arguments of your function myFunction, we can get an array that has what you want:
function myFunction (arg1, arg2 = "bob") {
var thisArguments = arguments;
console.log(getArgs(myFunction, thisArguments));
};
function getArgs(func, argums) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
var argsArray = args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
for(var i = 0; i < argsArray.length; i++){
argsArray[i] += " (default)";
}
var defaults = argsArray.slice(argums.length);
argums = Array.prototype.slice.call(argums);
return argums.concat(defaults);
};
Now, we can see the information in the console calling myFunction:
1. Passing more arguments than parameters
This will return only the arguments.
myFunction("foo", "bar", "baz");
//returns: ["foo", "bar", "baz"]
2. Passing less arguments than parameters
Will return the arguments and the remainder parameters as default, as you want (I added "default" to each string).
myFunction("foo");
//returns ["foo", "arg2 = "bob" (default)"]
3. Passing no arguments
This will return all the parameters.
myFunction();
//returns ["arg1 (default)", "arg2 = "bob" (default)"]
This is the fiddle: https://jsfiddle.net/gerardofurtado/25jxrkm8/1/
Say I have a pure constructor function (containing nothing but this.Bar = bar)
1) When I call it from another function, can I pass the caller function's arguments directly when I call or must I do var myBar=new bar, myBar.Bar=thebar, where the bar is a caller argument?
2) Will the constructor still instantiate even if it doesn't get all the args?
3) How can I check if one of the args is unique, IE no other instance of the object has this value for the property in question? Specifically, I want to assign each object a unique index at creation. Maybe array?
Many thanks in advance
Say I have a pure constructor function (containing nothing but this.Bar = bar)
I'm going to assume you mean:
function MyConstructor(bar) {
this.Bar = bar;
}
(Note: The overwhelming convention in JavaScript is that property names start with a lower-case letter. So this.bar, not this.Bar. Initially-capped identifiers are usually reserved for constructor functions.)
1) When I call it from another function, can I pass the caller function's arguments directly when I call or must I do var myBar=new bar, myBar.Bar=thebar, where the bar is a caller argument?
You can pass them directly:
function foo(a, b, c) {
var obj = new MyConstructor(b);
}
2) Will the constructor still instantiate even if it doesn't get all the args?
The number of arguments passed is not checked by the JavaScript engine. Any formal arguments that you don't pass will have the value undefined when the function is called:
function MyConstructor(bar) {
console.log(bar);
}
var obj = new MyConstructor(); // logs "undefined"
3) How can I check if one of the args is unique, IE no other instance of the object has this value for the property in question? Specifically, I want to assign each object a unique index at creation. Maybe array?
In general, that's usually not in-scope for a constructor. But yes, you could use an array, or an object, to do that.
var knownBars = [];
function MyConstructor(bar) {
if (knownBars.indexOf(bar) !== -1) {
// This bar is known
}
else {
// Remember this bar
knownBars.push(bar);
}
}
Of course, indexOf may not be what you want for searching, so you may need to use some other method of Array.prototype or your own loop.
Another way would be to use an object; this assumes that bar is a string or something that can usefully be turned into a string:
var knownBars = {};
function MyConstructor(bar) {
if (knownBars.indexOf(bar) !== -1) {
// This bar is known
}
else {
// Remember this bar
knownBars[bar] = 1;
}
}
When I call it from another function, can I pass the caller function's arguments directly
There wouldn't be much point in having this.Bar = bar in the constructor function if you could not.
Will the constructor still instantiate even if it doesn't get all the args?
Assuming nothing inside it throws an exception if the argument is missing, yes. Arguments just get a value of undefined.
How can I check if one of the args is unique, IE no other instance of the object has this value for the property in question?
You'd need to have a shared store of them that you check against.
For example:
var Constructor = (function () {
var unique_check_store = {};
function RealConstructor(bar) {
if (typeof bar == 'undefined') {
throw "You must specify bar";
}
if (unique_check_store.hasOwnProperty(bar)) {
throw "You have already created one of these called '" + bar + "'";
}
this.Bar = bar;
unique_check_store[bar] = true;
}
return RealConstructor;
})();
var a, b, c;
try {
a = new Constructor();
} catch (e) {
alert(e);
}
try {
b = new Constructor("thing");
} catch (e) {
alert(e);
}
try {
c = new Constructor("thing");
} catch (e) {
alert(e);
}
alert(a);
alert(b);
alert(c);
I'm trying to procedurally add getters/setters to objects in Javascript and although I think the code below should simply work, it doesn't act as I expected.
here is my code:
var data = {a:1, b:2, c:3};
function Abc(data) {
var data = data || {};
for ( var key in data ) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
})
}
return this;
}
abc = Abc(data);
console.log('this should be 1', abc.a);
console.log('this should be 2', abc.b);
console.log('this should be 3', abc.c);
and this is my unexpected output
a 1
b 2
c 3
using getter
this should be 1 3
using getter
this should be 2 3
using getter
this should be 3 3
the output makes absolutely no sense to me, but I get the same output on Chrome and Webkit, so I'm guessing I'm just stupid and this is not a bug of the Javascript engines :)
as the comments mention my triple use of "data" isn't really good!
By the time the closure that was passed to __defineGetter__ is executed, the loop has finished and key remains at the last value. Try this:
function Abc(data) {
if(!(this instanceof Abc)) {
return new Abc(data);
}
data = data || {};
for(var key in data) {
(function(key) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this, key);
}
return this;
}
Some other things:
You weren't using var on key, so key was global. That wasn't causing your issue, but it's a good idea to add it anyway.
You were declaring a new variable named data when there was already a data in the scope of the function. It's not needed to use var there since there's already a data in the function; I removed it.
You weren't using new, which was also causing odd behavior; the new three lines at the top of the function cause it to act as if it was called with new.
#Robert Gould. main problem in your code sample is not in "defining getters and setters", but in "understanding closures".
See more about closures in MDN
Additionaly your code is invalid on using this keyword, because your Abc() function this object points to global window object. You must use new Abc() to properly use this keyword or must create new empty object inside Abc() and return it.
1)
function Abc(data) {
data=data||{};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this,data,key);
}
// dont need to return: when used *new* operator *this* is returned by default
}
var abc = new Abc(data);
or
2)
function Abc(data) {
data=data||{};
var obj={};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
obj.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
})(data,key);
}
return obj; // return object
}
var abc = Abc(data);
If you realy need to known about "defining getters|setterns" read about:
Object.defineProperty() MDN and get operator MDN
and for back compatibility __defineGetter__ MDN
you must also understand what not all top used browsers currently supports getters and setters for properties
key is a local variable in the scope of Abc, the anonymous function you wrote has no variable key, so it uses the one from the outer scope. That variable has changed to the last value in the loop by the time the anonymous function is used, so you see the last value. You can fix this with a closure:
this.__defineGetter__(key, (function(l_key){
return function() {
console.log('using getter');
return data[l_key];
}
})(key));
In that code l_key is a local copy of key to the anonymous function returned be another anonymous function.
Suppose you have the following:
function myfunc() {
// JS code
}
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
The problem: how do you process the args variable before submitting it to the JSON parser so that myfunc is replaced with the result of myfunc.toString() (that is, the body of the function)? The proposed solution should work on arbitrary functions and such quasi-JSON strings.
We use the optional second replacer argument to JSON.stringify to preprocess key/values, emitting functions in stringified form:
function stringify_with_fns(obj) {
return JSON.stringify(obj, function(key, value) {
return typeof value === "function" ? value.toString() : value;
});
}
Then to turn the stringified functions back into real functions on the way out, we use the optional second reviver parameter to JSON.parse, as in
function parse_with_fns(json) {
return JSON.parse(json, function(key, value) {
if (looks_like_a_function_string(value)) {
return make_function_from_string(value);
} else {
return value;
}
});
}
looks_like_a_function_string is just a regexp, and the first cut at make_function_from_string can use eval:
function looks_like_a_function_string(value) {
return /^function.*?\(.*?\)\s*\{.*\}$/.test(value);
}
function make_function_from_string(value) {
return eval(value);
}
To avoid eval, we can pick apart the function string to find its arguments and body, so we can pass them to new Function:
function make_function_from_string(value) {
var args = value
.replace(/\/\/.*$|\/\*[\s\S]*?\*\//mg, '') //strip comments
.match(/\(.*?\)/m)[0] //find argument list
.replace(/^\(|\)$/, '') //remove parens
.match(/[^\s(),]+/g) || [], //find arguments
body = value.match(/\{(.*)\}/)[1] //extract body between curlies
return Function.apply(0, args.concat(body);
}
Testing:
x = parse_with_fns(stringify_with_fns({a: function() {var x=1;}}))
x.a
> function anonymous() {var x=1;}
Note however that since functions created by new Function are all in the global scope, they will lose their enclosing scope and closures.
The only remaining question is whether this is useful. I suppose it might be for small utility functions.
Extending the concept to serializing/deserializing regexps or date objects is left as an exercise.
Like this? I had to change it because what you have isn't a valid JSON string since myfunc doesn't have double quotes. So I added those.
This parses it, gets the value of funcfield, finds the function (although it assumes it is in the global context), calls toString() updating the value of funcfield, the re-stringifies it as JSON.
Example: http://jsfiddle.net/patrick_dw/QUR6Z/
var myfunc = function() {
alert('hi');
};
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": "myfunc" }';
var parsed = JSON.parse( args );
parsed.funcfield = window[parsed.funcfield].toString();
var stringified = JSON.stringify( parsed );
alert(stringified);
Was this what you meant?
EDIT:
I guess you could use the current context, as long as that context is contained within the context that owns the function.
parsed.funcfield = this[parsed.funcfield].toString();
Or if you can't double quote the function name, you could use eval instead if you're certain the data is safe.
Example: http://jsfiddle.net/patrick_dw/QUR6Z/1/
var args = '{ "strfield": "hello world", "numfield": 10, "funcfield": myfunc }';
window.eval( 'var parsed = ' + args );
parsed.funcfield = parsed.funcfield.toString();
var stringified = JSON.stringify( parsed );
alert(stringified);