Dynamically instantiate class in javascript - javascript

I checked out this reference link and ended up using Matthew's solution, as it works for me.
var factory ={};
factory.Tree = function(arg1,arg2,arg3,arg4){
console.log(arg1+""+arg2+""+arg3+""+arg4);
}
function instantiate(classname){
return new (function(a){ return factory[classname].apply(this,a);})(Array.prototype.splice.call(arguments,1));
// also is this ^^^ a good practice? instead of declaring the temp function beforehand
// function t(a) {return factory[classname].apply(this,a);}
// return new t(Array.prototype.splice.call(arguments,1));
}
var newObj = instantiate("Tree",1,2,3,4); // this prints 1234 --> works
Though, I'm not sure why using user123444555621's solution only works if I pass in "arguments" (that is everything including "classname"):
function instantiate(classname){
return new (Function.prototype.bind.apply(factory[classname], arguments));
}
var newObj = instantiate("Tree",1,2,3,4); // this prints 1234 --> works
but if I slice "arguments" and remove "classname", then pass in the result array, it does not work as expected:
function instantiate(classname){
var args = Array.prototype.splice.call(arguments,1);
// ^^ I checked and this prints 1,2,3,4 as well
return new (Function.prototype.bind.apply(factory[classname], args));
}
var newObj = instantiate("Tree",1,2,3,4); // this prints 234undefined
I'm not sure why but somehow it seems like the args array is sliced (again) and removes its first element (1 in this case).
Could someone offer any insights? Thanks

Did you use the right array function slice vs splice ?
Array.prototype.slice() - Creates a new array from elements of an existing array. It does not modify the original array.
Array.prototype.splice() – Deletes and/or inserts elements in an array. Unlike slice(), the splice() method modifies the original array
and returns a new array. The splice() method takes three arguments.

Your problem is this line
var args = Array.prototype.splice.call(arguments,1);
You have essentially removed the first item from the arguments while turning it into array args
simply replace 1 by 0
var args = Array.prototype.splice.call(arguments,0);
DEMO
var factory ={};
factory.Tree = function(arg1,arg2,arg3,arg4){
console.log(arg1+""+arg2+""+arg3+""+arg4);
}
function instantiate(classname){
var args = Array.prototype.splice.call(arguments,0);
return new (Function.prototype.bind.apply(factory[classname], args));
}
instantiate("Tree",1,2,3,4);

Related

Why can't i pass `push` as a foreach function?

