Prototype object when looping through array? - javascript

I'm getting confusing results when looping through an array.
Filling the array looks like
var tables = [];
// ...
// t is table as jQuery object
tables[t.attr('id')] = t;
Later, when looping through the tables array, I'm getting one more element than I've actually added. The program breakes when the other object reaches t.removeClass()
for (t in tables) {
var t = tables[t];
t.removeClass(...);
}
Visual Studio Debugger describes the other object as "clone", which is the first method of the prototype object/property(?).
tables
[prototype]
[Methods]
clone
...
[prototype]
MyTable0
MyTable1
I've read that every javascript object comes with a prototype property but why is prototype here treated as an object?

Note : if your id aren't numbers, then you don't need an array, look at the other answer.
To loop over an array, don't use the for..in construct.
Use
for (var i=0; i<tables.length; i++) {
var t = tables[i];
t.removeClass(...);
}
or if you don't care for IE8 :
tables.forEach(function(t) {
t.removeClass(...);
});
or with jQuery :
$.each(tables, function(_,t){
t.removeClass(...);
});
Side note : it looks like somebody poorly enriched the prototype of the native Array class. It's generally seen as bad practice. Doing so and making the property enumerable is even worse. You should consider dropping or fixing the library you use.

Your declaration for tables should be:
var tables = {};

Related

Efficient memoization of object arguments

Summary: Is there a faster way to hash objects than JSON.stringify?
Details: I have a Ruby and JavaScript library (NeatJSON) that provides pretty-printing of JavaScript values. I recently fixed a problem where deeply-nested objects caused O(n!) performance (n being the nesting level) using memoization based on the object being serialized and the indentation amount.
In Ruby, the fix was really easy, because you can index hashes by arrays of unique sets of objects:
build = ->(object,indent) do
memoizer[[object,indent]] ||= <all the rest of the code>
end
In JavaScript, however, I can't index an object by another object (in a unique way). Following the lead of several articles I found online, I decide to fix the problem generically, using JSON.stringify on the full set of arguments to the function to create a unique key for memoization:
function memoize(f){
var memo = {};
var slice = Array.prototype.slice;
return function(){
var args = slice.call(arguments);
var mkey = JSON.stringify(args);
if (!(mkey in memo)) memo[mkey] = f.apply(this,args);
return memo[mkey];
}
}
function rawBuild(o,indent){ .. }
var build = memoize(rawBuild);
This works, but (a) it's a little slower than I'd like, and (b) it seems wildly inefficient (and inelegant) to perform (naive) serialization of every object and value that I'm about to serialize smartly. The act of serializing a large object with many values is going to store a string and formatting result for EVERY unique value (not just leaf values) in the entire object.
Is there a modern JavaScript trick that would let me uniquely identify a value? For example, some way of accessing an internal ID, or otherwise associating complex objects with unique integers that takes O(1) time to find the identifier for a value?
If you are looking to memoise your objects by identity (not by content), then you'll want to use a WeakMap which is designed for exactly this purpose. They don't work for primitive values though, so you'll need a different solution for such arguments.
Using #Bergi's suggestion of a WeakMap I found out about Map, which allows using any value type as the key (not just objects). Because I needed a compound key—uniquely memoizing the combination of the value passed in and the indentation string—I created a hierarchical memoization structure:
function memoizedBuild(){
var memo = new Map;
return function(value,indent){
var byIndent=memo.get(value);
if (!byIndent) memo.set(value,byIndent={});
if (!byIndent[indent]) byIndent[indent] = rawBuild(value,indent);
return byIndent[indent];
}
}
This proved to be about 4× faster than the memoization code I had been using when serializing a large 270kB JSON object.
Note that in the above code I'm able to use !byIndent[indent] only because I know that rawBuild will never return a falsey value (null, undefined, false, NaN, 0, ""). The safer code line would look something like:
if (!(indent in byIndent)) byIndent[indent] = rawBuild(value,indent);
If you just need to memoise objects then it makes sense to assign some unique ID to your objects .
var gID = 0;
function createNode() {
var obj = ...
obj.id = (++gID).toString();
}
and use those obj.id's as keys in your memo collection.
That would be fastest and least greedy solution.
Update:
If you want that id property to do not clash with existing properties
then you can create non-enumerable properties using standard ES5.1 Object.createProperty() (with some unique name) or to use ES6 symbols:
var gID = 0;
var gUidSym = Symbol("uid");
function getUidOf(obj) {
return obj[gUidSym]
|| (obj[gUidSym] = (++gID).toString());
}

