Why for-in iterates through array methods also? - javascript

on a page I use similar syntax to google analitycs code, to pass parameters to another script. I iterate through the array and try to construct query part of URL according to the parameters in the included script.
The problem is the following iterates through the javascript array objects methods also and mess up resulting queryString.
index.html:
<script>
var params = [];
params['first'] = '1';
params['second'] = '2';
(function() {
var vs = document.createElement('script');
vs.async = true; vs.type = 'text/javascript';
vs.src = 'http://my.domain.com/js/includedScript.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(vs, s);
})();
</script>
includedScript.js:
function(paramsArray) {
tmpArray = [];
for(i in paramsArray) {
tmpArray.push(i + '=' + escape(paramsArray[i]));
}
var queryString = tmpArray.join('&');
}(params);
I got (shortened):
queryString == 'first=1&second=2&push&$family=function%20%28%29%20%7B%0A%20%20%20%20return%20lower%3B%0A%7D&$constructor=function%20Array%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D&pop=function%20pop%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D&push=function%20push%28%29%20%7B%0A%'
I expect:
queryString == 'first=1&second=2'
It's strange, that on my localhost blank page it's working well. Could some other javascript on the index.html page collide with my code? How can I fix the collision by changing only my code (preferably only the includedScript.js file)?
Thanks in advance.

One small change:
var params = {}; // notice the difference here!
params['first'] = '1';
params['second'] = '2';
and another one here
for(i in paramsArray) {
if(paramsArray.hasOwnProperty(i)){
tmpArray.push(i + '=' + escape(paramsArray[i]));
}
}
JavaScript's Array should only be used with numeric indexes. For associative arrays use Object instead. Also to make sure that the properties you get were added by you and do not belong to the a prototype use hasOwnProperty.
See the documentation on mozilla.org for more info.
An array is an ordered set of values
associated with a single variable
name. Note that you shouldn't use it
as an associative array, use Object
instead.
Also you could read this article:
http://andrewdupont.net/2006/05/18/javascript-associative-arrays-considered-harmful/
Note: The for ... in JavaScript construct has no connection with Array. Its purpose is to iterate over Object properties, not Array elements. In JavaScript properties also include methods.
Fun with JavaScript.
Try the following code and tell me if you're surprised:
var x = [];
x['one'] = 1;
x['one'] = 2;
alert(x.length);
then try this one:
Object.prototype.myProp = 123;
var x = {one: 1, two: 2}
for(var prop in x){
alert(x[prop]);
}

You want to do this
for (i in paramsArray) {
if (paramsArray.hasOwnProperty(i)) {
....
}
}

You should not use for..in to iterate through array elements. Things can be prototyped onto an object automatically to make use of DOM methods or programatically by other javascript on a page (like prototyping).
You should instead get a count of array elements using .length and a normal for loop.
var l = paramsArray.length;
for (var c = 0; c < l; c++) {
// do stuff
}

duncan's answer is correct, and that is because the for-in loop treats the paramsArray as an object, not an array.
And as Alin mentioned, use for (var i = 0; i < paramsArray.length; i++) for arrays.

This is usually an issue when looping using for...in, which is never really recommended.
A way around it is to use the .hasOwnProperty() method to ensure you are getting your own variables:
for (var k in foo) {
if (foo.hasOwnProperty(k)) {
// do something with foo[k]
}
}
Or as others have suggested, use a regular for loop:
for(var k = foo.lenght-1; k >= 0; --k) {
// do something with foo[k]
}
Which is more efficient (especially when reversed)

I love Array.forEach(). It takes a function as an argument. That function receives the element, index, and original list as arguments.
["a","b","c"].forEach(function(item, index, list)
{
console.log("the item at index " + index + " is " + item);
});
To make it compatible with older versions of IE, see here.

Related

Looping through variable names as strings, and destroying them

