I need to pass certain parameters into a function and have that function pull from an array based on the arguments passed to it. It's hard to explain, so I'll show you what I'm trying to do.
function SearchDeck(deck,...){
var tryagain = true;
do{
if(deck[0].property == value){
//do something;
tryagain = false;
}
else{
deck.splice(0,1);
}
}
while(tryagain);
}
There are multiple decks to look in, the proper deck will be passed in. I want to always be drawing off the top of the deck (index 0 of the array). I need to draw continuously until I find a card that matches what I'm after. I splice out the 0 index if it doesn't match. What I'm after is dynamic, varying across the properties or even the operators I would use.
Some examples of if statements I would have are...
deck[0].color == "orange"
deck[0].value >= 5
deck[0].value < -4
I could make multiple functions or have the function fork based on an argument, but that doesn't seem like the best way to go about this.
If I'm understanding this correctly, you want the behavior of the if(deck[0].property == value) to be different for each invocation of the SearchDeck(...) function?
My recommendation would be to pass in a function:
function SearchDeck(deck, validationFunction, ...){
var tryagain = true;
do{
if(validationFunction(deck[0])){
//do something;
tryagain = false;
}
else{
deck.splice(0,1);
}
}
while(tryagain);
}
Then when you call the code, you can do:
SearchDeck(deck, function(firstCard) { return firstCard.color == "orange" }, ...);
SearchDeck(deck, function(firstCard) { return firstCard.value >= 5 }, ...);
SearchDeck(deck, function(firstCard) { return firstCard.value < -4 }, ...);
Or, if the cases you're looking for might be reused, it might also be cleaner to make those named functions:
function validateColor(firstCard) {
return firstCard.color == "orange";
}
function validateHighValue(firstCard) {
return firstCard.value >= 5;
}
function validateLowValue(firstCard) {
return firstCard.value < -4;
}
SearchDeck(deck, validateColor, ...);
SearchDeck(deck, validateHighValue, ...);
SearchDeck(deck, validateLowValue, ...);
It sounds like you may be interested in the typeof operator:
if (typeof deck == 'object') { ... }
if (typeof deck[0].color == 'string') { ... }
if (typeof deck[0].value == 'number') { ... }
Alternatively:
if (deck[0].hasOwnProperty('color')) { ... }
This is what I came up with. You need to push the way of check (either "== 'oragne'" or "<3") as string.
function searchDeck() {
var deck = Array.prototype.slice.call(arguments, 1),
tryagain = true,
string = deck[deck.length - 1];
deck.pop();
while (tryagain) {
if (eval('deck[0].property' + string)) {
//do something;
alert('card found');
tryagain = false;
} else {
deck.splice(0, 1);
}
}
}
Hope this is what you wanted ;)
Here is a working jsfiddle example, of course there might be a more elegant way, this is just what I came up with. Note that eval can be dangerous, so you should be carefull if user picks what the test will be (the string pushed into the array).
Related
I have one function which is having if elseif conditions and the cyclomatic complexity is approaching 5. How do I reduce it?
function testFunc() {
var step = getModel('step');
if(step === 1) {
this.resetTask(); //calling some function
this.updateStep(0);
return true;
} else if(step === 2) {
this.initTask; //some other function
return true;
} else if(step === 3) {
this.name === 'add' ? this.add() : this.edit();
return true;
}
return false;
}
tried replacing with switch case but it didn't help.
Very simple refactor - remove all conditional logic you have now and extract each piece as a separate function into a map. Since you only execute one branch each time, depending on step, you can make that value the key and fetch what needs to be executed. You can then provide a fallback for when there is nothing that corresponds to step.
Now the cyclomatic complexity is 2, since there is only one place the code branches - either you find the corresponding handler for step or not. Also, the branching in step 3 is a completely separate function now, thus it doesn't need to count as part of testFunc
function testFunc() {
var step = getModel('step');
var steps = {
1: function() {
this.editTask(); //calling some function
this.updateStep(0);
return true;
},
2: function() {
this.initTask; //some other function
return true;
},
3: function() {
this.name === 'add' ? this.add() : this.edit();
return true;
},
default: function() {
return false;
}
};
var fn = steps[step] || steps.default;
return fn();
}
This will reduce your code complexity and makes more readable.
You can decouple your condition into multiple function and attach that function in condition object. here I'm passing this in argument but you can use different approch. here I've also given value of this just for the shake of running example. copy and paste in your editor and try.
function meaningfulFuncName1() {
this.editTask(); //calling some function
this.updateStep(0);
return true;
}
function meaningfulFuncName2() {
this.initTask; //some other function
return true;
}
function meaningfulFuncName3(context) {
context.name === 'add' ? context.add() : context.edit();
return true;
}
function defaultCase() {
return false;
}
function testFunc() {
this.name = 'add'
const step = getModel('step')
conditionObject = {
1: meaningfulFuncName1,
2: meaningfulFuncName2,
3: meaningfulFuncName3,
default: defaultCase
}
return conditionObject[step](this);
}
To me, changing it like this makes it less complex to understand. Although to some will be less readable. Also it is a little faster.
function testFunc() {
var step = getModel('step');
if(step==1){this.editTask(); this.updateStep(0); return true;}
if(step==2){this.initTask; return true;}
if(step==3){this.name==='add' ? this.add() : this.edit();return true;}
return false;
}
I have the following code:
if (array.indexOf("undefined")===-1){
do something...
}
My initial arrays is this:
array=[,,,,,,];
It gets filled with values as the program goes on but i want the function to stop when there are no undefined spaces. The above syntax though is not correct. Anybody can tell me why.
Your code looks for the string "undefined". You want to look for the first index containing an undefined value. The best way is to use findIndex:
if(array.findIndex(x=>x===undefined) !== -1) {
//do something
}
for(var ind in array) {
if(array[ind] === undefined) {
doSomething();
}
}
Your check didn't work because you passed the string "undefined" instead the the value itself. Also, .indexOf() is designed to explicitly ignore holes in the array.
It seems wasteful to use iteration to detect holes in the array. Instead you could just track how many holes have been filled by using a counter, and execute your code when the counter matches the length.
Either way, the proper way to detect a hole at a particular index is to use the in operator.
Here's a class-based solution for reuse:
class SpacesArray extends Array {
constructor(n, callback) {
super(n);
this.callback = callback;
this.counter = 0;
}
fillHole(n, val) {
if (!(n in this)) {
this.counter++;
console.log("filling hole", n);
}
this[n] = val;
if (this.counter === this.length) {
this.callback();
}
}
}
// Usage
var sa = new SpacesArray(10, function() {
console.log("DONE! " + this);
});
// Emulate delayed, random filling of each hole.
for (let i = 0; i < sa.length; i++) {
const ms = Math.floor(Math.random() * 5000);
setTimeout(() => sa.fillHole(i, i), ms);
}
I am fairly new to JavaScript and I have a question regarding how to optimise if statements.
I will show you two scenarios.
//first
var number = 10;
var calculationOneResult = functionOne(number);
var calculationTwoResult = functionTwo(number);
if (calculationOneResult === true) {
//stuff
} else if (calculationTwoResult === true) {
//more stuffs
}
//second
var number = 10;
if (functionOne(number) === true) {
//stuff
} else if (functionTwo(number) === true) {
//more stuffs
}
Here is my question:
In the first scenario, I am calculating two times.
In the second one, if the first function returns true, will it calculate the second elseif statement or will it skip it after doing the stuff ?
The following code:
if(statement1) {
// stuff
} else if(statement2) {
// other stuff
}
is equivalent to
if(statement1) {
// stuff
} else {
if(statement2) {
// other stuff
}
}
as there is no elseif in JavaScript - see documentation.
So the answer is any function in statement2 will be simply skipped.
Nothing in an else clause executes if the if expression tests as true, so the second version of your code will definitely save a function call in such cases.
The jQuery documentation for the .toggle() method states:
The .toggle() method is provided for convenience. It is relatively straightforward to implement the same behavior by hand, and this can be necessary if the assumptions built into .toggle() prove limiting.
The assumptions built into .toggle have proven limiting for my current task, but the documentation doesn't elaborate on how to implement the same behavior. I need to pass eventData to the handler functions provided to toggle(), but it appears that only .bind() will support this, not .toggle().
My first inclination is to use a flag that's global to a single handler function to store the click state. In other words, rather than:
$('a').toggle(function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
});
do this:
var clicks = true;
$('a').click(function() {
if (clicks) {
alert('odd number of clicks');
clicks = false;
} else {
alert('even number of clicks');
clicks = true;
}
});
I haven't tested the latter, but I suspect it would work. Is this the best way to do something like this, or is there a better way that I'm missing?
Seems like a reasonable way to do it... I'd just suggest that you make use of jQuery's data storage utilities rather than introducing an extra variable (which could become a headache if you wanted to keep track of a whole bunch of links). So based of your example:
$('a').click(function() {
var clicks = $(this).data('clicks');
if (clicks) {
alert('odd number of clicks');
} else {
alert('even number of clicks');
}
$(this).data("clicks", !clicks);
});
Here is a plugin that implements an alternative to .toggle(), especially since it has been removed in jQuery 1.9+.
How to use:
The signature for this method is:
.cycle( functions [, callback] [, eventType])
functions [Array]: An array of functions to cycle between
callback [Function]: A function that will be executed on completion of each iteration. It will be passed the current iteration and the output of the current function. Can be used to do something with the return value of each function in the functions array.
eventType [String]: A string specifying the event types to cycle on, eg. "click mouseover"
An example of usage is:
$('a').cycle([
function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
}
]);
I've included a demonstration here.
Plugin code:
(function ($) {
if (!Array.prototype.reduce) {
Array.prototype.reduce = function reduce(accumulator) {
if (this === null || this === undefined) throw new TypeError("Object is null or undefined");
var i = 0,
l = this.length >> 0,
curr;
if (typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
throw new TypeError("First argument is not callable");
if (arguments.length < 2) {
if (l === 0) throw new TypeError("Array length is 0 and no second argument");
curr = this[0];
i = 1; // start accumulating at the second element
} else curr = arguments[1];
while (i < l) {
if (i in this) curr = accumulator.call(undefined, curr, this[i], i, this);
++i;
}
return curr;
};
}
$.fn.cycle = function () {
var args = Array.prototype.slice.call(arguments).reduce(function (p, c, i, a) {
if (i == 0) {
p.functions = c;
} else if (typeof c == "function") {
p.callback = c;
} else if (typeof c == "string") {
p.events = c;
}
return p;
}, {});
args.events = args.events || "click";
console.log(args);
if (args.functions) {
var currIndex = 0;
function toggler(e) {
e.preventDefault();
var evaluation = args.functions[(currIndex++) % args.functions.length].apply(this);
if (args.callback) {
callback(currIndex, evaluation);
}
return evaluation;
}
return this.on(args.events, toggler);
} else {
//throw "Improper arguments to method \"alternate\"; no array provided";
}
};
})(jQuery);
I want to use return false to break a .each() but also return a value at the same time. How can I do this?
Please refer to a work-around function to see what I am trying to do:
function HasStores(state) {
var statehasstores = false;
$(stores).each(function (index, store) {
if (state == store.state && store.category == "meyers") {
statehasstores = true;
return false; // break
}
});
return statehasstores;
}
What Id like to do in pseudo code is:
Function () {
for() {
if found {
return true;
}
}
return false;
}
You're doing it right...
Quote from http://api.jquery.com/each/
"We can stop the loop from within the callback function by returning false."
Be creative:
try {
$(stores).each(function (index, store) {
if(state == store.state && store.category == "meyers"){
throw store;
}
});
}
catch(e) {
// you got e with the value
}
No, I was just kidding, don't use this :). It came as an idea I liked to share.
Use a variable outside the loop to get the value and use it afterward.
var value;
$(stores).each(function (index, store) {
if(state == store.state && store.category == "meyers"){
statehasstores = true;
value = store; // added line
return false; //break
}
});
alert(value);
The way you're doing is just fine. I've tested on jsFiddle, see an example here.
It's not working for you? Can you show more context?
jQuery .each
Alternatively, you could use a for loop instead of each(), and just return the value.
What you're suggesting is the way to do it. I'd think of it less as a workaround and more as an idiom.
How about:
$.each( myObj, function( key, value ){
...
if( sthg... ){
myObj.somethingWentHorriblyWrong = true;
return false;
}
});
if( myObj.somethingWentHorriblyWrong ){
// ... do something, not forgetting to go:
delete myObj.somethingWentHorriblyWrong;
}
PS I was initially interested in what $.each(... actually returns. As it says on the relevant JQ page, "The method returns its first argument, the object that was iterated", but in fact the solution doesn't even require that you use that fact...
PPS Need a function that returns a value? Wrap in an outer function of course.
Okay I guess there's a little doubt about this point so maybe I'm making it clearer here :
When jquery doc says : "We can stop the loop from within the callback function by returning false." and you do :
Function () {
for() {
if found {
return true;
}
}
return false;
}
This doesn't mean that you're function will return true when find the searched element. Instead, it will always return false.
So to make your function work as you whish I propose to do so :
Function () {
variable found = false;
foreach() {
if found {
found = true;
return false; // This statement doesn't make your function return false but just cut the loop
}
}
return found;
}
Of course there are many other ways to perform this but I think this is the simplest one.
Coopa - Easy !
As others have noted from jQuery Each, returning false will only break from the loop not return the value, returning true however will 'continue' and immediately begin the next iteration. With that knowledge, you could somewhat simplify your code like this:
function HasStores(state) {
var statehasstores = false;
$(stores).each(function (index, store){
// continue or break;
statehasstores = !(state == store.state && store.category == "meyers"))
return statehasstores;
});
return !statehasstores;
}
This of course is a little silly using the double negative, and has the side effect of saving 'true' to statehasstores for every false iteration and vice versa, however the end result should be the same and you no longer have that if statement.