Problems with JavaScript "for in" loop

I have an array of objects which will be the basis for a certain menu in my website. It will be build using JavaScript:
[
{"menuName":"Contact Info","sectionName":"contacts"},
{"menuName":"Facilities","sectionName":"facilities"},
{"menuName":"Locations","sectionName":"locations"},
{"menuName":"Packages","sectionName":"packages"},
{"menuName":"Policies","sectionName":"policies"},
{"menuName":"Reviews","sectionName":"reviews"},
{"menuName":"Rooms","sectionName":"rooms"}
]
So I decided to use the "for in loop" so that I won't have to deal with indexes and lengths. I expect seven items to appear in the menu when it gets built (I'll be using <ul> and <li>).
When I was debugging and accidentally added a background color to the <li>, is when all hell broke loose. I found at least 30 empty <li> after the visible 7th menu <li>.
Why is this happening?
EDIT:
Here's the loop. The loop creates another object for another function to parse later on. (It creates an <li> with an <a> inside with properties provided by the previous array.) I know that the other function works fine because when I change this "for-in" loop to an ordinary for loop, or while loop, it works fine.
this.sectionList = function(menu, id) {
var list = new Array();
for(var i in menu) {
var listItem = {
"element" : "li",
"contains" : [{
"element" : "a",
"attr" : {
"href" : menu[i].sectionName + ':' + id
},
"contains" : menu[i].menuName
}]
}
list.push(listItem);
}
}
for in iterates over object properties. Javascript arrays are just a specific kind of object with some handy properties to help you treat them as just arrays, but they still have internal object properties .. and you don't mean to iterate over these).
So, you shouldn't use for in to iterate over arrays. This only became evident to you when you added your background colour, but it'll always be the case.
Instead, loop with a counter and array .length.
Your object gets methods and properties passed by JavaScript itself. Those are methods that every object gets when its created.
You have to use .hasOwnProperty to find only the properties and methods you assigned to the object!
for(var i in myObject){
if(myObject.hasOwnProperty(i)){
console.log(myObject.i);
}
}
Hope that helps!
Here are two articles that helped me two understand it better:
http://bonsaiden.github.com/JavaScript-Garden/#object.hasownproperty
http://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/
I see no difference between the two ways of iterating your data structure in this jsFiddle: http://jsfiddle.net/jfriend00/HqLdk/.
There are lots of good reasons why you should not use for (x in array) on arrays. The main reason is that that type of iteration iterates over the properties of the object, not just the array elements. Those properties can include other properties of the array if any have been added where as the for (var i = 0; i < array.length; i++) method will never have issues with added properties because by definition, it only iterates over the array elements.
It is somewhat luck that when no additional properties have been added to the array object, that iterating over the properties happens to include just the array elements. The language does not want you to iterate array elements that way. You should iterate arrays with
for (var i = 0; i < array.length; i++).
I understand the seduction of the simpler syntax, but it is not the right way to do it.

Adding functions to javascript's Array class breaks for loops

