IE9 JavaScript array initialization bug - javascript

Apparently JS implementation in IE9 contains (IMO, critical) bug in handling array literals.
In IE9 in some cases this code:
var a = [1,2,3,4,];
will create array of length 5 with last element equals to undefined.
Here are two versions of my KiTE engine test pages:
http://terrainformatica.com/kite/test-kite.htm - works in IE9
http://terrainformatica.com/kite/test-kite-ie9-bug.htm - fails in IE9
The only difference is that first document contains data.contacts property initialized as [1,2,3,4] and second one as [1,2,3,4,].
Internal IE debugger reports that data.contacts array contains 5 elements in second case. Without debugger this code fails at line 98 in kite.js (trying to get property of undefined - fifth element of that data.content array )
Questions:
How and where people usually report bugs in IE?
Have you seen anything similar to this problem? I am looking for simplest case where this problem is reproducible.
Update: here is the test http://jsfiddle.net/hmAms/ where all browsers (IE9 included) agree on the fact that var a = [1,2,3,4,]; is of length 4.

A single trailing comma in an array literal should be ignored. Two trailing commas is an elision and should add one to the array's length. So:
alert( [1,2,3,4,].length ); // 4
alert( [1,2,3,4,,].length ); // 5
Some versions of IE (< 9?) treat the single trainling comma as an elison and incorrectly add one to length, so the results above are 5 and 6 respsectively. That is inconsistent with ECMA-262 ยง11.1.3 and therefore is a bug.
The purpose of an elision is to increase array length without creating a extra property or assigning directly to length, so:
var x = [,1,,];
is equivalent to:
var x = new Array(3);
x[1] = 1;
The result in both cases should be an array with length 3 and one property named '1' with value 1. The leading comma and trailing comma pair are elisions, they only affect the length, they do not create properties. IE interprets the leading comma correctly but incorrectly interprets both trailing commas as elisions, incrementing the length by 1 too many.
var x = [,1,,3,,];
var s = 'length: ' + x.length;
for (var p in x) {
s += '\nindex ' + p + ' has value ' + x[p];
}
alert(s);
The result should be:
length: 5
index 1 has value 1
index 3 has value 3
Incidentally, this bug has probably been around since IE allowed array literals, version 4 at least (1997?).

That's not a bug. That's exactly how it should behave. Microsoft did that on purpose. If you want an array with only 4 items, get rid of the last comma. Simple as that.
If the results you're after is to have an extra, undefined value at the end, you're in luck. Even without the comma, it'll be undefined. It, and every single number after 3.

Related

console.log("this.length--->" + this.length); this behaviour

I am trying to learn js basics.
I tried analysing the string count code but I am not sure about certain statements.
can you guys tell me why it's behaving in such a way?
this will help to understand better and in future, I can fix the issues by myself.
providing code below
String.prototype.count=function(c) {
var result = 0;
var i = 0;
console.log("this--->" + this); // how come this prints strings here, since we dont pass stings here
console.log("this.length--->" + this.length);
for(i;i<this.length;i++)
{
console.log("this[i]--->" + this[i]); // here we did not declare this an array anywhere right then how come its taking this[i] as s and stings also we did not put in any array
console.log("c--->" + c);
if(this[i]==c)
{
result++;
console.log("inside if result++ ---->" + result++);
// here it prints 1 and 3 but only two times s is present so it should print 1 and 2 right
}
}
console.log("out of for loop result--->" + result);
// how its printing 4 but s is present only two times
return result;
//console.log("out of for loop result--->" + result);
};
console.log("strings".count("s")); //2
output
this--->strings
this.length--->7
this[i]--->s
c--->s
inside if result++ ---->1
this[i]--->t
c--->s
this[i]--->r
c--->s
this[i]--->i
c--->s
this[i]--->n
c--->s
this[i]--->g
c--->s
this[i]--->s
c--->s
inside if result++ ---->3
out of for loop result--->4
4
Questions from your code example are basicly about 2 issues:
this - is JS keyword. You don't have to initialize it, it automatically defined everytime, everywhere in your JS code. And it could mean different things in different situations, but it's too advanced topic if you've just started learning JS. For now lets just say, that this refers to the context, that called count() function, which is the string "strings". So it has length and you can iterate over it as an array of characters
Why the count rose to number 4, when it should be 2? Because when you found a match between current letter of the string and the character "s" (line with this[i]==c) you increment the result by 1 using result++. But you do it also in your console.log right after it, so it gets increased by 2 every time it finds a match
console.log("this--->" + this); // how come this prints strings here, since we dont pass stings here
That prints a string because you have a string + something. This is called string concatenation and the result of string concatenation is a string. In this case, this is the object that called the count function which was a string.
console.log("this[i]--->" + this[i]); // here we did not declare this an array anywhere right then how come its taking this[i] as s and stings also we did not put in any array
this is a string. Bracket notation on a string will return the char at the provided index.
result++;
console.log("inside if result++ ---->" + result++);
// here it prints 1 and 3 but only two times s is present so it should print 1 and 2 right
the result++ notation is the same as result += 1, but result++ does the addition of 1 AFTER the value is evaluated while ++result will do it before. So in this example the order of events is:
add 1 to the result, then print the console.log, then add 1 to the result
Hope that helps!

