Array and Hash (Associative array) in JavaScript v.3.0

L

Lasse Reichstein Nielsen

I agree with everything else you write, so I'll insist on nitpicking
this one thing:
The second slice operation is the same as:

arrayTwo = arrayOne.slice( 99, arrayOne.length );

or

arrayTwo = [ arrayOne[99], arrayOne[100] ];

These two are not completely equivalent, as the latter satisfies "0 in
arrayTwo" (or "arrayTwo.hasOwnProperty(0)"), whereas the first does
not. The equivalent would be:

arrayTwo = [,arrayOne[100]];

Ahem. Anyway ...
The ECMAScript Language specification is not an authority on arrays in
general, but it does prescribe the basic properties of JavaScript
arrays and therefore must be used as the primary reference when
describing their properties or behaviour.

I do believe VK has a point in describing Arrays at two levels: the
implementation and the abstract concept, only he doesn't make the
point very clearly.

The specification only states the implementation, and leaves the
abstraction up to the user. Unlike other languages, ECMAScript doesn't
hide its implementation, which allows us to use it in other ways than
how the abstract concept looks at it - breaking the abstraction.
In as "hacky"" a language as Javascript, I wouldn't put it past its
inventors to consider that a good thing :)

/L
 
V

VK

RobG said:
Using your example and with a copy of the ECMAScript Language
Specification 3rd Edition December 1999 handy:

var arrayOne = new Array();
arrayOne[0] = "foo"
arrayOne[100] = "bar"
var arrayTwo = arrayOne.slice(0,50);
// arrayTwo.length == 50

arrayTwo will consist of the elements 0 up to but not including 50 of
arrayOne and therefore have a length of 50 (as per section 15.4.4.10).
arrayTwo[0] will be "foo"; all the other elements are undefined.

arrayTwo may actually have one defined element and 49 undefined ones,
or just one defined element and a length of 50 - however it is
actually implemented is irrelevant 99.99% of the time.

// But:
var arrayTwo = arrayOne.slice(99,200);
// arrayTwo.length == 2
// Only "in-within array" elements are counted

Which is exactly as per the specification - if the 'end' (in this case
200) is beyond arrayOne's length, then only the elements up to the end
of arrayOne are included.
You then go on to assert:

As you can see, despite that mechanically (on the memory level) we
have only two elements in the entire arrayOne (arrayOne[0] and
arrayOne[100]), programmatically we have one continuos array of 101
element in total:
"foo", undefined, undefined, ... , undefined, "bar"

Which is your interpretation of things.

Sorry, and what is *your* interpretation of things? You just stated
(you were just forced to): "elements from ... to...". Because it's
right: array consists of the number of elements length-1 *when we are
using array as array*.
In the particular for array methods where are always 0...length-1
elements in array. If you have another interpretation of "things" then
state it.

So far your interpretation as I get it could be summarized this way:
"Array consists of length-1 elements which we can see clearly by
applying array methods. Array consists of only defined elements and it
has numeric property 'length'."

With all my respect, it's not an interpretation, but a loosely made
aporia.
 
R

RobG

VK said:
RobG wrote:
[...]
Which is your interpretation of things.


Sorry, and what is *your* interpretation of things?

I think of an array of having as many elements as its length, with
only those that have been defined as taking up any space. But that is
just for convenience - if an array should behave in some way different
to my concept of it but in accordance with its definition, then I will
accept that as a failure of my concept, not of the array.

If ultimately I am unable to reconcile my concepts with the reality of
the platform, I'll use something else.

You just stated
(you were just forced to): "elements from ... to...". Because it's
right: array consists of the number of elements length-1 *when we are
using array as array*.
In the particular for array methods where are always 0...length-1
elements in array. If you have another interpretation of "things" then
state it.

I guess this is where it gets difficult. I don't consider the
undefined elements to actually exist, they are kind of like credit
that I can use if I wish and only exist if I do use them. And I can
use them up to my limit unless I extend my credit.

Gosh, that's a pretty horrid metaphor but there you go.
So far your interpretation as I get it could be summarized this way:
"Array consists of length-1 elements which we can see clearly by
applying array methods. Array consists of only defined elements and it
has numeric property 'length'."