I have a list of variables (let's call them obj1, obj2, etc.). I would like to loop through all of them and, if they exist, destroy them. Obviously .destroy() is an external call.
Why does this work:
for (var i = 0; i < 10; i++) {
var obj = "obj" + i.toString();
if (window[obj]) {
window[obj].destroy();
}
}
But this doesn't:
var objs = [];
for (var i = 0; i < 10; i++) {
objs.push("obj" + i.toString());
if (objs[i]) {
objs[i].destroy(); //throws a TypeError
}
}
And is there a better solution that's more like the second, and doesn't involve accessing global scope via window? Please don't say eval().
objs.push("obj" + i.toString()); will push a string to the objs array. But strings don't have a .destroy property. In comparison, window["obj" + i.toString()] tries to reference a property on the window object by that name. (The value in that property, or undefined, will be pushed to objs)
It would be better to restructure your script so that rather than searching through the window object for variables of a certain name, you instead put all the related variables into an array at the start, so that you can then iterate through the array and destroy() them directly:
const objs = [
<someObj1>,
<someObj2>,
...
];
objs.forEach(obj => obj.destroy());
In the first example, you are using the string as the key to the object window, like this:
window.obj1
and then calling destroy on it.
The second example doesn't work because what you are pushing into the array is a string,
"obj" + i.toString()
You are calling destroy in the string itself since you created an array of strings. That's why you get the TypeError.
If you bother to use window object, why don't you clone it to a new object called container? That may work for you. Thanks
const container=window;
for (var i = 0; i < 10; i++) {
var obj = "obj" + i.toString();
if (container[obj]) {
container[obj].destroy();
}
}

Add a key to an object with keys of an existing array with objects

I've got an array of objects array = [object1, object2, ...], each of them has some keys object1 = { key1: 'value1', ... }. I want to add a key this way:
$rootScope.array[i].newKey = 'someValue'
But angular tells me that $rootScope.array[i] is undefined.
What I've noticed from console is that the objects get the new key but the console still says the same.
You should use less than and not less or equal than comparator.
$scope.init = function () {
for (i = 0; i < /* not <= */ $rootScope.meatTypes.length; i++) {
console.log("I am showing the meatypes:");
console.log($rootScope.meatTypes);
$rootScope.meatTypes[i].counter = '0';
counterOperations.setCounters(i, 0);
}
$rootScope.total = 0;
counterOperations.setTopCounter(0);
};
because when i equals $rootScope.meatTypes.length then $rootScope.meatTypes[i] is undefined.
You are trying to access a member of the array that does not exist.
You need to create a new object and push it onto the array:
$rootScope.array.push({'key1': 'someValue'});
You did not mention lodash, but when I see someone encounter an issue like this, I want to offer the recommendation of using lodash (or underscore.js).
With lodash, you would do something like so, using _.set, which defensively protects against your described issue by automatically adding the necessary elements in the path:
_.set($rootScope, ['array', i, 'newKey'], 'someValue');
This library, properly utilized, solves many issues that you can have with setting and getting variables, ase well as other super useful tools. It has been a major life-saver (and time-saver) for us on our projects.
Like this you can add
$rootScope.array[i] = {}; // first we should create an object on that array location
$rootScope.array[i]['newKey'] = 'someValue'; // then only we can add values
EDIT:
$scope.init = function () {
for (i = 0; i <= $rootScope.meatTypes.length; i++) {
console.log("I am showing the meatypes:");
console.log($rootScope.meatTypes);
**// This is needed**
$rootScope.meatTypes[i]={};// here we should tell that metaType[newItem] is an object other wise it treat it as undefined
$rootScope.meatTypes[i].counter = '0';
counterOperations.setCounters(i, 0);
}
$rootScope.total = 0;
counterOperations.setTopCounter(0);
};

How to get the indexOf an object within an AngularJS response object collection?

Use Case
I have a collection of objects returned from a REST request. Angular automatically populates each element with a $$hashKey. The problem is that when I search for an object in that array without the $$hashKey, it returns -1. This makes sense. Unfortunately, I don't have knowledge of the value of $$hashKey.
Question
Is there a more effective way to search for an object within an object collection returned from a REST request in AngularJS without stripping out the $$hashKey property?
Code
function arrayObjectIndexOf(arr, obj) {
var regex = /,?"\$\$hashKey":".*?",?/;
var search = JSON.stringify(obj).replace(regex, '');
console.log(search);
for ( var i = 0, k = arr.length; i < k; i++ ){
if (JSON.stringify(arr[i]).replace(regex, '') == search) {
return i;
}
};
return -1;
};
angular.equals() does a deep comparison of objects without the $ prefixed properties...
function arrayObjectIndexOf(arr, obj){
for(var i = 0; i < arr.length; i++){
if(angular.equals(arr[i], obj)){
return i;
}
};
return -1;
}
Since it's angular, why not use filtering:
$filter('filter')(pages, {id: pageId},true);
where pages is the array and id is the object property you want to match and pageId is the value you are matching with.
Ok, so this is a bit ugly, but it's the simplest solution:
function arrayObjectIndexOf(arr, obj) {
JSON.parse(angular.toJson(arr)).indexOf(obj)
}
The angular.toJson bit strips out any attributes with a leading $. You might just want to store that clean object somewhere for searching. Alternatively, you could write your own comparison stuff, but that's just bleh.
Use lodash's find, where, filter to manipulate collections, check docs http://lodash.com/docs

Javascript for...in Loops in Chrome Sample Extension

I was editing Chrome's sample oauth contacts extension
when I came across an interesting for-loop in line 7 of contacts.js:
for (var i = 0, contact; contact = contacts[i]; i++) {
variable i was never used in the body of the for loop, so it seemed like a typical "for...in" loop. I tried replacing the for-loop with
for (contact in contacts) {
but when I ran the extension, all my contacts came back undefined
Here is the full for-loop from the extension
for (var i = 0, contact; contact = contacts[i]; i++) {
var div = document.createElement('div');
var pName = document.createElement('p');
var ulEmails = document.createElement('ul');
pName.innerText = contact['name'];
div.appendChild(pName);
for (var j = 0, email; email = contact['emails'][j]; j++) {
var liEmail = document.createElement('li');
liEmail.innerText = email;
ulEmails.appendChild(liEmail);
}
div.appendChild(ulEmails);
output.appendChild(div);
}
What the code given does
What that does is evaluate what contacts[i] is and whether it is truthy or not, while at the same time caches the array element of the applicable index.
It's equivalent to the following code (note that in this example ++i has the same side effect as i++):
for (var i = 0; contacts[i]; ++i)
{ var contact = contacts[i];
// use contact
}
This could be interpreted as something like the following:
If !contacts[i] is false (i.e. it is truthy) continue the loop.
Otherwise, end the loop (it is falsy).
If the goal of that code was to iterate through all of an array, the problem would be that if you wanted to iterate through an element but it was falsy, it would end the entire loop instead of performing the (likely) intended effect. Take this example:
var foo = [1, 3, 5, 7, 9, 0, 2, 4, 6, 8];
// example for-loop given
for (var i = 0; foo[i]; ++i)
{ var bar = foo[i];
console.log('example: ' + bar);
}
// "normal" way of iterating through array
for (var i = 0, l = foo.length; i < l; ++i)
{ var bar = foo[i];
console.log('normal: ' + bar);
}
You'd find that the example only logs up to the number 9, while the "normal" way goes through the entire array. Of course though, if you could guarantee that all values within the array would be truthy (e.g. all array elements are objects), then this isn't that much of an issue.
What for-in does and why it doesn't work
You tried to replace that code with the following:
for (contact in contacts) { /*code here*/ }
However, this doesn't work for a number of reasons:
contact is a string of the property name, not the value of it. Take this example:
var foo =
{ bar1: 1
, bar2: 2
, bar3: 3
, bar4: 4
, bar5: 5 };
for (var i in foo) console.log(i);
What you get back is the property name (i.e. "bar1, bar2...") instead of the value. To do so for an object, you'd have to do something like the following:
for (var i in foo)
{ var bar = foo[i];
console.log(bar);
}
Now you should get back "1,2,3,4,5" on separate lines. If you got that, and some other things, you might be have defined items on Object.prototype - which is why it's generally a bad idea, unless it really makes the code cleaner, and there is a substantial purpose for doing so. To filter these out, add a hasOwnProperty() check:
for (var i in foo) if (foo.hasOwnProperty(i))
{ var bar = foo[i];
console.log(bar);
}
The upcoming version of ECMAScript (the "standard" version of JavaScript, minus the DOM) will have something called for-of loops, which will make it easier to do this sort of thing.
For-in loops generally aren't meant for arrays (it is possible, but it's just not a good idea usually). If you need to use for-in, you probably should be using an object instead - all arrays are objects, just that arrays have special internal length property and a few other things.
contact is an implied global, big no-no. In fact, implied globals are banned in strict mode. Use a variable declaration (inside or outside the for-in loop, doesn't matter) to solve this issue.
It's just learning about how JavaScript works and where to apply its various methods of doing things - some are more suitable than others in particular situations.
Here you are using an array,not an object.
Though using for..in outputs the same result as a normal for loop,this would be my answer.
MyRecommendation:
Use for..in for iterating over objects:
for..in iterates over properties of an object.
Note:the order of iteration is arbitary.
var Myobj = {
a: 1,
b: 2,
c: 3
};
for ( var prop in Myobj ) {
console.log(prop); // a...b...c
console.log(Myobj[prop]); // 1...2...3
}
but with this the problem is it will continue searching for enumerable properties up the prototype chain.So unless you dont use hasOwnProperty,it will iterate over local object and the prototype it is attached to.
//Improved version of above code:
for (var prop in Myobj) {
if ( Myobj.hasOwnProperty(prop) ) {
// prop is actually obj's property (not inherited)
console.log(prop); // a...b...c
console.log(Myobj[prop]); // 1...2...3
}
}
Use for loop for iteration over an array
for loop iterates over an array in sequential way.

How do i create variable names using contents of an array using javascript

i have created an array,
var myBuildingName=['A1','A2','A3','A4'];
where A1,A2,A3 and A4 are the names obtained through user input.
i now want to create arrays that have names A1,A2,A3 and A4.
i have tried using
for(var i=0;i<myBuildingName.length;i++)
{
var myBuildingName[i]=[];
}
but it does not work...
please help.
You create a master parent object and use the array values as keys into the object where you can store an array for each.
var myBuildingName=['A1','A2','A3','A4'];
var master = {};
for (var i = 0; i < myBuildingName.length; i++) {
master[myBuildingName[i]] = [];
}
Then, you can access the data like:
var a1Array = master['A1'];
or
var firstA1Item = master['A1'][0];
If you actually wanto create variables with those names (which I won't recommend), you'd have to eval() them. So:
for(var i=0;i<myBuildingName.length;i++)
{
eval("var " + myBuildingName[i] + " = [] "); // This creates Array variables called A1, A2 etc.
}
Again, the above method is NOT recommended. You should assign the names as keys to an object literal, like:
var myStuff = {};
for(var i=0;i<myBuildingName.length;i++)
{
var myStuff[myBuildingName[i]] = [];
}
You can't access the local variable object (except in global code), so you can't add properties other than by variable declaration. For global code in the global context you could do:
var global = this;
for ( ...) {
global[myBuildingName[i]] = []
}
but you can't do that for function code in function context. See jfriend00's answer.
Here's a demo
var myBuildingName = ['A1', 'A2', 'A3', 'A4'];
function arrayFromNames(arr) {
var store = {}; //storage for the arrays
for (var i = 0; i < arr.length; i++) { //for each in the passed names
store[arr[i]] = []; //add to the storage an array with the corresponding name
}
return store; //return the storage
}
var nameArrays = arrayFromNames(myBuildingName); //build using your array
console.log(nameArrays);​
//you now have:
//nameArrays.A1, nameArrays.A2,...
//or
//nameArrays['A1'], nameArrays['A2'],...
You have received a lot of great answers, if one of them serves your need, you should accept that answer. The fact that this is still open makes us think no-one has quite answered your question the way you had hoped.
If that is the case, I can only assume that you wanted to use those variables in a global scope.
var myBuildingName=['A1','A2','A3','A4'];
for (var i = 0; i < myBuildingName.length; i++) {
window[myBuildingName[i]] = [];
}
Now you can access your variables 'normally'.
A1.push('test');
Note: this is horrible practice, since you should never pollute the global space.

Categories

Resources