var array = [];
document.querySelectorAll("a").forEach(array.push); //Uncaught TypeError: Cannot convert undefined or null to object
Why does this fail? what does this error mean? It seems perfectly reasonable to pass an array method as a function, what am I not understanding/seeing?
array.push loses context (its this), so you need to pass a function with context captured.
But, even if what you wanted worked - you still would not get the result you want, since NodeList:forEach passes 3 arguments to the callback function, so you would fill your array with elements, indexes and the list of nodes.
So a solution would be to do
var array = [];
document.querySelectorAll("a").forEach(e => array.push(e));
You can rebind the this context
var arr = []; document.querySelectorAll("a").forEach(arr.push.bind(arr));
console.log(arr);
one
two
three
BUT the big issue here is the fact that push() when given multiple arguments, will add all of those arguments to the array.
So push(1,2,3) will add 1, 2, and 3 to the array. So the above code would have 3 links, but it will add 9 entries into the array because forEach has 3 arguments element, index, array. So you will need to use a function to do it. Or just use Array.from() to create the array.
var arr = [];
document.querySelectorAll('a').forEach(el => arr.push(el))
console.log(arr);
var arr2 = Array.from(document.querySelectorAll('a'))
console.log(arr2);
one
two
three
The problem at first instance is not passing array.push, the problem is you are iterating over a NodeLists and this structure is not an array, you can use Array.from in next way:
const array =Array.from(document.querySelectorAll("a"));
When you extract a function from an object, it loses its context, so when you call it and it accesses this, its original value has been lost.
To fix the issue you need to use Function.prototype.bind() to keep the this reference pointing to the right object.
You can see the problem and how bind works in this example:
const obj = {
prop: 'there',
print(prefix) {
console.log(`${ prefix }: Hello ${ this.prop }.`);
}
};
obj.print('obj.print');
// Context lost:
const extractedPrint = obj.print;
extractedPrint('extractedPrint');
// Context preserved to the original object:
const bindedPrint = obj.print.bind(obj);
bindedPrint('bindedPrint');
// Context replaced:
const alsoBindedPrint = obj.print.bind({ prop: 'bro' });
alsoBindedPrint('alsoBindedPrint');
Wondering where is this pointing when it's "lost"? It points to window:
const obj = {
prop: 'there',
print(prefix) {
console.log(`${ prefix }: Hello ${ this.prop }.`);
}
};
const extractedPrint = obj.print;
window.prop = 'window';
extractedPrint('extractedPrint');
In your case, you need to make sure that when push is called by forEach its context is preserved, that is, its this value should still be referencing the original array:
links.forEach(array.push.bind(array));
Anyway, that won't work as expected because NodeList.prototype.forEach() calls its callback with 3 arguments: currentValue, currentIndexand listObj and Array.prototype.push() accepts multiple arguments at once, so you could do:
const array = [];
const links = document.querySelectorAll('a');
links.forEach(array.push.bind(array));
console.log(array.length);
<a>1</a>
<a>2</a>
<a>3</a>
But for each Node or your NodeList, you would be calling push with 3 arguments instead of 1, ending up getting some unwanted elements on the list.
To convert a NodeList to an Array you could use Array.from() instead:
console.log(Array.from(document.querySelectorAll('a')).length);
<a>1</a>
<a>2</a>
<a>3</a>
Although there are other ways to do it, like pushing all the elements one by one defining your own callback:
const links = document.querySelectorAll('a');
const arr = [];
// Note we only define a single argument, so we ignore the other 2:
links.forEach((link) => {
arr.push(link);
});
console.log(arr);
<a>1</a>
<a>2</a>
<a>3</a>
Or the the same thing using a loop:
const links = document.querySelectorAll('a');
const arr = [];
for (const link of links) {
arr.push(link);
}
// Also valid:
// for (let i = 0; i < links.length; ++i) {
// arr.push(links[i]);
// }
console.log(arr);
// Note this won't work, as NodeList has some other iterable
// properties apart from the indexes:
const arr2 = [];
for(const i in links) {
arr2.push(links[i])
}
console.log(arr2);
<a>1</a>
<a>2</a>
<a>3</a>
I'm not 100% sure what you're trying to do, but I'm going to guess that you'd like to push the elements returned by querySelectorAll into 'array'. In which case, you can't just pass the function push to forEach, you must call push on array with each element as the argument, like so:
document.querySelectorAll("a").forEach(item => array.push(item));
What are you trying to push in your example?
Maybe you are missing some parameters?
Try it out:
document.querySelectorAll("a").forEach(function(item) {
array.push(item);
});

pass array to method with condition

Im using the following code to split array which is working,
I need to pass some value when array
for examle here is split the value to array
var myArr = val.split(/(\s+)/);
and if array in place 2 is empty I need to use the method like following
pass empty array in second arg
var val = process.run(myArr[0], [], options);
if the array place 2 is not empty I need to pass it like following
var val = process.run(myArr[0], [myArr[2]], options);
The second arg is array inside arry with the value of 2
there is nice way to do it instead of if on the method ?
I would create a function, as Dave Newton recommends. I could take the initial val and options as an argument and return the result of process.run:
function runProcess(val, options) {
var myArr = val.split(/(\s+)/);
var argArray = [];
if(myArr[2]) {
argArray.push(myArr[2]);
}
return process.run(myArr[0], argArray, options);
}
Since I don't know what the function exactly does, the name of the function and variables are pretty arbitrary. Feel free to change them to your needs.
If myArr[2] is a flat array and will always be flat, why not...
var val = process.run(myArr[0], [].concat(myArr[2]), options);