Yes, near enough.
With all my respect, it's not an interpretation, but a loosely made
aporia.

You like your philosophy I see! I had to look 'aporia' up:

“a logical contradiction beyond rational resolution”.

<URL:http://www.litencyc.com/php/stopics.php?rec=true&UID=1578>

You are free to hold that opinion - it's rational to me though maybe a
bit of a spin-out for some! :)
 
R

Richard Cornford

VK said:
RobG wrote:
As you can see, despite that mechanically (on the memory level)
we have only two elements in the entire arrayOne (arrayOne[0]
and arrayOne[100]), programmatically we have one continuos array
of 101 element in total:
"foo", undefined, undefined, ... , undefined, "bar"

Which is your interpretation of things.

Sorry, and what is *your* interpretation of things?
You just stated (you were just forced to): "elements from
... to...". Because it's right: array consists of the number
of elements length-1 *when we are using array as array*.
In the particular for array methods where are always
0...length-1 elements in array. If you have another
interpretation of "things" then state it.

The Array methods; slice, reverse, concat, shift and unshift have (ECMA
262) specified behaviour that masks the nature of their Array object.
They each generate a sequence of numeric values limited by the Array's
length property and then use those numeric values, converted into
strings, with the Array's [[Get]] method to acquire a value and then use
the Array's [[Put]] method to assign that value to a property of an
Array.

Because the [[Get]] method of ECMAScritp objects (and so Arrays) returns
Undefined when the object does not have a property with the
corresponding name, but the [[Put]] method creates a property with the
corresponding name when there is no pre-existing property, the
application of an array method may produce a result that is
significantly different from the original Array.

A good example is the - reverse - method (as it acts upon the array
itself instead of producing an array result).

Using an ECMA 262 conforming browser such as Mozilla:-

var a = [];
a[0] = 'foo';
a[99] = 'bar'

- creates and array with a length property of 100, and counting its
defined elements with:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 2. But then applying:-

a.reverse();

- and repeating:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 100. The act of calling [[Get]] with each number
in the range 0 to (array.length-1) and then calling [[Put]] has created
all of those properties, regardless of whether they existed before the
call to reverse.

Thus, if the Array methods make the array look as if it is a continuous
sequence of properties instead of an object with possibly fewer existing
'array index' properties than 0 to (array.length-1) would accommodate,
that may be because the specified action of the methods is to create an
object that does actually have all of those properties. The results of
those method calls imply nothing about the objects on which they are
called.

In ECMAScript we can create, and work with, an object such as:-

var a = [];
a[4294967294] = 'foo';

- without flooding the entire OS memory map, precisely because the
underlying object does not have 4294967295 existing properties. And we
can enumerate the properties of that object with a for/in loop and
expect to see the result in our lifetime. We can also be sure that
calling the - reverse - method of that object in an ECMA 262 conforming
environment would be a somewhat foolish thing to do.

There is a general concept of what an 'array' is, stressing an ordered
arrangement, and a concept more specific to computing in general that
places more stress on the subscript/index. ECMAScript demonstrates that
the concept can be satisfied by many structures. For practical purposes
it is only necessary that the 'length' of an array (or combination of
its upper and lower bounds, where they may both be variable) expresses
the limits of its contents, and that accessing items within those limits
always has a consistent outcome.

Because the ECMAScript Array's special [[Put]] method may interact with
its - length - property upon assignment operations, and the native
ECMAScript object's [[Get]] method always returns a value (which may be
the Undefined value, if that is the value of the property and whenever
the property does not exist) we have an object that may be treated as a
continuous sequence of ordered storage locations limited by the arrays
length without any need for it to actually possess such a sequence of
storage locations. The general concept is satisfied and can be used in
designing code to be implemented in ECMAScript.

However, while the general concept of an array can be employed in script
authoring it can only benefit the author of such scripts to understand
the nature of the object that they are using. Take Kevin Newman's code
as an example. If the ECMAScript Array implementation really was a
continuos sequence of indexed storage locations then his implementation
would be objectively bad (as it must get slower the more it operated,
and it must consume progressively more memory as the Array got longer).
As it is his implementation is fine from a practical point of view. The
worst that can be said of it is that it doesn't 'feel' right to be using
an Array just for its self-incrementing - length - property (very much a
matter of opinion).

