'Reflecting' a Javascript Array - is this legal? - javascript

I found myself needing to convert from a small integer into a corresponding, arbitrary string. The obvious way to do this is with an Array. But I also wanted to go back the other way: given the string, find the corresponding integer. Obviously I could do this by Array.indexOf(), but handling the 'no match found' case was a bit awkward. But in Javascript, Arrays are Objects, and Objects have properties, so I tried defining a function:
var reflect = function(array) {
for (var i = 0; i < array.length; i++) {
array[array[i]] = i;
}
};
which adds a property corresponding to each array element. So given:
lighting2 = ['AC', 'HOMEEASY_EU', 'ANSLUT'];
reflect(lighting2);
I can write lighting2[1] giving me 'HOMEEASY_EU'; and also lighting2['HOMEEASY_EU'] which gives me 1. Furthermore, lighting2['FOO'] gives me undefined as it ought to. The Array.length property does not appear to be affected by this 'reflection'. The technique works - at least it works in node.js - but I have never seen it before. Is this 'legal'? Will it cause nasty things to happen? Is it likely to be useable in all Javascript implementations?
Cue the Javascript language lawyers!

Related

How to find the owner of a property in Javascript

Ok, because my initial question sounds unclear, so I decided to edit it. My question is how do you find out who defined a certain property, for example, the parseInt function, how do I know on which object it was definded like if parseInt was definded on the window object or the document object or whatever object it is? Thank you
I know the parseInt was definded the window object, I am just using it as an example in general, I am not specifically asking what object definded the parseInt property.
Also, please don't show me jQuery codes since I don't know jQuery that very good.
There is unfortunately no way to determine using code what the variable environment is of a given variable.
As for object properties, they should be obvious if they are myObj.property. If not obvious, it could be possible to use an exhaustive search to look for their existence in certain places, or certain known recursively.
Overall, it is not possible to know without looking at implementation documentation.
I know that to solve my question, we could use Object.prototype.hasOwnProperty(), but that would be very alot of typing because you have to type it out each time you need to know if a certain property is defined on a object. I have decided to write my own function to make this a little easier even though this is of no good practical use, I just wanted to satisfy my curiosity.
function findOwner(property, ownerObjectArray) {
var result = []; // Array to store the objects that the given property is defined on
for (var i = 1; i < arguments.length; i++)
{
var obj = arguments[i]; // the object currently being inspected
var properyList= Object.getOwnPropertyNames(arguments[i]); // a list of all "Owned" properties by this object
for (var j = 0; j < properyList.length; j++)
{
if (property === properyList[j]) result.push(obj.constructor);
}
}
return result.length > 0 ? result : "undefinded";
}
run this method
window.onload = run;
function run()
{
alert(findOwner("parseInt", Array.prototype, window, document)); // passing 3 objects we want to test against to this method. It printed : [object Window], the given property "parseInt" was found on the "Window" object
}

Javascript for loop syntax