Underscore how to use the each and sort multiple array of objects

I have 2 sets in an array, each of these sets is an array of objects. I'd like to sort both of them using _.each and return a sorted object with the following:
var O1 = [{'name':'one'}, {'name':'two'}, {'name':'three'}, {'name':'four'}, {'name':'five'}, {'name':'six'}];
var O2 = [{'name':'rat'}, {'name':'cat'}, {'name':'lion'}, {'name':'tiger'}, {'name':'dog'}, {'name':'horse'}];
var sortIt = [O1, O2];
_.each(sortIt, function(item){
item = _.sortBy(item, function(arr){
return arr.name;
})
return item;
})
console.log(O1, "\n", O2); //nothing changed!
... but, apparently, nothing changes. The question is, what should be the proper approach?
Live Demo
First and foremost, it's kind of useless checking the values of O1 and O2 variables after the sorting process - they won't change. See, _.sortBy doesn't order the given array in place (as native Array#sort does), instead it returns a new (sorted) copy.
Second, it makes little sense to return anything from _.each iterator function - it'll just be ignored.
Finally, to adjust the specific element of a list, you'll need to address that element; just reassigning some value to a param variable (item in your code) won't be enough.
Here's one approach to do what you want:
var sortIt = [O1, O2];
_.each(sortIt, function(item, index, list) {
list[index] = _.sortBy(item, function(arr) {
return arr.name;
});
})
console.log(sortIt); // something is definitely changed now!
Demo.

Javascript arrays remaining identical after one is changed

I am making an app in which an algorithm is run on an array. Because the contents of the array will change during the execution of the algorithm, I'm storing the array contents to another array beforehand - performing the 'if' statements on the source array but updating the temporary array, then equating them afterwards.
The problem is that after running the algorithm, the two arrays are still identical. It seems that updating the temporary array automatically updates the source array.
I've created this jsfiddle to demonstrate:
var a = new Array( 0 , 1 , 2 );
var b = a;
b[1]=3;
document.write( (a[1]==b[1]) );
//Should show 'false' as this will not be correct
The code above returns "True". Is this normal behaviour? How can I overcome this?
That is normal behaviour, when copying array you are actually making a reference, so when you do var b = a that means instead of copying value you are just copying the reference
if you want to shallow copy an array (array with only one depth level) then you can use simple method like this:
var b = a.slice(0);
But if you want to deep copy an array (array with 2 or more depth level) then you can use below method for that:
var objClone = function(srcObj) {
var destObj = (this instanceof Array) ? [] : {};
for (var i in srcObj)
{
if (srcObj[i] && typeof srcObj[i] == "object") {
destObj[i] = objClone(srcObj[i]);
} else {
destObj[i] = srcObj[i];
}
}
return destObj;
};
For use of both of these methods check this jsFiddle
b = a doesn't copy the array, but a reference. Use b = a.slice() to make a copy.
See also ...
As others have mentioned a = b does not copy the array. However, if you do end up using the .slice() method to copy arrays please note that objects within an array will be copied by reference.
Take the following example
var obj = { greeting: "hello world" };
var a = [ obj ];
var b = a;
a[0].greeting = "foo bar";
b[0].greeting // => "foo bar";
Other than that you are good to go :)

Jquery function consumes the array

I don't quite understand what's wrong with my function that I wrote,
When I pass an array to it, eg:
var pvars=['health/1/1.jpg','health/1/2.jpg','health/1/3.jpg','health/1/4.jpg'];
cache_threads(pvars,1);
Then I end up with an empty variable, eg:
alert(pvars);
Returns an empty string.
Here is my function:
var cache_threads=function (arruy,quant){
if (quant==undefined) quant=1;
var div=Math.ceil(arruy.length/quant);
var a = arruy;
while(a.length) {
cache_bunch(a.splice(0,div));
}
}
a and arruy are the same array.
When you .splice one, you'll be splicing the other one, too!
If you want a (shallow) copy of the array, use .slice():
var a = arruy.slice(0);

Categories