It is not in satisfying the general concept of 'array' that the
ECMAScript author needs to understand the object being used, it is when
they go outside of that concept. There is the use of an Array as an
'associative array' and the oft repeated question as to why the
assignment of a non-'array index' property does not affect the Array's
length. There is the employment of an array as a repository for
name/value pairs that can also be looped over, where the named
properties store integer values that refer to the real vales stored
under 'array index' property names. And host of other applications of
Arrays that can only be properly explained by an appreciation of what an
ECMAScript Array really is.
So far your interpretation as I get it could be summarized
this way: "Array consists of length-1 elements which we can
see clearly by applying array methods. Array consists of only
defined elements and it has numeric property 'length'."

With all my respect, it's not an interpretation, but a loosely
made aporia.

Your unfounded opinion that the Array methods might demonstrate a
non-sparseness in ECMAScript arrays is at the root of your perception of
a contradiction. The object that is an ECMAScript array is quite capable
of being used as if it was a continuos sequence of indexed storage
locations without actually being a continuos sequence of storage
locations. ECMA 262 explains both how and why Arrays behave in the way
they do, both in satisfying the general concept of an 'array' and in
providing behaviour that is well outside of that concept.

Richard.
 
R

Richard Cornford

VK said:
RobG wrote:
As you can see, despite that mechanically (on the memory level)
we have only two elements in the entire arrayOne (arrayOne[0]
and arrayOne[100]), programmatically we have one continuos array
of 101 element in total:
"foo", undefined, undefined, ... , undefined, "bar"

Which is your interpretation of things.

Sorry, and what is *your* interpretation of things?
You just stated (you were just forced to): "elements from
... to...". Because it's right: array consists of the number
of elements length-1 *when we are using array as array*.
In the particular for array methods where are always
0...length-1 elements in array. If you have another
interpretation of "things" then state it.

The Array methods; slice, reverse, concat, shift and unshift have (ECMA
262) specified behaviour that masks the nature of their Array object.
They each generate a sequence of numeric values limited by the Array's
length property and then use those numeric values, converted into
strings, with the Array's [[Get]] method to acquire a value and then use
the Array's [[Put]] method to assign that value to a property of an
Array.

Because the [[Get]] method of ECMAScritp objects (and so Arrays) returns
Undefined when the object does not have a property with the
corresponding name, but the [[Put]] method creates a property with the
corresponding name when there is no pre-existing property, the
application of an array method may produce a result that is
significantly different from the original Array.

A good example is the - reverse - method (as it acts upon the array
itself instead of producing an array result).

Using an ECMA 262 conforming browser such as Mozilla:-

var a = [];
a[0] = 'foo';
a[99] = 'bar'

- creates and array with a length property of 100, and counting its
defined elements with:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 2. But then applying:-

a.reverse();

- and repeating:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 100. The act of calling [[Get]] with each number
in the range 0 to (array.length-1) and then calling [[Put]] has created
all of those properties, regardless of whether they existed before the
call to reverse.

Thus, if the Array methods make the array look as if it is a continuous
sequence of properties instead of an object with possibly fewer existing
'array index' properties than 0 to (array.length-1) would accommodate,
that may be because the specified action of the methods is to create an
object that does actually have all of those properties. The results of
those method calls imply nothing about the objects on which they are
called.

In ECMAScript we can create, and work with, an object such as:-

var a = [];
a[4294967294] = 'foo';

- without flooding the entire OS memory map, precisely because the
underlying object does not have 4294967295 existing properties. And we
can enumerate the properties of that object with a for/in loop and
expect to see the result in our lifetime. We can also be sure that
calling the - reverse - method of that object in an ECMA 262 conforming
environment would be a somewhat foolish thing to do.

There is a general concept of what an 'array' is, stressing an ordered
arrangement, and a concept more specific to computing in general that
places more stress on the subscript/index. ECMAScript demonstrates that
the concept can be satisfied by many structures. For practical purposes
it is only necessary that the 'length' of an array (or combination of
its upper and lower bounds, where they may both be variable) expresses
the limits of its contents, and that accessing items within those limits
always has a consistent outcome.

