Hello I'm working on a problem that requires me to change an set array of numbers into an array that returns the original numbers as a function. So we get a return of a2 instead of a[2].
I dont want the answer I just need a hint. I know i can loop through the array and use .pop() to get the last value of the array, but then I dont know how to convert it to a function from there. any hints?
var numToFun = [1, 2, 3];
var numToFunLength = numToFun.length;
for (var i = 0; i < numToFunLength; i++) {
(function(num){
numToFun.unshift(function() {
return num;
});
}(numToFun.pop()))
}
DEMO
basically it pops out a number from the last, builds a function with that number returned, and put back into the first of the array. after one full cycle, all of them are functions.
here's the catch: how this works, it's up to you to research
why the loop does not look like the straightforward pop-unshift:
for (var i = 0; i < numToFunLength; i++) {
numToFun.unshift(function() { //put into first a function
return numToFun.pop() //that returns a number
});
}
and why i did this: (HINT: performance)
var numToFunLength = numToFun.length;
There's three important steps here:
Extract the number value from the array. Within a loop with an iterator of i, it might look like this:
var num = numArray[i];
This is important, because i will not retain its value that it had when you created the new function - it'll end up with the last value it had, once the for loop is finished. The function itself might look like this:
function() { return num; }
There's no reference to i any more, which is important - to understand better, read about closures. The final step would be to add the new function to the array of functions that you want.
...and you're done!
EDIT: See other's answers for good explanations of how to do this right, I will fix mine also though
As others have pointed out, one of the tricky things in javascript that many struggle with (myself included, obviously) is that scoping variables in javascript is dissimilar to many other languages; scopes are almost purely defined by functions, not the {} blocks of, for example, a for loop, as java/C would be.
So, below you can see (and in other answers here) a scoping function can aid with such a problem.
var numArray = [12, 33, 55];
var funcArray = [];
var numArrLength = numArray.length; // Don't do this in for loop to avoid the check multiple times
for(var j=0; j < numArrLength; j++) {
var scopeMe = function() {
var numToReturn = numArray[j];
console.log('now loading... ' + numToReturn);
var newFunc = function() {
return numToReturn;
};
return newFunc;
}();
funcArray.push(scopeMe);
};
console.log('now me');
console.log(funcArray);
console.log(funcArray[0]());
console.log(funcArray[1]());
console.log(funcArray[2]());
console.log(funcArray[1]()); // To ensure it's repeatable
EDIT my old bad answer below
What you'll want to do is something like
var funcArray = [];
for(...) {
var newFunc = function() {
return numArray.pop();
}
funcArray.push(newFunc);
}
The key here is that functions in javascript can be named variables, and passed around as such :)
Related
We need to map an object array within a for loop, which actually works, but the editor is giving us a warning saying not to put a function within a loop:
for(var i=0; i<$scope.data.list.length; i++){
$scope.data.list[i].isRowSelected=false;
var pos1 = $scope.selectedItems.map(function(e) { return e.sys_id; }).indexOf($scope.data.list[i].sys_id);
if(pos1!==-1){
var add = $scope.selectedItems.indexOf($scope.data.list[i].sys_id);
$scope.selectedItems.splice(add,1);
}
}
To mitigate this, we're thinking about creating a separate function for the mapping and then calling it within the loop, like this:
function mappingID(e){
return e.sys_id;
}
However, when we call upon it within the loop, we're lost as to what to pass in...any suggestions? Thanks!
two things, create a function outside the loop and avoid repeating indexing and object nesting. It will make your code much cleaner and easier to reason about. I'm pretty sure this whole function could be done a lot better but I'm not sure of the bigger scope
var items = $scope.selectedItems;
var sys_id = function(e) { return e.sys_id; }
for(var i=0; i<$scope.data.list.length; i++){
var data = $scope.data.list[i]; // might be a better name for this...
data.isRowSelected=false;
var pos1 = items.map(sys_id).indexOf(data.sys_id);
if(pos1!==-1){
var add = items.indexOf(data.sys_id);
items.splice(add,1);
}
}
The comments suggest lodash, which is a good suggestion. For the purposes of your original question, however, you can declare the function mappingID as you have it, and simply put
var pos1 = $scope.selectedItems.map(mappingID).indexOf($scope.data.list[i].sys_id);
and that will do the job.
You don't need to bring lodash to handle this, you can use find: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
for(var i=0; i<$scope.data.list.length; i++){
$scope.data.list[i].isRowSelected=false;
var item = $scope.selectedItems.find(e => (e.sys_id === $scope.data.list[i].sys_id));
if (item) {
$scope.selectedItems.splice(item,1);
}
}
Also I suggest changing selectedItems to an plain-object/Map/Set so you can lookup in constant time.
To avoid doing the same mapping on each iteration of the loop, move the mapping outside the loop:
var idArr = $scope.selectedItems.map(function(e) { return e.sys_id; })
$scope.data.list.forEach(item => {
item.isRowSelected=false;
var pos1 = idArr.indexOf(item.sys_id);
if(pos1!==-1){
var add = $scope.selectedItems.indexOf(item.sys_id);
$scope.selectedItems.splice(add,1);
}
})
I need help with this JS callback function. I am trying to figure out how exactly callbacks work in JS.
--quick test code follows:
function debFilter(deb_array, fillCb){
var filt_darr = [];
for (var inx in deb_array) {
filt_darr.push(fillCb(deb_array[inx]));
}
return filt_darr;
}
console.log(debFilter(savedInp, function(x) { if (x%2 == 0) { return x;}} ));
Let's say my savedInp array contains [2,3,4,5,6,7,8,9] something like this. How do I make sure my callback returns only the even elements and not the odd ones? so my filt_darr would be [2,4,6...etc].
With the above test code I am getting [2,undefined,4,undefined,..etc]. I have tried with other similar conditions too with no avail. I just need to know how to tell JS not to 'push/return' something I dont need. Sorry if this is a beginner Q.
Thanks for the help.
Iterate the array and then push evens into a new array:
var a = [1,2,3,4,5];
function getEvens(originalArray){
var evens = [];
for(var i = 0; i < originalArray.length; ++i){
if(originalArray[i] % 2 === 0){
evens.push(originalArray[i]);
}
}
return evens;
}
As you probably noticed, you are collecting every return value into your result array and your callback returns undefined for every odd.
You could change your code to sth like
function debFilter(deb_array, fillCb){
'use strict';
var filt_darr = [],
len = deb_array.length;
for (var i = 0; i < len; i++) {
if (fillCb(deb_array[i])) {
filt_darr.push(deb_array[i]);
}
}
return filt_darr;
}
By the way, ES 5 supports Array.prototype.filter which might be what you are looking for. There is also a polyfill that you can take some inspiration from.
So I've written a few each functions like jQuery has, and to be honest they are nice but I got to thinking, what if the user at hand does not want to start at 0, or wants to manipulate the iterator. I've tried many ways of doing this and it seems to either crash or cause an infinite loop. Here are some examples.
//a being an array of elements
_$(a).each(function(i){
console.log(i);
i++;
});
//backend js
each:function(fn){
var i = fn[0]; // equals the first argument in the callback
for(i=0;i<this.length;)
fn.call(this[i],i);
return this;
},
Next Example
//a being an array of elements
var inta = 6;
_$(a).each(function(i){
console.log(i);
},inta++);
//backend js
each:function(fn,iterator){
var len = this.length;
var ct = iterator;
while(ct < len)
fn.call(this[ct],ct);
return this;
},
There are a few more examples and they don't work either. So question is how do I manipulate the iterator inside the function like the first example above. If not possible that is fine just curious as to this curious possibility.
So what I've read above from map, filter, reduce, some, every, they just won't work with what I need since I am writing a small minimal library. So I need to know exactly what to iterate over, which in my case is this but only this[INT]. So I created an optional parameter after the function to add to the iterator by.
each:function(fn,add){
/*
First check if add is assigned
If it is not assign it to be 1 + 1 (i++)
If it is, make sure it is not 0 (causing an infinite loop)
And that will result in ( i + add)
*/
add = 0 !== add && add || 1;
for(var i=0;i<this.length;i = i + add)
fn.call(this[i],i);
return this;
}
So the use case would be.
var domElements = document.querySelectorAll('*'); //10 items per se
_$(domElements).each(function(i){
console.log(i);
},2);
//logs
//0 2 4 6 8
This is just a very basic iterator for now, and like I've said for a small convenient library not boogered by lots of "bull".
Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.
My values naturally come in this form:
[1,2,3,4,5,6,7,8,9]
I am developing against a server api which requires an input parameter like:
[1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9]
Is there a faster or more js-style way to do than a simple for loop?
var f = function(values) {
var newList = [];
var i;
for (i = 0; i < values.length; i++) {
newList.push(values[i]);
newList.push(values[i]);
}
return newList;
}
You could avoid a .push() call by combining them since .push() is variadic.
newList.push(values[i], values[i]);
Other than that, I doubt you'll get much quicker.
You can use each function.
this will reduce your step.
var list=[1,2,3,4,5,6,7,8,9]
var newlist=[];
$.each(list,function(index,data){newlist.push(data);newlist.push(data)})
hope this helps.
May be assignment is faster...
L2 = [];
for (var i=L1.lenght*2; i-->0;) {
L2[i>>1] = L1[i];
}
but this kind of micro-optimization really needs to be profiled on the specific implementations (and I wouldn't be surprised in big differences between different Javascript engines).
I'd keep the most readable way unless this is a key issue (and if it's a key issue they probably Javascript is the wrong tool).
Try: [].concat.call([1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9]).sort();
or more generic:
(function(){
return this.concat(this).sort(function(a,b){return a-b;});}
).call([1,2,10,20,30,40,50,60,70,80,9]);
or just a function:
function(v) {
var i = v.length, nw = [];
while (i--) { nw.push(v[i],v[i]); }
return nw.reverse();
}
or using map
var nw = ([1,2,3,4,5].map(function(a){this.push(a,a)},nw=[]),nw);
I suspect the function is the most efficient.