I've the following situation: I want to prepend (unshift) the arguments given to an function by another parameter. How my current approach looks like:
function eventReferer(event) {
var self = this;
return function() {
var args = ([event]).concat(Array.prototype.slice.call(arguments, 0, arguments.length));
return eventFunction.apply(self, args);
};
}
"eventFunction" is a custom function. I need to redirect the called event + all arguments to this function.
Because "arguments" is no valid Array in Javascript, the method of Array will not work. Is there any better way to merge my event and the arguments to a new array?
There are some more ways shown in the benchmark on http://jsperf.com/ghel-args - The only thing that's to do, is to unshift the "event" param to the array after converting the arguments to a regular JS-Array using one of the functions. So my first approach was correct, but not ideal (because it's not the fastest method available).
Related
I'm learning different implementations of the memoize function, and something is confusing me. Here is one version of the code:
_.memoize = function(func) {
let cache = {};
return function() {
let arg = JSON.stringify(arguments); //arg is an stringified array
if(!(arg in cache)) {
cache[arg] = func.apply(this, arguments);
}
return cache[arg];
};
};
My understanding of cahce[arg] is that the cache object has a key of arg, or if it doesn't, the function call of func.apply(this, arguments) becomes the property at an arg key. If arg is already a key in cache, than the function returns the property at cache[arg].
Ok, but isn't arg an array with a string in it? Assigned to a stringified version of arguments? Or is it a super long, single string formed by JSON.stringify?
How are the different arguments accessed? To me, this seems like the cache has a single key of either an array or a long string. Which means the identity of the individual arguments would be lost in a single piece of data, and I don't see how they are being accessed. I guess I would assume a for loop or forEach would loop through to access each argument, but that's not happening, so I don't really know how to read this.
Ok, but isn't arg an array with a string in it?
No, it is actually:
Or is it a super long, single string formed by JSON.stringify?
Yes. For example, if you memoize a function and then call it with
foo(1, 2)
foo(3, 4, 5)
foo('str')
the cache keys will then be composed of the following strings:
{"0":1,"1":2}
{"0":3,"1":4,"2":5}
{"0":"str"}
Each such line uniquely identifies a particular combination of arguments; there is a one-to-one mapping between a unique ordered number of arguments and the resulting stringified key. No information is lost; the fact that the arguments can be converted into a single string representing those arguments does not mean that any information about the arguments (their order or value) goes away; it's just put into a different format, that of a string, for the cache to work. (One-to-one correspondence is an incredibly useful technique in mathematics and algorithms. This is just one instance of it.)
I guess I would assume a for loop or forEach would loop through to access each argument, but that's not happening
That loop is happening, it's just implemented in native code, under the hood with JSON.stringify. But it only has to be done one way. Once an ordered set of arguments has been turned into a string, that string can then be used to uniquely identify the arguments, so the key can then be either looked up on the cache, or set on the cache.
function foo() {
console.dir(JSON.stringify(arguments));
}
foo(1, 2);
var curryIt = function(uncurried) {
var parameters = Array.prototype.slice.call(arguments, 1);
return function() {
return uncurried.apply(this, parameters.concat(
Array.prototype.slice.call(arguments, 0)
));
};
};
var greeter = function(greeting, separator, emphasis, name) {
console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."
I get the overall picture of what is happening but I do not understand what is being carried out in the curryIt function.
Every function has an object called arguments, which contains in an array like data structure the arguments which the caller of the function uses.
For example, let that we have the following function:
function sum(a,b){
return a+b;
}
If we call sum as below:
sum(3,4)
the arguments would contain two items 3 and 4. (Actually, you could call sum with 3, 4 or more arguments. All these values would be contained in the arguments).
The key word in the above statement is the array like data structure. arguments is not an array. So you can't have available the Array's methods (push, shift, slice, etc.)
What does slice?
The slice() method returns a shallow copy of a portion of an array
into a new array object selected from begin to end (end not included).
The original array will not be modified.
For further info please have a look here.
So if you want to apply slice on arguments would could you do?
Since arguments is not an array, (arguments instanceof Array returns false), you can't do so like below:
var a = ["zero", "one", "two", "three"];
var sliced = a.slice(1,3);
But you can't do it like below:
Array.prototype.slice.call(arguments, 1);
What does call?
The call() method calls a function with a given this value and
arguments provided individually.
For further info please have a look here.
So, essentially, the following line of code
Array.prototype.slice.call(arguments, 1);
calls the function called slice on the arguments objects passing 1 as it's argument. So you get an array with all the arguments except the first.
This is all about a functional programming paradigm called Currying. It's all about saving one or more arguments into a returned function to be re used at a later time. It's highly related with the closures topic of JavaScript.
Let's refactor the code in a more functional manner.
var curryIt = (uncurried,...args) => name => uncurried(...args,name),
greeter = (greeting, separator, emphasis, name) => console.log(greeting + separator + name + emphasis),
greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."
curryIt function takes several argument of which the first one is a function called uncurried the rest of the argument are collected in the args array by the help of the rest operator (...) of ES6.
Then we return a function which takes a single argument called name and makes use of the parameters passed to it's parent function by args array. So, right now the args array and it's elements are under closure. The returned function will invoke the passed uncurried function by providing the available arguments in a proper order.
I am trying to create a function that mimics Array.prototype.push.
It takes a variable number of arguments and pushes them into a specific array.
I have managed to do this with the following code:
var array=[];
function append(){
for(var i=0;i<arguments.length;i++)
array.push(arguments[i]);
}
Now my question is:Can I rewrite the append function without using "for loop"?
Thanks in advance.
If you need to get arguments array, you should use Array's slice function on an arguments object, and it will convert it into a standard JavaScript array:
var array = Array.prototype.slice.call(arguments);
You could use Array.prototype.push.apply
function append(){
// make arguments an array
var args = Array.prototype.slice.call(arguments);
// return the number of elements pushed in the array
return Array.prototype.push.apply(array, args);
}
So, what's happening here with args? We use Array.prototype.slice.call with arguments, the purpose being to make arguments an array, because it is a special object. Function.prototype.call is used to call a function with a specific context (aka this), and then the arguments to call the function with (comma separated). Conveniently, it appears that slice() looks at the length property of the this context, and arguments has one too, and when not empty, has properties from 0 to length -1, which allows slice to copy arguments in a new array.
You can rewrite this without a for loop, but you have to use a loop of some sort (you're working with multiple items, it's a necessity).
If you have access to ES6 or Babel, I would use something like:
function append(...args) {
return array.concat(args);
}
Without ES6, you need to work around the fact that arguments isn't a real array. You can still apply most of the array methods to it, by accessing them through the Array prototype. Converting arguments into an array is easy enough, then you can concat the two:
function append() {
var args = Array.prototype.map.call(arguments, function (it) {
return it;
});
return array.concat(args);
}
Bear in mind that neither of these will modify the global array, but will return a new array with the combined values that can be used on its own or assigned back to array. This is somewhat easier and more robust than trying to work with push, if you're willing to array = append(...).
Actually i honestly believe that push must be redefined for the functional JS since it's returning value is the length of the resulting array and it's most of the time useless. Such as when it's needed to push a value and pass an array as a parameter to a function you cant do it inline and things get messy. Instead i would like it to return a reference to the array it's called upon or even a new array from where i can get the length information anyway. My new push proposal would be as follows;
Array.prototype.push = function(...args) {
return args.reduce(function(p,c) {
p[p.length] = c;
return p
}, this)
};
It returns a perfect reference to the array it's called upon.
I've created a Javscript prototypal object with a range of attributes. And I need to pass some of these attributes to a function. The function processes the attributes and returns some result.
function ObjCt(id,data) {
this.id = id;
this.data = data;
}
So, I'm trying to pass the data of the chart object to a function. Im calling it as:
var obj=new ObjCt(id,data);
process(obj.id,obj.data);
I read from somewhere that passing a value in JS results in a call by value and passing an object results in a call by reference. Here I am trying to call by value, but, seems whatever processing happens in the process() function is reflected in the object obj.
I checked the variable received in the process function using typeof and it comes up as 'object' and not variable. So, what can I do to pass this data as a value?
Edit
I found a rather tiresome workaround. id is primitive and data is a JSON object. So, I tried this in the process() function
JSON.parse(JSON.stringify(data))
Apparently, all references were slashed here. I have a working implementation now, but, Id still love to see if there is an easier way for passing an attribute as a value.
Edit
Solved the problem. As per Paul's answer, tried a normal step by step cloning and another alternative using jquery extend method which goes like this
var object=$.extend(true,{},oldObject);
and passed the newly created object to the process() function.
Please refer this link: What is the most efficient way to deep clone an object in JavaScript?
In JavaScript, primitives (Number, String) are passed ByVal and Objects (including Array, Function) are passed ByRef.
Most of the time this makes no difference, however you can experience problems when using methods which modify an Object without taking into consideration you may want it elsewhere, too.
To get around this, clone your Object before passing it into such a method.
Array has the native Array.prototype.slice to clone, for Objects you need to loop over keys and recurse over the properties.
Custom function to clone, lets you extend it to do more Objects via instanceof testing
function clone(o) {
var o2 = {}, k;
if (typeof o !== 'object')
return o;
if (o instanceof Array)
return o.slice().map(clone);
if (o instanceof Date)
return new Date(o);
for (k in o)
if (Object.prototype.hasOwnProperty.call(o, k))
o2[k] = clone(o[k]);
return o2;
}
Cloning via JSON, only things which can be written in JSON are supported
function clone(o) {
return JSON.parse(JSON.stringify(o));
}
Pretending to clone by using inheritance
This will not protect foo.bar.baz = primitive, but will protect foo.bar = primitive so how good a choice it is for you depends on what the Object looks like or what is being done to it.
function pseudoclone(o) {
return Object.create(o);
}
Don't try to clone functions, it will go horribly wrong. If you need to modify them somehow, wrap them.
Are you attached to that approach only?
var Chart=function(id,data){
this.ObjCt = function () {
this.id = id;
this.data = data;
}
}
then
var obj=new Chart(id,data);
obj.ObjCt();
//process(obj.id,obj.data);
Assuming that id is primitive and data is an object:
function ObjCt(id, data){
this.id = id;
this.data = eval(data.toSource());
}
This will clone the object held in data with simple trick.
Is there a way to apply a two-dimensional array to an object?
Like this:
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
It seems to apply only the first inner array :-/
Why is that?
Okay, since your question was so uninformative I'm going to assume a lot of stuff. Firstly, I'm going to assume that someObject is a function. Next I'm going to assume that it has only one formal parameter like #Adam pointed out. So this is what I assume your code looks like:
function someObject(a) {
alert(a); // were you expecting [[0,1],[2,3]]?
}
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
This is what I think you want instead:
function someObject() {
alert(arguments); // now it alerts [[0,1],[2,3]]
}
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
Remember, when you apply arguments to a function you pass it the arguments as an array. It's kind of like calling the function as follows:
function someObject() {
alert(arguments);
}
var myArray = [[0,1],[2,3]];
someObject(myArray[0], myArray[1]);
Of course, it also assigns the function a custom this pointer.
Edit: Looking back at your code I think you might have intended to use call instead of apply. The method call allows you to pass the arguments to the function as separate arguments instead of an array of arguments. So your code would look:
function someObject(a, b) {
alert(a); // now a is [[0,1],[2,3]]
alert(b); // b is 5 and so on
}
var myArray = [[0,1],[2,3]];
someObject.call(null,myArray,5);
You code works..
With that code you pass two arguments to the someObject function, [0,1] and [2,3]
According to this fiddle that is exactly what happens.
http://jsfiddle.net/BgVxQ/
Edit: If you have an unknown number of arguments, use the arguments variable available inside the function to get hold of them. If you have a fixed number of arguments, then it's often easier to declare them
function someObject(parameter1, parameter2){
//Do stuff
}
That way you don't need to manually extract them from arguments
var myArray = [[0,1],[2,3]];
someObject.apply(null,[myArray]);
According to the docs, apply takes an array of arguments and passes them to your function. So, you need to place myArray inside an array that will be unpacked to form the argument to someObject:
var myArray = [[0, 1],[2, 3]];
someObject.apply(null, [myArray]);
In the code you posted, the function someObject was receiving two arguments: [0, 1] and [2, 3]. This is legal because JavaScript allows functions to be called with a number of arguments that differs from the number of formal parameters. But because there were more arguments than formal parameters, the second argument ([2, 3]) was lost and you only saw the first ([0, 1]).