Because the ECMAScript Array's special [[Put]] method may interact with
its - length - property upon assignment operations, and the native
ECMAScript object's [[Get]] method always returns a value (which may be
the Undefined value, if that is the value of the property and whenever
the property does not exist) we have an object that may be treated as a
continuous sequence of ordered storage locations limited by the arrays
length without any need for it to actually possess such a sequence of
storage locations. The general concept is satisfied and can be used in
designing code to be implemented in ECMAScript.

However, while the general concept of an array can be employed in script
authoring it can only benefit the author of such scripts to understand
the nature of the object that they are using. Take Kevin Newman's code
as an example. If the ECMAScript Array implementation really was a
continuos sequence of indexed storage locations then his implementation
would be objectively bad (as it must get slower the more it operated,
and it must consume progressively more memory as the Array got longer).
As it is his implementation is fine from a practical point of view. The
worst that can be said of it is that it doesn't 'feel' right to be using
an Array just for its self-incrementing - length - property (very much a
matter of opinion).

It is not in satisfying the general concept of 'array' that the
ECMAScript author needs to understand the object being used, it is when
they go outside of that concept. There is the use of an Array as an
'associative array' and the oft repeated question as to why the
assignment of a non-'array index' property does not affect the Array's
length. There is the employment of an array as a repository for
name/value pairs that can also be looped over, where the named
properties store integer values that refer to the real vales stored
under 'array index' property names. And host of other applications of
Arrays that can only be properly explained by an appreciation of what an
ECMAScript Array really is.
So far your interpretation as I get it could be summarized
this way: "Array consists of length-1 elements which we can
see clearly by applying array methods. Array consists of only
defined elements and it has numeric property 'length'."

With all my respect, it's not an interpretation, but a loosely
made aporia.

Your unfounded opinion that the Array methods might demonstrate a
non-sparseness in ECMAScript arrays is at the root of your perception of
a contradiction. The object that is an ECMAScript array is quite capable
of being used as if it was a continuos sequence of indexed storage
locations without actually being a continuos sequence of storage
locations. ECMA 262 explains both how and why Arrays behave in the way
they do, both in satisfying the general concept of an 'array' and in
providing behaviour that is well outside of that concept.

Richard.
 
M

Michael Winter

Michael Winter wrote:
[snip]
I would (and in fact, do) use a list for similar code.

Do you have an example of what you mean by a list?

Using recursion:

var listeners = null;

function Node(d) {
var n = null;

this.notify = function(v) {
d.unFocusHistoryUpdate(v);
if(n) {n.notify(v);}
};
this.add = function(o) {
if(d == o) {return;}
if(n) {n.add(o);}
else {n = new Node(o);}
};
this.remove = function(o) {
if(d == o) {return n;}
if(n) {n = n.remove(o);}
return this;
};
}

function _NotifyListeners(v) {
if(listeners) {listeners.notify(v);}
}
this.AddListener = function(o) {
/* ... */
if(listeners) {listeners.add(o);}
else {listeners = new Node(o);}
/* ... */
}
this.RemoveListener = function(o) {
if(listeners) {listeners = listeners.remove(o);}
};


Using iteration:

var listeners = null;

function _NotifyListeners(v) {
var n = listeners;

while(n) {
n.data.unFocusHistoryUpdate(v);
n = n.next;
}
}
this.AddListener = function(o) {
var n = listeners,
p;
/* ... */
if(n) {
do {
if(n.data == o) {return;}
p = n;
} while((n = n.next));
p.next = {data : o};
} else {
listeners = {data : o};
}
/* ... */
};
this.RemoveListener = function(o) {
var n = listeners.next,
p = listeners;

if(p.data == o) {listeners = n;}
else if(n) {
do {
if(n.data == o) {
p.next = n.next;
return;
}
p = n;
} while((n = n.next));
}
};

[snip]

Mike
 
K

Kevin Newman

This entire post has been very instructive. Thank you for that!

btw, I'm using an object now, and incrementing my own variable - you
have convinced me :)

Kevin N.
 
D

Douglas Crockford

This entire post has been very instructive. Thank you for that!
btw, I'm using an object now, and incrementing my own variable - you
have convinced me :)