As javascript developers we all have to write a lot of for loops. Before a couple of months I saw an alternative syntax, which I really liked. However, I'm now interested, is there any other nice way.
Let's say that I have an array of data representing users in a system. What I did before is:
var users = [
{ name: "A"},
{ name: "B"},
{ name: "C"},
{ name: "D"},
{ name: "E"}
];
var numOfUsers = users.length;
for(var i=0; i<numOfUsers; i++) {
var user = users[i];
// ...
}
There is one additional row var user = users[i];. Normally I feel more comfortable if I have user instead of users[i]. So, the new way:
for(var i=0; user=users[i]; i++) {
// ...
}
I'm also wondering if the second approach produces problems in some of the browsers. One of my colleagues reported that this syntax is a little bit buggy under IE.
Edit:
Thankfully, the answers below pointed me out to the right direction. If some of the elements of the array is falsy then the loop will stop. There is some kind of solution:
for(var i=0; typeof (user=users[i]) !== "undefined"; i++) {
// ...
}
But that's too much for me. So, I guess that I'll use this syntax only when I'm 100% sure that all the elements are truly (which means never :)).
In your “new” approach, you don’t need numOfUsers any more.
As for the potential problems: This approach relies on all users[i] having values evaluating to true for the loop to continue (and user becoming undefined, equal to false and therefor ending the loop after the last user is processed) – but sometimes you might have data where not every record evaluates to true, but “false-y” values might also occur in the data – and in that case, this approach of course fails.
The problem with this approach:
for(var i=0; user=users[i]; i++) {
// ...
}
...is that it assumes user won't be "falsey" (0, "", null, undefined, NaN, or of course false) until you've gone past the end of the array. So it'll work well with an array of non-null object references, but if you then get in the habit of using it, it will bite you when you have an array of numbers, or strings, or such.
The other reason not to declare variables within the for construct is that it's misleading: Those variables are not scoped to the for loop, they're function-wide. (JavaScript's var doesn't have block scope, only function or global scope; ES6 will get let which will have block scope.)
On modern JavaScript engines (or with an "ES5 shim"), you can of course do this:
users.forEach(function(user) {
// ...
});
...which has the advantage of brevity and not having to declare i or numUsers or even user (since it's an argument to the iteration callback, and nicely scoped to that). If you're worried about the runtime cost of doing a function call for each entry, don't be. It'll be washed out by whatever actual work you're doing in the function.
I'm amazed if the second syntax works at all your middle operation should evaluate to true for each loop you want to complete and false as soon as you want to be done looping. As for any issues with your first for loop, a JavaScript is function scoped so that inner var statement will still leak to the containing function (as well as that i). This is different than most other languages that have block scoping. It's not so much of a problem but something to keep in mind if you are debugging.
If you are already using jQuery, you can use the jQuery.each function to loop over your arrays.
In any case you can look at the source code of that function and copy the relevant parts for your own foreach function: http://james.padolsey.com/jquery/#v=1.10.2&fn=jQuery.each

jQuery merge() and array.length data type

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.

Instantiating class in loop in javascript uses last value

First of all, I'm aware there are many questions about closures in JavaScript, especially when it comes to loops. I've read through many of them, but I just can't seem to figure out how to fix my own particular problem. My main experience lies with C#, C++ and some ASM and it is taking some getting used to JavaScript.
I'm trying to populate a 3-dimensional array with new instances of a class (called Tile) in some for loops. All I want to do is pass along a reference to some other class (called Group) that gets instantiated in the first loop (and also added to another array). As you might have guessed, after the loops are done, every instance of the Tile class has a reference to the same Group object, namely the last one to be created.
Apparently instead of passing a reference to the Group object, a reference to some variable local to the function is passed along, which is updated in every iteration of the loop. My assumption is that solving this problem has something to do with closures as this appears to be the case with many similar problems I've come across while looking for a solution.
I've posted some trimmed down code that exposes the core of the problem on jsFiddle:
//GW2 namespace
(function( GW2, $, undefined ) {
//GW2Tile class
GW2.Tile = function(globalSettings, kineticGroup)
{
//Private vars
var tilegroup = kineticGroup;
// console.log(tilegroup.grrr); //Shows the correct value
var settings = globalSettings;
this.Test = function(){
console.log(tilegroup.grrr);
}
this.Test2 = function(group){
console.log(group.grrr);
}
} //Class
}( window.GW2 = window.GW2 || {}, jQuery ));
var zoomGroups = [];
var tiles = [];
var settings = {};
InitArrays();
tiles[0,0,0].Test(); //What I want to work, should give 0
tiles[0,0,0].Test2(zoomGroups[0]); //How I'd work around the issue
function InitArrays(){
var i, j, k, zoomMultiplier, tile;
for(i = 0; i <= 2; i++){
zoomGroups[i] = {};
zoomGroups[i].grrr = i;
tiles[i] = [];
zoomMultiplier = Math.pow(2, i);
for(j = 0; j < zoomMultiplier; j++){
tiles[i,j] = [];
for(k = 0; k < zoomMultiplier; k++){
tile = new GW2.Tile(settings, zoomGroups[i]);
tiles[i,j,k] = tile;
}
}
}
}
Up till now when working with JavaScript, I've generally fiddled with the code a bit to make it work, but I'm tired of using work-arounds that look messy as I know there should actually be some fairly simple solution. I'm just not fond of asking for help, but this is really doing my head in. Any help is very much appreciated.
Multidimensional arrays
The problem
The first issue with your code above is how you are attempting to create multidimensional arrays.
The syntax you are using is:
tiles[0,0,0]
However, the way JavaScript will interpret this is:
tiles[0]
Accessing a multidim array
If you wish to access a multidim array you have to use:
tiles[0][0][0]
And to create a multidim array you would need to do the following:
tiles = [];
tiles[0] = [];
tiles[0][0] = [];
tiles[0][0][0] = 'value';
or:
tiles = [[['value']]];
With respect to your code
In your code you should be using:
tiles[i][j][k] = tile;
But you should also make sure that each sub array actually exists before setting it's value, otherwise you'll get undefined or illegal offset errors.
You can do this by way of:
(typeof tiles[i] === 'undefined') && (tiles[i] = []);
(typeof tiles[i][j] === 'undefined') && (tiles[i][j] = []);
tiles[i][j][k] = tile;
Obviously the above can be optimised depending on how you are traversing your loops i.e. it would be best to make sure the tiles[i] level exists as an array before stepping in to the the [j] loop, and then not worry about checking it's existence again whilst stepping j.
Other options
Depending on what your dataset is, or at least what you hope to do with the tiles array it can be worth considering using an object instead:
/// set up
tiles = {};
/// assignment
tiles[i+','+j+','+k] = 'value';
However this method is likely to be slower, although I've been proved wrong a number of times by my assumptions and differing JavaScript interpreters. This would probably be were jsPerf would be your friend.
Optimisation
One benefit of using the tiles[i][j][k] approach is that it gives you the chance to optimise your references. For example, if you were about to process a number of actions at one level of your multidimensional array, you should do this:
/// set up
var ij = tiles[i][j];
/// use in loops or elsewhere
ij[k] = 'value'
This is only of benefit if you were to access the same level more than once however.

Length of Array Differs In Internet Explorer With Trailing Comma

I'm currently working with some data using Javascript that is in the form of an array. The array may contain an empty entry at the end, such as [1,2,]. In Google Chrome and Firefox, the length of that example would be 2; however, in IE, the length is 3.
In short: Internet Explorer is giving a different length for an array in Javascript than Google Chrome and Firefox. Is there a way to standardize this behavior across all browsers?
Code:
var a = [1,];
alert(a.length);
EDIT:
A lot of answers are saying not to have a trailing comma, however, the data is given to me in this way.
NEVER have trailing commas in IE. Period.
That goes for ARRAYs too
Javascript Browser Quirks - array.Length
To handle your edit this works (tested in in IE8):
if (a[a.length-1]==null) a.length--; // or a.pop()
For a safer test, please look at the other suggestion on this page: Length of Array Differs In Internet Explorer With Trailing Comma - DEMO HERE
By the way, never heard the words elision or elided before - learn something new here every day
No. IE incorrectly interprets a single trailing comma as an elision and adds one to the length when it shouldn't (ECMA-262 sect. 11.1.4).
Edit
To clear up the confusion here, IE treats a single trailing comma in an array literal (incorrectly) as an elision, which means it increments array's length property but does not create a property. In other words, given:
var a = [0,1,];
In IE, a.length is 3, but there is no property a[2]. So if an appropriate solution is to remove only elided members from the end of an array (which is likely the best solution if they are an issue), then:
function tidyTrailingElisions(array) {
var i = array.length;
while (!array.hasOwnProperty(--i)) {}
array.length = ++i;
return array;
}
will remove only elided members from the end of the array (that is, properties that don't exist), it will not remove them elsewhere, nor will it waste time iterating over the entire array (which can result in elided members being added as undefined). To add to Array.prototype:
Array.prototype.tidyTrailingElisions = function() {
var i = this.length;
while ( !this.hasOwnProperty(--i)) {}
this.length = ++i;
return this;
};
Note that this is how Array.prorotype.filter works, it doesn't iterate over elided members (it uses a hasOwnProperty test and removes any elided mebers as part of filtering the array).
Try removing empty elements:
a = a.filter(function(){return true});
Then it should work the same way in IE and other browsers.
Update: if the JS version is lower than 1.6, try this:
Array.prototype.clean = function() {
for (var i = 0; i < this.length; i++) {
if (this[i] == undefined) {
this.splice(i, 1);
i--;
}
}
return this;
};
a.clean()

Categories