Since jQuery 3 .outerHeight() returns undefined instead of null if called on a non-existing element. This causes problems when adding up heights of elements that don't exist, because number + undefined now results in NaN. While prior to jQuery 3 number + null would return number.
var lorem = $('#div1').outerHeight() + $('#div2').outerHeight();
Returns NaN if for example #div2 does not exist.
Potential solution:
undef2null = function(myvar) {
if (myvar === undefined) {return null;}
else {return myvar;}
}
Would turn above code into:
var lorem = undef2null($('#div1').outerHeight()) + undef2null($('#div2').outerHeight());
Is there a more elegant solution than this?
You can guard against an undefined or null value using the || operator:
($('#div1').outerHeight() || 0)
...which is less typing than calling a function on each of the potentially problem values (although obviously you could make a one-line function out of that if you wanted to).
Or instead of a function that checks a single value to see if it is undefined you could write a function that adds up all supplied arguments, checking each:
function addNumbers() {
var result = 0;
for (var i = 0; i < arguments.length; i++)
result += arguments[i] || 0;
return result;
}
console.log( addNumbers(1,2,3) );
console.log( addNumbers(1,undefined,3) );
console.log( addNumbers(1,undefined,null) );
(Note that the code I've shown doesn't actually test the type of the supplied values, so if you called it with strings or whatever it would happily concatenate those into the result. You could add an extra test for non-numeric/undefined/null values if desired.)
It seems strange to write a method to turn undefined to null when you really want to treat both as zero.
To coerce both undefined and null to zero, you can
someValThatMightBeNullOrUndefined || 0
Related
I am trying to write a function to get the length of a string without using the .length property nor any loops or built in methods. I am using recursion, however, the stopping statement is not evaluating and causing a stack overflow. I tried console logging string[i] and sure enough, once the length is reached, it console logs "undefined" but the if statement still won't evaluate.
const getLength = (string, i = 0) => {
if (string[i] === 'undefined') return 0;
return 1 + getLength(string, i+1);
}
console.log(getLength("what is going on??")); //18
the if-state should be string[i] === undefined
Your test for undefined is not working
You can test for falsy or more detailed
Here is just a falsy one, it will fail on falsy values like 0
const getLength = (string, i = 0) => string[i] ? 1 + getLength(string, i+1) : 0;
console.log(getLength("what is going on??")); //18
In the for loop: counter < (x.lenght) is spelled wrong, but the function returns zero. When corrected to x.length the function returns the correct number of Bs, 3. 1) Why is zero being returned? 2) Why does javascript not catch this error? 3) For the future, anything I can do to make sure these types of errors are caught?
function countBs(x){
var lCounter = 0;
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
Accessing x.lenght is returning undefined causing the for loop to terminate immediately. Therefore the initial value of lCounter is returned.
You can check for the existence of a property in an object by using the in keyword like so:
if ( 'lenght' in x ) {
...
x.lenght is returning undefined. Comparison operators perform automatic type juggling, so undefined is converted to a number to perform the comparison, and it converts to NaN. Any comparison with NaN returns false, so the loop ends.
Javascript doesn't catch this error because it uses loose typing, automatically converting types as needed in most cases.
There's no easy way to ensure that typos like this are caught. A good IDE might be able to detect it if you provide good type comments.
JavaScript does all kind of crazy coversions, instead of throwing an error: https://www.w3schools.com/js/js_type_conversion.asp
'undefined' in particular becomes NaN when necessary (very last line of the very last table), which results in 'false' when compared to a number (regardless of <, >, <=, >=, == or !=, they all fail, NaN does not even equal to itself).
If you want to catch or log an error to make sure your variable property is defined. Please see code below:
function countBs(x){
var lCounter = 0;
if(typeof x.lenght == 'undefined')
{
console.log('Undefined poperty lenght on variable x');
return 'Error catch';
}
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
To catch this particular error, set lCounter to -1 instead of 0.
That will ensure that the loop will run at least once if the for condition is correct.
You can return (or throw) an error if the loop isn't entered.
Otherwise, return lCounter + 1 to account for the initialization of -1.
function countBs(x) {
var lCounter = -1;
for (var counter = 0; counter < (x.lenght); counter++) {
if((x.charAt(counter)) == "B") {
lCounter++;
}
}
if(lCounter == -1) {
return 'Error';
} else {
return lCounter + 1;
}
}
I hope I just missing something simple, but I for the life of me cant see why this doesn't work:
// GET ALL SHEET NAMES FROM THE SPREADSHEET AND PUT INTO AN ARRAY
function sheetNames() {
var out = new Array()
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheets.length ; i++) {
out.push( [ checkSheetName(sheets[i].getName()) ] )
}
Logger.log("before : " + out);
var listOfsheets = out.filter(function(x){ return (x !== (undefined || null || '')); });
Logger.log("after : " + listOfsheets);
return listOfsheets ;
}
// SEARCH ALL SHEETS AND ONLY SELECT THEM IF THEY HAVE THE TEXT "REPORT_" IN THE NAME
function checkSheetName(sheetName) {
var checkFor = 'Report_'
if (sheetName.indexOf(checkFor) >= 0) {
return sheetName;
} else {
sheetName = '';
return sheetName;
}
My logger shows the exact same result before and after its been put through the filter. An error would be great, but runs fine... just doesn't seem to do anything.
I suppose another question would be, can I put something in the initial getSheets() so that it only gets what I need in the first place? But Even if that is possible, I'm still curious about why my filter doesn't work
x is an array rather than an array-item string, make it
out.push( checkSheetName(sheets[i].getName()) ); //observe that array wrapping is removed
You can shorten your code by doing (assuming that sheets is array-like, not a direct array)
return Array.from( sheets ).filter( x => !!checkSheetName( x.getName() ) );
If sheets is an array then make it
return sheets.filter( x => !!checkSheetName( x.getName() ) );
or even without checkSheetName
return sheets.filter( x => !!( x.getName().indexOf( "Report_" ) != -1 ) );
out.filter(function(x){ return (x !== (undefined || null || '')); });
Your filter function reduces to
out.filter(function (x) { return (x !== ''); });
because the engine looks inside the parentheses and parses from left to right:
(x !== (undefined || null || ''))
// undefined is falsy, replace with expression on the right
(x !== (null || ''))
// null is falsy, replace with expression on the right
(x !== '')
Now look at what you're filtering:
out.push( [ checkSheetName(sheets[i].getName()) ] )
Here, you push a new array with one element onto the out array. Drop the square brackets to just push the name.
Since x is always an array, it is never the empty string, and your filter filters out nothing. Even if you drop the brackets making x an array, if x is null or undefined, it will still pass your filter. Replace your filter with
out.filter(Boolean);
This will coerce your array value to a boolean. The empty string, null, and undefined are all falsy and return false. Other strings are truthy and return true, the exact filtering you need.
I have an object that I am creating that could potentially have undefined properties.
Is there a more concise way to set the property than what I am doing below:
var ruleObj = {
factor: (ruleArray[2] ? ruleArray[2].trim() : null),
resultElseTarget: (ruleArray[10] ? ruleArray[10].trim() : null)
}
Notice how I have to repeat the variable after the ternary operator twice. The reason I'm asking is that I've run into this same type of problem several times and it doesn't seem like the best way to handle it.
Here’s a function that wraps another function to do nothing on null and undefined values:
const liftMaybe = f => x => x == null ? null : f(x);
Then you can define a trim that does nothing to undefined:
const trimMaybe = liftMaybe(x => x.trim());
and make use of it:
var ruleObj = {
factor: trimMaybe(ruleArray[2]),
resultElseTarget: trimMaybe(ruleArray[10]),
};
It differs from your original in its handling of empty strings, but I don’t know if that was intentional or if it’s even relevant.
Conciseness is one thing but with Javascript the bigger concern is readability and type checking.
In your example, if the value of ruleArray[2] is a boolean then it'd evaluate to false and set factor to null. Maybe that's what you want but just looking at your example code right now, I'd assume your ruleArray contains bools and not potential undefines.
The better way is to write a function to do null check
EDIT: someone was faster than me :) https://stackoverflow.com/a/46436844/643084
EDIT2: the other answer is great but i'd like to just make a note. null should not be treated the same as undefined even though they evaluate the same most of the times.
Some things:
Since you're checking indexes, you'd need to make sure that you have a length of at least the size you want. Otherwise ruleArray[10] can throw you and out of range error.
Assuming you are certain that you have the right number of elements in your array, you can use this to check a var for undefined, this is common to check incoming arguments (say you had something like function ( arg1, arg2 ) ):
arg1 = arg1 || 'some_default';
In your case, again assuming your array is long enough:
factor: ( ruleArray[2] || 'some other default' );
(Why would you set it to null if that's what you are trying to avoid).
If you're wondering, "is there a way to access an index that doesn't exist and just return null", the answer is "maybe...but I wouldn't".
Note, if the value is indeed falsy (say, 0, '', or false), you may not get what you expect, in which case you'd want to check for something more explicit, like null.
I get a lot of use of out the terse "something = thisValIfNotFalsy || someOtherDefaultVal. Like anything though careful when and where, etc.
You could do something like:
var ruleArray = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
function formatRule(rule) {
if (!rule) return null
return rule.trim()
}
var ruleObj = {
factor: formatRule(ruleArray[2]),
resultElseTarget: formatRule(ruleArray[10])
}
console.log(ruleObj.factor)
console.log(ruleObj.resultElseTarget)
We created a pure function that is tasked with producing either null or a trimmed value, which avoids duplicating this logic elsewhere.
Is there a more concise way
So far all answers seem to assume your input is either a string or null / undefined. For me I'd say the check for null / undefined is the wrong way round. You can only call trim on a string, so why not check it's a string?. It would also mean NaN / Numbers / Arrays etc, would not error. I'm assuming what your wanting this function to do is trim a string if it's a string, so I would also say you should pass the original value if not a string.
Maybe that's what #zaftcoAgeiha meant when he talking about not treating null & undefined the same.
Anyway, here is an example. Notice how numbers are still passed, but hello gets trimmed.
const ruleArray = [];
ruleArray[2] = null;
ruleArray[5] = 7;
ruleArray[7] = 'Helllo ';
const trimIfString = (x) => typeof x === 'string' ? x.trim() : x;
var ruleObj = {
factor: trimIfString(ruleArray[2]),
resultElseTarget: trimIfString(ruleArray[5]),
hello: trimIfString(ruleArray[7])
}
console.log(ruleObj);
You can use a function pattern and set default parameter with AND && operator when passing the parameter to check if variable is defined, if not set element value to null. You can include further checks to determine if variable is passed is a string.
let ruleArray = [];
ruleArray[10] = "def ";
let ruleFn = (factor = null, resultElseTarget = null) =>
({factor, resultElseTarget});
let ruleObj = ruleFn(ruleArray[2] && ruleArray[2].trim()
, ruleArray[10] && ruleArray[10].trim());
console.log(ruleObj, ruleObj.factor === null);
These are the instructions to the script I have to write:
function longest(first, second) {
if (first.length >= second.length) {
return first;
} else {
return second;
}
Use the || operator to specify default values for first and second in
the function. If one or both parameters are not specified, the empty
string should be used as the default value.
Once you make your changes, you should be able to test the function as
follows:
console.log(longest('Alice')); // second is undefined - second defaults to the empty string
//Alice
console.log(longest()); // both first and second are undefined - both default to the empty string
//(an empty string)
console.log(longest('hi','hello'));
//hello
console.log(longest('hi', 'me'));
//hi
console.log(longest(''));
//(an empty string)
I don't even know where to begin. Can someone shed some light for me?
Try this:
function longest(first, second) {
var firstDefault = '';
var secondDefault = '';
first = typeof first !== 'undefined' ? first : firstDefault;
second = typeof second !== 'undefined' ? second : secondDefault;
if (first.length >= second.length) {
return first;
} else {
return second;
}
}
Default value
first = first || '';
Or, but that is not defined in the requirement
first = (typeof first !== 'undefined') ? first : '';
Apply this to both arguments
The issue with
function longest(first, second) {
if (first.length >= second.length) {
return first;
} else {
return second;
}
}
if you call it as longest('Alice') is that it spews out an error:
TypeError: Cannot read property 'length' of undefined
because second is undefined, and properties of undefined like .length can not be read.
undefined is actually a thing in Javascript rather than an automatic error like in many other languages. We'll come back to that soon...
The purpose is to get you to thinking about how to fix the function. If the function assigned a blank string in place of undefined, the blank string would have a length, 0, that could be read.
In Javascript the || operator can be used to assign default values to missing variables as follows:
function longest(first, second) {
var defaultValue = '';
var first = first || defaultValue;
var second = second || defaultValue;
if (first.length >= second.length) {
return first;
} else {
return second;
}
}
Now if either first or second is undefined, they will be replaced locally with the value of the defaultValue variable, which here is set to the empty string ''.
The empty string is not an undefined value, it is a string that contains no characters. It has a length, and that length is zero. Now the if statement will not fail when an undefined value is passed.
Now longest('Alice') yields 'Alice'
Bugs
Unfortunately the assignment as shown does not teach you enough about Javascript. It is probably worth knowing about a peculiar Javascript feature: any property whether it is called 'length' or something else can be read from existing objects that do not have that property. This may lead to undesired behavior. The result is a value called undefined, which is a value that things can be in Javascript.
When undefined is compared with a number, the result is always false. Mathematically that's normally impossible. We normally think that if x>1 is false, then x<1 must be true or x is 1. That kind of logic does not work for undefined.
When undefined is compared with undefined the result is also false, unless it is an equality test which is true.
Why does this matter? It relates to bugs in the longest() function above. Number inputs are one example. Strings representing numbers have a length but numbers do not have a length. Reading the length of a number yields undefined. And comparing undefined with a defined number is false. That means we can do this:
longest(1,100) returns 100 Correct.
but
longest(100,1) returns 1 OOPS.