You may have drawn the wrong conclusion, perhaps as a consequence of
the large amount of misinformation spewed under this topic. Even the
title of this topic (Array and Hash (Associative array) in JavaScript
v.3.0) contains errors. A better title would have been "Array and
Object in ECMAScript Version 3".

The simple rule is this: If all of your indexes are non-negative
integers, use an array []. Otherwise, use an object {}.

http://www.crockford.com/javascript/survey.html
 
L

Lasse Reichstein Nielsen

Richard Cornford said:
Using an ECMA 262 conforming browser such as Mozilla:-

var a = [];
a[0] = 'foo';
a[99] = 'bar' ....
a.reverse();

- and repeating:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 100.

Then Mozilla isn't ECMA 262 conforming, because ECMA262 v3 section
15.4.4.8 specifies the behavior of Array.prototype.reverse such that
the result should still be 2. Both Opera and IE gives 2.

Mozilla (in my case Firefox v1.04) have other array-related bugs,
e.g.,

var a = [0,,,,,,,,,,,,,,,,,,,19];

also gives a count of 20 elements in Firefox, but 2 in IE, Opera and
the ECMA 262 standard. Also, sort() has the same problem as reverse,
introducing elements that wasn't there in the original (Opera has
a bug there too).
The act of calling [[Get]] with each number
in the range 0 to (array.length-1) and then calling [[Put]] has created
all of those properties, regardless of whether they existed before the
call to reverse.

And the definition of reverse in ECMA 262 does two [[Get]]'s, and
then, depending on whether the object has properties of those names,
do either two [[Put]]'s, one [[Put]] and one [[Delete]], or two
[[Delete]]'s (rather superflous, when it just checked that neither
property existed).


/L
 
R

Richard Cornford

Lasse said:
Richard said:
Using an ECMA 262 conforming browser such as Mozilla:-

var a = [];
a[0] = 'foo';
a[99] = 'bar' ...
a.reverse();

- and repeating:-

var count = 0;
for(var prop in a){
++count;
}
alert(count);

- produces a count of 100.

Then Mozilla isn't ECMA 262 conforming, because ECMA262 v3
section 15.4.4.8 specifies the behavior of
Array.prototype.reverse such that the result should still
be 2. Both Opera and IE gives 2.
<snip>

You are right. And Mozilla is wrong in not having the result of its
Array methods being as sparse and the original object. Though that makes
it less reasonable of VK to assert that the array methods imply that
that arrays are continuous sequences of storage locations.

It also means that Mozilla browsers are open to 'malicious' actions such
as:-

var a = [];
a[4294967294] = 'x';
a.reverse();

- being harmless to IE while having Mozilla grind to a halt, and
eventually cripple the entire OS.

Richard.
 
K

Kevin Newman

Michael said:
Michael Winter wrote:

[snip]
I would (and in fact, do) use a list for similar code.


Do you have an example of what you mean by a list?


Using recursion:

var listeners = null;

function Node(d) {
var n = null;

this.notify = function(v) {
d.unFocusHistoryUpdate(v);
if(n) {n.notify(v);}
};
this.add = function(o) {
if(d == o) {return;}
if(n) {n.add(o);}
else {n = new Node(o);}
};
this.remove = function(o) {
if(d == o) {return n;}
if(n) {n = n.remove(o);}
return this;
};
}

function _NotifyListeners(v) {
if(listeners) {listeners.notify(v);}
}
this.AddListener = function(o) {
/* ... */
if(listeners) {listeners.add(o);}
else {listeners = new Node(o);}
/* ... */
}
this.RemoveListener = function(o) {
if(listeners) {listeners = listeners.remove(o);}
};


Using iteration:

var listeners = null;

function _NotifyListeners(v) {
var n = listeners;

while(n) {
n.data.unFocusHistoryUpdate(v);
n = n.next;
}
}
this.AddListener = function(o) {
var n = listeners,
p;
/* ... */
if(n) {
do {
if(n.data == o) {return;}
p = n;
} while((n = n.next));
p.next = {data : o};
} else {
listeners = {data : o};
}
/* ... */
};
this.RemoveListener = function(o) {
var n = listeners.next,
p = listeners;

if(p.data == o) {listeners = n;}
else if(n) {
do {
if(n.data == o) {
p.next = n.next;
return;
}
p = n;
} while((n = n.next));
}
};

