I'm getting a nonsensical error message in IE8, tell me that the constant '2' is null or not an object. The line of code is:
if (! localtree[idx][2]) {
I also tried coding it like this:
if (localtree[idx][2] == 0) {
The value in the array at that location is always zero (for now).
How can IE8 think that the number 2 is null? I'm mystified!
The exact error is:
Message: '2' is null or not an object
Has anyone seen this?
EDIT : This is a very misleading error message. See my answer below for what actually went wrong.
This is a very confusing error message. It turned out that I was stepping one element beyond the end of the array. 'idx' was referencing a non-existent value that I was attempting to treat as an array reference (with the [2]).
Rather than telling me that '2' was null, it should have said that 'localtree[idx]' was null.
The root cause of this was that I had a trailing comma where I defined the array, leading to an extra, null value in the array. In firefox, trailing commas are ignored (like in perl), but in IE, they are significant.
the constant '2' is null or not an object
if (! localtree[idx][2]) {
JavaScript doesn't have constants, at least not yet. And you aren't checking a number, but a member of an array, i.e.: the variable with index number 2 of object localtree[idx] (where idx must contain a string to refer to an object property or an index number to refer to an array).
Related
I am fairly new to JavaScript and am going over some code. However there is one bit i am unsure about.
product = product !== null && product[0] !== null && product[0].id || "";
Where product is an array. Could someone please help me understand what this does. Any help would be much appreciated. Many thanks =)
One way to understand what this does it to run it and observe the result.
Here's a JSBin showing 3 examples - which produce a different outcome, depending on the initial value of product - https://jsbin.com/roruvecubi/edit?js,console
To further clarify this with an explanation...
It will attempt to evaluate that all the following premises are true at the same time and re-assign product to the value of id of the first object found (if these are found) or an empty string if otherwise.
product array is NOT null
AND
First element of product array is NOT null
AND
First element of product array is an object containing a truthy key-value pair with key id. I.e. First element could like this:
{
id: "someValue" // product[0].id would be a truthy value in this case
}
AND
If 3.0 is true - assign value of id. If 3.0 is NOT true (id: does not contain a truthy object, array, number, string, true - Google the precise definition of truthy), then just assign empty string "" and thus result will be product = "";
product !== null it checks if product is null if it is it will stop right here and not do the other calculations (this is practiced so you won't get undefined, in this case, hmm null)
product[0] !== null checks if null, so when .id you won't get an error can't find id of undefined / null
let usr = null
console.log(usr.id)
GIVES ERROR Uncaught TypeError: Cannot read property 'id' of null
enter code here
With a few words, these are some practices to check if the VARIABLE has unwanted values to stop the calculations right there and not get errors.
Some prefer to try catch v--
Suppose I have the following event handler:
function handleCsvDump(e) {
console.log(e.currentTarget.getAttribute('download'));
e.currentTarget.download = undefined;
console.log(e.currentTarget.getAttribute('download'));
console.log(e.currentTarget.getAttribute('download') === undefined);
The information logged to the console when the corresponding button is clicked is:
mycsv.csv
undefined
false
Why is the last value false? Since e.currentTarget.getAttribute('download') is undefined, shouldn't it be true? If this is the wrong way to go about this, how can I test whether a variable is undefined?
You have to be careful when setting things this way, often things are expected to be strings, and if you set a value that isn't a string, it will first be coerced to a string, then assigned.
The download attribute is indeed a DOMString, meaning that anything you assign to it will first be coerced to a string if it isn't already, so when you assign undefined, it's actually first coerced to "undefined" and that is stored.
When you get it back out, and compare it to undefined, you're actually doing:
console.log("undefined" === undefined)
Hence getting false. If you do actually want to remove it, which is implied by wanting to set it to undefined (or null), you can instead use removeAttribute:
e.currentTarget.removeAttribute('download')
I am looking at the Mozilla Developers website on the concept of the delete operator. In the last sub section of the page referring to “Deleting array elements” two similar scripts are shown, but the only difference in the scripts is how they modified the array.
In the first script, I quite don’t understand why “if” statement does not run. My current understanding is that delete operator “removes the element of the array”. If I were to type trees[3] in the console, it would return undefined in the console.
var trees = ["redwood","bay","cedar","oak","maple"];
delete trees[3];
if (3 in trees) {
// this does not get executed
}
The second script seems to "mimic" the delete, but not literally. Undefined is assigned to trees[3]. It doesn’t make sense to me how the “if” block runs in this script, but the first example does not. Can anyone help me understand this JavaScript behavior?
var trees = ["redwood","bay","cedar","oak","maple"];
trees[3] = undefined;
if (3 in trees) {
// this gets executed
}
There is a huge difference between the two methods you are trying:
Method 1:
You are deleting, destroying, completely removing the key 3 in your array called tree, hence there is no 3 in tree left, and the if check returns false.
Method 2:
You are assigning a new value to the key 3, which is undefined, there is still 3 in tree, and the if check returns true.
In your second example the key 3 still exists. It just holds a value that happens to be undefined. It IS confusing, but that's just the way Javascript is.
The in operator just checks if the key exists, not if the value is defined.
If you were to output the whole arrays after each of your "deletions" the first example would display something like this:
["redwood", "bay", "cedar", 4: "maple"]
Whilst the second example would print out something like this:
["redwood", "bay", "cedar", undefined, "maple"]
So as you can see, in your first example the key is completely missing and it continues with the next key which is 4. In the second example the key still exists, but it's value is set to undefined.
There is a difference between undefined which is set by the user and undefined which the javascript engine returns once something is actually undefined, meaning doesn't exist.
javascript can tell the difference between the two.
So in your example, when you do this:
var trees = ["redwood","bay","cedar","oak","maple"];
trees[3] = undefined;
if (3 in trees) {
console.log("hi");
}
javascript can tell that property 3 exists, but it was set to undefined by the user.
to prove so you have the following:
if (5 in trees) {
console.log("hi");
}
the property 5 of the array was never created, javascript knows it's undefined
by lack of creation and regards it as a property which doesn't exist, and therefore doesn't display the "hi"
if(3 in tree) {
//Stuff that won't get executed
}
is in fact correct, the thing is that in operator in Javascript does not work like in python, it simply checks if an object has a proprety. An array in javascript has a proprety 0, just like a string has a proprety 2 with the value someString[2].
the difference between delete object[prop]; and object[prop] = undefined; can be seen through object.hasOwnProperty(prop); or iterating through values or props of the object.
I have been looking through some of the jQuery source and I ran across the merge function. Here is the source code for it:
function merge(first, second) {
var l = second.length,
i = first.length,
j = 0;
if (typeof l === "number") {
for (; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
}
first.length = i;
return first;
}
While I understand the code, something doesn't make sense to me. Particularly the if (typeof l === "number") part. I have tried passing an array to this function when I have manually changed the .length property to something like "3" and checked it's type and I still get a type of number.
My question is when would the length property ever not be a type of number in JavaScript arrays?
The merge function you're looking at is a public jQuery method: jQuery.merge() or $.merge(). So you might think that reading the jQuery.merge() documentation would shed some light on this question.
Unfortunately, it doesn't, at least as of this writing.
As explained in the other answers and comments, this test for a numeric length property will always succeed when second is an Array, because an Array will always have a numeric length. The only possible reason to have this test in the code is to handle the case where second is not an Array.
But the documentation doesn't mention this case at all. It only discusses cases where both first and second are both native JavaScript Array objects, not "array-like" objects.
If this documented use was all this function did, it would be pretty useless, since JavaScript provides a perfectly good native array.concat(array) method that can be used instead of jQuery.merge().
So it would seem that the real purpose of this function must be in its undocumented use. Let's try it out in the Chrome console on the jQuery.merge() doc page. Open the developer tools, select the Console tab, and enter an example similar to the ones in the doc:
jQuery.merge( [ 'a', 'b' ], [ 'c', 'd' ] )
and then try the same thing with .concat():
[ 'a', 'b' ].concat([ 'c', 'd' ])
They will both log exactly the same thing:
["a", "b", "c", "d"]
Since this is jQuery, the most likely case of an "array-like" object would be a jQuery object. For example, that doc page currently has three <h3> elements on it, so we can get them with:
$('h3')
That will log:
[►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
That isn't an array, but it looks a lot like one, and it does have a numeric length property which we can check with:
typeof $('h3').length
So let's try it with concat:
['x'].concat( $('h3') )
Oops. That should give us an array of four elements, 'x' followed by the three DOM elements. Instead, i gives us an array of two element, with the jQuery object as the second element:
[►e.fn.init[3] ]
It looks like jQuery's "array-like" object isn't array-like enough for .concat().
Oddly enough, some other array methods do with work jQuery's array-like object, such as .slice():
Array.prototype.slice.call( $('h3'), 1 )
That logs the correct result, an array of two elements:
[►<h3>…</h3>, ►<h3>…</h3>]
So let's try $.merge():
$.merge( ['x'], $('h3') )
Sure enough, that logs what we expected:
["x", ►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
There's also another way to do this. jQuery provides a .toArray() method, so we can do the same thing by combining that with .concat():
['x'].concat( $('h3').toArray() )
That logs the same thing as the $.merge() call.
What if first is a jQuery object?
$.merge( $('h1'), $('h3') )
That works too:
[<h1 class="entry-title">jQuery.merge()</h1>,
►<h3>…</h3>, ►<h3>…</h3>, ►<h3>…</h3>]
So it would appear that this is the purpose of this method: to do array.concat()-like operations on jQuery objects.
But wait! This still doesn't answer the question of why that extra code is in there for the non-numeric .length. After all, a jQuery object does have a numeric .length, so we still haven't exercised the code for the non-numeric case.
So this part of the code must be there for some purpose other than handling jQuery objects. What could that be?
We can find out a bit more by going to the jQuery source repository and locating the file in the src directory that has the merge: code (it happens to be core.js). Then use the Blame button and search within the page for the merge: code to see what the commit comment for the code is. Again, the code we're wondering about now is the else clause, where typeof l is not a number.
The commit was by John Resig on 2009-12-09, with a comment "Rewrote merge() (faster and less obtuse now). Fixed #5610." There's a bit of controversy in the discussion on that page:
Well, your version is faster only in the special (very rare) case of overwritted length property. In all other cases, my one was little faster.
Also you are not considering the case obj.length = new Number(N) - but yeah it is not so relevant I suppose.
This helps explain it a little, but what is issue #5610? Maybe that will tell us. Don't see an issue tracker on the GitHub repo? jQuery has its own issue tracker and if we search there for #5610 (or do a Google search for jquery issue 5610, we finally find issue #5610:
MAKEARRAY CAN CAUSE BROWSER CRASH ON NON ARRAY OBJECTS WITH LENGTH PROPERTY
Description
I haven't tested this with 1.4
For an object with a length property that casts to a non-zero positive integer, makeArray will create an array with that length filled with undefined.
>>> jQuery.makeArray({'length': '5'})
[undefined, undefined, undefined, undefined, undefined]
If the length property is zero, negative or a decimal then Firefox and Safari both hang. (FF shows the unresponsive script error, Safari hangs until it crashes)
>>> jQuery.makeArray({'length': '0'})
>>> jQuery.makeArray({'length': '5.2'})
>>> jQuery.makeArray({'length': '-3'})
You could be passing an object to the function, e.g.
merge ({ length: 'mylength' }, { length: 'mylength' });
if second is undefined or null or an object not supporting length, l will not be a number. I cannot say why second might be undefined or null as it's being passed from the caller.
I've tested this only in Firefox, but apparently you can use an empty string as a key to a property in an object. For example, see the first property here:
var countsByStatus = {
"": 23, //unknown status
"started": 45,
"draft": 3,
"accepted": 23,
"hold": 2345,
"fixed": 2,
"published": 345
}
In skimming through the EcmaScript specs, it appears that (at least in 5), property keys are defined as strings, and strings as 0 or more characters. This implies that an empty string is a valid property name according to the specs.
Anyway, I'm tempted to use this in a section of code where I'm calculating summaries of some counts by the status of a data item (similar to what I've shown above). There are some items which might not have a status, and I need a placeholder for those. Since statuses are user-definable, I don't want to risk using a dummy word that might conflict.
It seems so simple and elegant, in looking at the data I can easily tell what the blank string would mean. It also makes the code a little bit more efficient, since the empty string would be the exact value of the status in the items without a status.
But at the same time, my instincts are telling me that something is wrong with it. I mean, apart from the chance that some browser might not support this, I feel like I've encountered a bug in JavaScript that will be fixed some day. But, at the same time, that's the same feeling I once had about a lot of other JavaScript features that I now use every day (such as the time I discovered that && and || returns the value of one of the operands, not just true or false).
An object's key must be a string, and the empty string ('') is a string. There is no cross browser issue that I've ever come across with empty strings, although there have been very few occasions where I thought it was acceptable to use an empty string as a key name.
I would discourage the general usage of '' as a key, but for a simple lookup, it'll work just fine, and sounds reasonable. It's a good place to add a comment noting the exceptional circumstance.
Additionally, during lookup you may have issues with values that are cast to a string:
o = {...} //some object
foo = 'bar';
//some examples
o[foo] //will return o['bar']
o[null] //will return o['null']
o[undefined] //will return o['undefined']
If you'd like to have null and undefined use the '' key, you may need to use a fallback:
key = key || '';
If you might have non-string values passed in, it's important to cast too:
key = key || '';
key = '' + key;
note that a value of 0 will turn into '', whereas a value of '0' will stay '0'.
In most cases, I find I'm picking a pre-defined value out of a hashtable object. To check that the value exists on the object there are a number of options:
//will be falsey if the value is falsey
if (o[key]) {...}
//will return true for properties on the object as well as in the prototype hierarchy
if (key in o) {...}
//returns true only for properties on the object instance
if (o.hasOwnProperty(key)) {...}
Technically, there is nothing wrong and you can savely use it on any js engine (that I'm aware of). Since ECMAscripts spec says any object key is a string, it of course can also be an empty string.
The only caveat is, that you'll never be able to access that property with the dot notation
countsByStatus.;
will lead to a syntax error of course, so it always needs to be
countsByStatus[''];
That much about the technical part. If we talk about the convinient part, I'd vote for a very clear no, never use it.
It'll lead to confusion and as we all know, confusion is the enemy.
The problem is that since the statuses are user-defineable there is nothing stoping the user from also using the empty string as a status, thus ruining your logic. From this point of view what you are doing is no different then just using an ugly custom name like __$$unknown_status. (Well, I'd say the ugly custom name is more descriptive but to each its own...)
If you want to be really sure the "unknown" property does not collide you need to keep it separate:
var counts = {
unknownStatus: 23,
byStatus: {
"": 17, //actual status with no name, (if this makes sense)
"started": 45,
"draft": 3,
"accepted": 23,
"hold": 2345,
"fixed": 2,
"published": 345
}
};
I think it's ok. "" has semantics in your application, and its valid javascript. So have at it.
Note that
x."" = 2;
will error out, so you need to use syntax like
x[""] = 2;
Is "unknown status" a null value or is your status field "not null"?
In the first case I'd say you will have to use a separate counter, in the second I'd say that "empty" is a perfectly valid status - just use the word "unknown" for output instead of "". This might only lead to confusion when your user uses the same word as a status type, but to prevent that you only can use a different visual style for "unknown status" output text.