google apps script counting characters in a cell

I am trying to find a way to adjust a merged cell group to show all text characters that were contained in the first cell when merged. I thought there would be a simple way to count the number of characters in that first cell and then I can adjust the height of a cell or cells by developing a formula (such as add .2 for each 30 characters).
I am using the following code to try and count the characters:
var tempValue;
var tempCount = 0;
tempValue = sprintSheet.getRange("D3").getDisplayValue();
tempCount = tempValue.length();
Unfortunately, I get the following error on the last line:
TypeError: Cannot call property length in object
I can't seem to make the transition from range / value to text to use the length property.
Short answer
Use tempCount = tempValue.length instead of tempCount = tempValue.length();
Explanation
Google Apps Script is based on JavaScript. The getDisplayValue() returns an a JavaScript string primitive. A primitive data type can use the the length property, and its called by using .length, (note that parenthesis are not used).
References
https://developers.google.com/apps-script/overview
The string length is available as a property rather than as a function call.
https://www.w3schools.com/jsref/jsref_length_string.asp
tempCount = tempValue.length;

Sync storage, max quota bytes per item and chunked data storage

I'm using a modified version of this code (Update: that answer has since been updated to use correct code, but this question still carries value since it contains relevant test cases and discussions for this problem) to store a single object after stringification in chunked keys inside of sync storage.
Note that sync storage has a maximum quota size per item. So, I have those maxLengthPerItem and maxValueLength variables.
function lengthInUtf8Bytes(str) {
// by: https://stackoverflow.com/a/5515960/2675672
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
var m = encodeURIComponent(str).match(/%[89ABab]/g);
return str.length + (m ? m.length : 0);
}
function syncStore(key, objectToStore, callback) {
var jsonstr = JSON.stringify(objectToStore), i = 0, storageObj = {},
// (note: QUOTA_BYTES_PER_ITEM only on sync storage)
// subtract two for the quotes added by stringification
// extra -5 to err on the safe side
maxBytesPerItem = chrome.storage.sync.QUOTA_BYTES_PER_ITEM - NUMBER,
// since the key uses up some per-item quota, use
// "maxValueBytes" to see how much is left for the value
maxValueBytes, index, segment, counter;
console.log("jsonstr length is " + lengthInUtf8Bytes(jsonstr));
// split jsonstr into chunks and store them in an object indexed by `key_i`
while(jsonstr.length > 0) {
index = key + "_" + i++;
maxValueBytes = maxBytesPerItem - lengthInUtf8Bytes(index);
counter = maxValueBytes;
segment = jsonstr.substr(0, counter);
while(lengthInUtf8Bytes(segment) > maxValueBytes)
segment = jsonstr.substr(0, --counter);
storageObj[index] = segment;
jsonstr = jsonstr.substr(counter);
}
// later used by retriever function
storageObj[key] = i;
console.log((i + 1) + " keys used (= key + key_i)");
// say user saves till chunk 20 in case I
// in case II, user deletes several snippets and brings down
// total no. of "required" chunks to 15; however, the previous chunks
// (16-20) remain in memory unless they are "clear"ed.
chrome.storage.sync.clear(function(){
console.log(storageObj);
console.log(chrome.storage.sync);
chrome.storage.sync.set(storageObj, callback);
});
}
The problem is in this line:
maxLengthPerItem = chrome.storage.sync.QUOTA_BYTES_PER_ITEM - NUMBER,
The problem is that 5 is the minimum NUMBER for which there's no error. Here's the sample code you can use to test my theory:
var len = 102000,
string = [...new Array(len)].map(x => 1).join(""),
Data = {
"my_text": string
},
key = "key";
syncStore(key, Data, function(){
console.log(chrome.runtime.lastError && chrome.runtime.lastError.message);
});
Using 4 yields MAX_QUOTA_BYTES_PER_ITEM exceed error. You can yourself adjust the value of len (to 20000, 60000 < 102000, etc.) to check my theory.
Question:
Why is the current method requiring exactly 5 as the minimum value? I know there's two quotes for stringification, but what about the other 3 characters? Where'd they come from?
Additionally, I've noticed that in textual Data like this one,
even 5 does not work. In the specific case above, minimum NUMBER required is 6.
Clarification:
The point of my question is not what are the other means to store data in sync.
The point of my question is why is the current method requiring exactly 5 (And why that textual data requires a 6.) Imho, my question is very specific and surely does not deserve a close vote.
Update: I've added new code which stores data based on measurement of length of UTF-8 bytes, but it still does not provide desirable results. I've also added code to more easily test my theory.
The problem is that Chrome applies JSON.stringify to each string chunk before storing it, which adds three \ characters to the first string (which, added to the known 2 for outer quotes, makes a full 5). This behavior is noted in the Chromium source code: Calculate the setting size based on its JSON serialization size (and the implementation does indeed compute size based on key.size() + value_as_json.size()).
That is, the value in key_0 is the string
{"my_text":"11111111...
But it is stored as
"{\"my_text\":\"11111111..."
The reason you need to account for the two outer quotes is the same reason you need to account for added slashes. Both are indicative of the output of JSON.stringify operating on a string input.
You can confirm that escape-slashes are the issue by doing
var jsonstr = JSON.stringify(objectToStore).replace(/"/g,"Z")
And observing that the required NUMBER offset is 2 instead of 5, because {Zmy_textZ:Z11111... does not have extra slashes.
I haven't looked closely, but the Lorem text contains a newline and a tab (see: id faucibus diam.\), which your JSON.stringify (correctly) turns into \n\t but then Chrome's additional stringify further expands to \\n\\t, for an extra 2 bytes you do not account for. If that gets chunked with two other quotes or other escapable characters, it could cause a chunk with 4 unaccounted-for bytes.
The solution here is to account for the escaping that Chrome will do upon storage. I'd suggest applying JSON.stringify to each segment when evaluating if it's too big, so that the correct number of bytes will be consumed by the chunking algorithm. Then, once you decide on a size that will not cause problems, even after being double-stringifed, consume that many bytes from the regular string. Something like:
while(lengthInUtf8Bytes(JSON.stringify(segment)) > maxValueBytes)
...
Note that this will automatically account for the two bytes from outer quotes, so there's no need to even have a QUOTA_BYTES_PER_ITEM - NUMBER computation. In the terms you've presented it, with this approach, the NUMBER is 0.
For some reason, the technique only works when we do this:
while(lengthInUtf8Bytes(JSON.stringify(JSON.stringify(segment))) > maxValueBytes)
here's a paste containing data that you can use to compare both this and #apsiller's original approach (and verify the fact that only the above approach works).
Here's the code I used to test all this stuff
I am not accepting either answer yet since neither of them provides an acceptable logic as to why only the above approach is working.
After carefully reading through this thread I finally was able to understand where the extra bytes come from. apsillers actually reference the part from the chromium code that holds the answer:
key.size() + value_as_json.size()
You have to account for the side of the key as well. So the working accurate check is:
while((lengthInUtf8Bytes(JSON.stringify(segment)) + key.length) > maxValueBytes)

JavaScript returns 'memory' as 0, even though I increase or decrease its value

Code: http://pastebin.com/xGa9VLDY
The question:
I am making a little calculator app to hone my JavaScript skills, and it turned out alright. However, the problem I am experiencing is that JavaScript returns my variable 'memory' as 0, even though the value is increased (or decreased), and I can't seem to figure out how, so the calculator is pretty much useless, since the equal button only returns 0. I've tried to use the console in Chrome to increase the value, just to test and this is my result:
memory + 5
5
But when I try to check the value of 'memory' again:
memory
0
Is it something I am missing, or is it just a stupid, little mistake?
What I've tried:
As you can see, I have now tried to store the value in localStorage, but to no avail, and I do not see what else I can do. I recently switched from
memory += textBox.value;
to
memory = memory + textBox.value;
but obviously, that didn't work either.
EDIT:
I have got a very strange problem now:
memory: 0
textBox.value: "6"
parseInt(textBox.value): 6
memory + parseInt(textBox.value): 6
typeof(memory): "number"
This is all the values when the textbox still is populated with a number, and this is these are values right after pressing the plus sign:
memory: NaN
textBox.value: ""
parseInt(textBox.value): NaN
memory + parseInt(textBox.value): NaN
typeof(memory): "number"
The console is giving you the result of your mathematical operation, not storing it anywhere.
So you start with memory containing the value 0. To make that 5, you need to add 5, and then store the result back in memory.
memory = memory + 5;
There is a shorthand for that:
memory += 5;
And also a shorthand for just adding 1 (because it's a very common task):
++memory;
although this is not considered best practise.
Regarding the textBox.value version, where you are doing this, the value property of a textBox is a string, not a numeric value. Even though 1234 looks like a number, it is actually the characters '1', '2', '3' and '4'. You could happily include 'a' or 'z'. To get a numeric value out that you can use in a mathematical expression, you need to parse the string:
Either
enteredValueInt = parseInt(textBox.value); // or
enteredValueFloat = parseFloat(textBox.value);
depending on whether you expect the textBox to have a whole number or a floating point value in.
Demo here: http://jsbin.com/yijezifowu/1/edit?html,js,output
EDIT: Re 'very strange problem':
You have a spurious call to emptyBox at the top of the mathStuff function in your pastebin code:
function mathStuff(operator) {
emptyBox();
//... stuff with operators
}
So you're beginning by clearing the textbox before attempting to retrieve the numbers.
memory + 5
is a calculation which yields the sum of the two values, which is 0 in your case, since memory is 0. You want to store the value in memory, but you need to assign the value to your memory variable, like this:
memory = memory + 5;
or, a shorthand version:
memory += 5;
Read more about assignment operators here.
Also, you must make sure the operator you are using will work well, as:
'0' + 5 === '05'
while
0 + 5 === 5

JavaScript if Statement .length not working. Why?

I have this if statement i have came up with here:
var TotalMoney=0;
var Orbs=0;
if (TotalMoney.length==2) {
Orbs+=1;
}
What this code is supposed to do is if the the "TotalMoney" value digit length equals 2,
example (the number 10 has 2 digits)
then it will add 1 "Orb" to the "Orbs" value. Currently, it does nothing. There is HTML and CSS linked to this code but i figured the problem is in this code as it works fine for everything else. Please fix it as i have been trying for hours. Thanks!
For my second question that i just found out with this code here:
var totalMoney=0;
var orbs=0;
if (totalMoney.toString().length==2) {
orbs+=1;
}
This works on getting the number value digits as 2 digits long. The problem now is that once it reaches 10, every time that number goes up (10-99) all the way up, it will add 1 orb each time. I only want it to add 1 orb only when it gets to the 2 digit number (10) and stops adding 1 orb after it reaches it. How can i achieve this? Thanks!
TotalMoney is a number, so it doesn't have a length property. You can check the length of the number by first converting to a string: TotalMoney.toString().length.
Number object in js has no length property, so TotalMoney.length return undefined.
If you want count digits you may use this:
if (TotalMoney.toString().length == 2) {
Orbs+=1;
}
But if TotalMoney will be negative, -1 for exmple, Orbs wil be incremented.
I think there are better way to find all 2-digits number:
if (TotalMoney>9 && TotalMoney<100) {
Orbs+=1;
}
TotalMoney is numeric
so to find its length use this code
TotalMoney.toString().length;
Instead of
TotalMoney.length;
so try to modify your code as below:
var TotalMoney=0;
var Orbs=0;
if (TotalMoney.toString().length==2) {
Orbs+=1;
}
Length is property of array & string.It can not be applied on other variables.
If you want to count number of digits you can do
if(TotalMoney>9)
Or you can convert it to string then check it's length
if(TotalMoney.toSting().length>2)
here are some ideas and general comments on your code.
// recommended to start with lower case. upper case such as 'TotalMoney'
// is stylistically reserved for constructors.
var totalMoney=0;
// again - changing form Orbs to orbs;
var orbs=0;
// recommended to use '===' until you are more experienced with JavaScript and
// know about the 'gotchas' that '==' might produce.
// you will be able to check the length of totalMoney only after converting it to a string.
if (totalMoney.toString().length === 2) {
orbs+=1;
}
Finally, totalMoney of 0 will not add one to orbs. But totalMoney of 10, as you mentioned, will.

Categories