[snip]

Mike

This is pretty spiffy! Thanks :)

Kevin N.
 
K

Kevin Newman

I'm so confused :)

Douglas said:
The simple rule is this: If all of your indexes are non-negative
integers, use an array []. Otherwise, use an object {}.

This is what I originally thought, which is why I used an array. But I'm
using for...in to iterate my array.

Having thought about this, I think it might be best to use a standard
for loop since that will be more consistent with an array, and it would
allow me to only iterate integer indexes, so that if someone messes with
Array.prototype to add Array.push or something similar, it will not get
iterated like it would with for...in.

I'll just have to figure out how to keep the indexes contiguous when I
remove an element (I'm guessing there is a way to do that using
Array.slice, instead of delete - I'll have to experiment).

Thanks for all the info, it's been fun :)

Kevin N.
 
M

Michael Winter

On 01/08/2005 17:31, Kevin Newman wrote:

[snip]
I'll just have to figure out how to keep the indexes contiguous when I
remove an element (I'm guessing there is a way to do that using
Array.slice, instead of delete - I'll have to experiment).

The Array.prototype.splice method[1] can be used to delete (and insert)
elements at arbitrary locations within an array. Essentially, it just
copies values about. However, this is a problem that I alluded to when I
mentioned the differences between sequential and linked data structures.
With the former, insertions and deletions at any location except the end
require all elements after the point of change to be shifted which, in a
large structure, can take a long time. With linked lists, it's simply a
matter of changing the links as the new element can be added anywhere in
memory (for insertion) or just forgotten about (for deletion).

Sequential structures offer fast, random look ups, but as yours will
always be complete iteration through the collection, this is only an
advantage when inserting or deletion at specific locations. Even then,
insertion for you will always be at the end once you've checked that the
new element is not a duplicate (more complete iteration). That just
leaves deletion, which should be rare anyway.

Despite how it may seem, I'm not necessarily pushing for you to use a
list. I do, but it's up to you to decide for yourself. Theoretical
reasons don't always in scripting, particularly as ECMAScript Arrays
don't have the same drawbacks that 'real' arrays do (including
reallocation during insertion).

Mike


[1] Not available in JScript 5 (usually IE5.0) and earlier.
 
G

Grant Wagner

Richard Cornford said:
You are right. And Mozilla is wrong in not having the result of its
Array methods being as sparse and the original object. Though that
makes
it less reasonable of VK to assert that the array methods imply that
that arrays are continuous sequences of storage locations.

It also means that Mozilla browsers are open to 'malicious' actions
such
as:-

var a = [];
a[4294967294] = 'x';
a.reverse();

- being harmless to IE while having Mozilla grind to a halt, and
eventually cripple the entire OS.

Richard.

Not harmless in Internet Explorer 6.0.2900 here. CPU usage climbed to
90+% and stayed there for several minutes. jscript.dll version
5.6.0.8825

In Firefox 1.0.6, CPU rises to 90+% and stays there, eventually the
application generates an error and

Opera 8.02 returns almost immediately from a.reverse(), but attempting
to do -document.write(a);- results in a hung application.
 
L

Lasse Reichstein Nielsen

Grant Wagner said:
var a = [];
a[4294967294] = 'x';
a.reverse();
Opera 8.02 returns almost immediately from a.reverse(), but attempting
to do -document.write(a);- results in a hung application.

That's because the default string representation of that array would
be an "x" followed by 4294967294 commas. You asked for it, prepare to
wait :)

/L
 
M

Martin Honnen

Lasse said:
Then Mozilla isn't ECMA 262 conforming, because ECMA262 v3 section
15.4.4.8 specifies the behavior of Array.prototype.reverse such that
the result should still be 2. Both Opera and IE gives 2.

The bug with reverse has been fixed recently in Mozilla so in a Mozilla
1.8 nightly or Deer Park nightly the result should be 2 too.
Here is the bug:
<https://bugzilla.mozilla.org/show_bug.cgi?id=299738>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top