I was looking for a way to add max/min functions to JavaScript's Array class, which seemed to be a solved problem: JavaScript: min & max Array values?. However, when I tried using that, I started getting errors from my code. It turns out that this approach doesn't work with loops.
for(i in myArray) { console.log(i) }
prints out
1
2
3
max
min
Is there another approach I can use?
The accepted solution solves your immediate problem, but extending core objects is generally a bad idea. What happens when you include a library later that uses for..in? Or when you forget months later and use the wrong approach in a different section of your code?
Another option is to wrap and extend. Create a new type that uses an instance of Array as its prototype:
function ArrayThing() {}
ArrayThing.prototype = new Array();
Now you've got an object that you can extend without affecting Array:
ArrayThing.prototype.max = function() {
return Math.max.apply(null, [].slice.call(this, 0))
}
ArrayThing.prototype.min = function() {
return Math.min.apply(null, [].slice.call(this, 0))
}
var list = new ArrayThing();
// standard array methods still work
list.push(5);
list.push(22);
list.push(0);
list.push(-14);
list.length // => 4
// as do your new custom methods
list.max() // => 22
list.min() // => -14
This won't work in every situation, but unless you're sure you really, really need an Array, this is a useful alternative.
The for...in loop is used for looping through properties of an object. If you want to get the values from your array, you can do this:
for (var i = 0; i < myArray.length; i++)
{
console.log(myArray[i])
}
for in is a common Javascript trap. Instead of behaving like a foreach from other languages, it actualy enumerates all properties in a given object.
Since Javascript Arrays happen to have a property for each index using for in works sometimes, but as you have seen, it also enumerates any other properties you add. Another issue is that the for in is not guaranteed to go through the properties in any particular order, so your results can vary depending on which browser/runtime you use.
It is safer, then, to just use a boring for loop instead. There are many for loop idioms in Javascript, so I will list some:
Regular for loop:
for(i=0; i<arr.length; i++){
Regular loop, caching the length:
for(i=0, n=arr.length; i<n; i++){
Loops over arrays of objects/NodeLists:
for(i=0; obj=arr[i]; i++){ //this works as long as the array has no falsy values
foo(obj)
You need to apply a check using hasOwnProperty.
However this needs to applied wherever you are looping.
i.e:
for(i in myArray)
{
if(arr.hasOwnProperty(i))
{
console.log(i);
}
}
There's a more modern (IE9+) way to do this now:
var g = [];
Object.defineProperty(g, "log", {
enumerable: false,
configurable: false,
writable: false,
value: function(){ console.log(this); }
});
g.push(5);
g.push(9);
var t;
for (t in g){
console.log(g[t]);
}
prints 5 and 9 but does not list the "log" function
g.log() -> echos [5,9]
The key to this working is being able to flag a property as "enumerable: false" with marks the property as something that shouldn't be iterated over.
More on Object.defineProperty here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Why is 'for(var item in list)' with arrays considered bad practice in JavaScript?

Given a simple zero based, numerically indexed array:
var list = ['Foo', 'Bar', 'Baz'];
Many times, I have noticed that when someone suggests looping through variables in an array like this:
for(var item in list) { ... }
...there's almost certainly someone suggesting that that's bad practice and suggests an alternative approach:
var count = list.length;
for(var i = 0; i < count; i++) {
var item = list[i];
...
}
What's the reasoning for not using the simpler version above and to use the second example instead?
First, the order of the loop is undefined for a for...in loop, so there's no guarantee the properties will be iterated in the order you want.
Second, for...in iterates over all enumerable properties of an object, including those inherited from its prototype. In the case of arrays, this could affect you if your code or any library included in your page has augmented the prototype of Array, which can be a genuinely useful thing to do:
Array.prototype.remove = function(val) {
// Irrelevant implementation details
};
var a = ["a", "b", "c"];
for (var i in a) {
console.log(i);
}
// Logs 0, 1, 2, "remove" (though not necessarily in that order)
Speed?
for(..;..;..) loop proved to be 36 times faster than for .. in when I tested it here.
Link courtesy this SO answer
for ... in ... doesn't return items of list, but instead enumerates array properties.
For that reason alone, it cannot act as a replacement of for (i=0; i<arr.length; i++) loop.
The appropriate alternative is for ... of ... construct. It enumerates values of an iterable object, such as an array. You can read more about it on MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
It's supported by the relevant modern browsers (Internet Explorer doesn't count, with it being replaced by Microsoft Edge). If you can afford not supporting older browsers, it's probably the way to go. You can check the convenient browser support table at the end of aforelinked MDN page to see which browser versions actually allow for ... of ... usage.
If you use for/in like that, item enumerates through string values "0", "1", ..., so not the actual objects in the list. So the the 'item' in the first snippet is more like the i in the second snippet,not the item. Furthermore string values are enumerated where you'd expect numbers. And you get in trouble when you properties to the list, like array.ID = "a123", as they will get enumerated also.
But with these downsides, I still think the syntax is very useful, if your team is aware of what it does.
Add list.foo = bar; and try to use simple for.
If you don't use some libraries(like prototypeJs) and don't add any new properties to array object - you can use simple for-statement.

Most efficient way to convert an HTMLCollection to an Array

Is there a more efficient way to convert an HTMLCollection to an Array, other than iterating through the contents of said collection and manually pushing each item into an array?
var arr = Array.prototype.slice.call( htmlCollection )
will have the same effect using "native" code.
Edit
Since this gets a lot of views, note (per #oriol's comment) that the following more concise expression is effectively equivalent:
var arr = [].slice.call(htmlCollection);
But note per #JussiR's comment, that unlike the "verbose" form, it does create an empty, unused, and indeed unusable array instance in the process. What compilers do about this is outside the programmer's ken.
Edit
Since ECMAScript 2015 (ES 6) there is also Array.from:
var arr = Array.from(htmlCollection);
Edit
ECMAScript 2015 also provides the spread operator, which is functionally equivalent to Array.from (although note that Array.from supports a mapping function as the second argument).
var arr = [...htmlCollection];
I've confirmed that both of the above work on NodeList.
A performance comparison for the mentioned methods: http://jsben.ch/h2IFA
not sure if this is the most efficient, but a concise ES6 syntax might be:
let arry = [...htmlCollection]
Edit: Another one, from Chris_F comment:
let arry = Array.from(htmlCollection)
I saw a more concise method of getting Array.prototype methods in general that works just as well. Converting an HTMLCollection object into an Array object is demonstrated below:
[].slice.call( yourHTMLCollectionObject );
And, as mentioned in the comments, for old browsers such as IE7 and earlier, you simply have to use a compatibility function, like:
function toArray(x) {
for(var i = 0, a = []; i < x.length; i++)
a.push(x[i]);
return a
}
I know this is an old question, but I felt the accepted answer was a little incomplete; so I thought I'd throw this out there FWIW.
For a cross browser implementation I'd sugguest you look at prototype.js $A function
copyed from 1.6.1:
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
It doesn't use Array.prototype.slice probably because it isn't available on every browser. I'm afraid the performance is pretty bad as there a the fall back is a javascript loop over the iterable.
This works in all browsers including earlier IE versions.
var arr = [];
[].push.apply(arr, htmlCollection);
Since jsperf is still down at the moment, here is a jsfiddle that compares the performance of different methods. https://jsfiddle.net/qw9qf48j/
To convert array-like to array in efficient way we can make use of the jQuery makeArray :
makeArray: Convert an array-like object into a true JavaScript array.
Usage:
var domArray = jQuery.makeArray(htmlCollection);
A little extra:
If you do not want to keep reference to the array object (most of the time HTMLCollections are dynamically changes so its better to copy them into another array, This example pay close attention to performance:
var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.
for (var i = 0 ; i < domDataLength ; i++) {
resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
}
What is array-like?
HTMLCollection is an "array-like" object, the array-like objects are similar to array's object but missing a lot of its functionally definition:
Array-like objects look like arrays. They have various numbered
elements and a length property. But that’s where the similarity stops.
Array-like objects do not have any of Array’s functions, and for-in
loops don’t even work!
This is my personal solution, based on the information here (this thread):
var Divs = new Array();
var Elemns = document.getElementsByClassName("divisao");
try {
Divs = Elemns.prototype.slice.call(Elemns);
} catch(e) {
Divs = $A(Elemns);
}
Where $A was described by Gareth Davis in his post:
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
If browser supports the best way, ok, otherwise will use the cross browser.
I suppose that calling Array.prototype functions on instances of HTMLCollection is a much better option than converting collections to arrays (e.g.,[...collection] or Array.from(collection)), because in the latter case a collection is unnecessarily implicitly iterated and a new array object is created, and this eats up additional resources. Array.prototype iterating functions can be safely called upon objects with consecutive numeric keys starting from [0] and a length property with a valid number value of such keys' quantity (including, e.g., instances of HTMLCollection and FileList), so it's a reliable way. Also, if there is a frequent need in such operations, an empty array [] can be used for quick access to Array.prototype functions; or a shortcut for Array.prototype can be created instead. A runnable example:
const _ = Array.prototype;
const collection = document.getElementById('ol').children;
alert(_.reduce.call(collection, (acc, { textContent }, i) => {
return acc += `${i+1}) ${textContent}` + '\n';
}, ''));
<ol id="ol">
<li>foo</li>
<li>bar</li>
<li>bat</li>
<li>baz</li>
</ol>
Sometimes, Even You have written code the correct way, But still it doesn't work properly.
var allbuttons = document.getElementsByTagName("button");
console.log(allbuttons);
var copyAllButtons = [];
for (let i = 0; i < allbuttons.length; i++) {
copyAllButtons.push(allbuttons[i]);
}
console.log(copyAllButtons);
you get empty array.
Like, This
HTMLCollection []
[]
Console_javascript
For Solving this problem, You have to add link of javascript file after body tag in html file.
<script src="./script.js"></script>
As you can see below,
html_file
Final Output
HTMLCollection(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b, b: button#b]
(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b]

Categories

Resources